强制实现抽象方法是否不符合Python风格?
在设计类的时候,抽象方法可以非常有用。根据我的了解,Python并没有强制要求子类一定要实现抽象方法的机制。在我的代码中(见下面的例子),我在基类里加入了一个失败的断言,这样如果子类没有实现这个方法,就会在运行时出现错误。这这样做算不算不符合Python的风格呢?
class Dog(Animal):
def speak(self):
return "bark"
class Animal():
def speak(self):
assert(False) #abstract
4 个回答
好吧,如果你在基础类里没有包含 speak
这个方法,但又不小心用到了它,代码就会出错。问题是,这种情况发生的可能性有多大,以及怎么告诉用户(这里用 NotImplementedError
可能比用断言更合适)。
抽象基类(ABCs)是C++中的一种特性,它和鸭子类型(duck-typing)是相对立的。如果动物类(Animal)没有定义speak
这个方法,它就会按照你的意图自动处理,不需要额外的努力。
>>> class Animal(object):
... pass
...
>>> class Dog(Animal):
... def speak(self):
... print "bark"
...
>>> animal = Animal()
>>> dog = Dog()
>>> animal.speak()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Animal' object has no attribute 'speak'
>>> dog.speak()
bark
C++和相关语言要求你创建抽象基类,因为抽象基类实际上是一个接口的描述。而Python则不需要编译器强制要求你声明接口,因为它认为用代码来记录一个合同是不必要的,这种合同可以通过其他方式来更好地执行。
其实,Python是有抽象类和抽象方法的:
>>> import abc
>>>
>>> class IFoo(object):
... __metaclass__ = abc.ABCMeta
...
... @abc.abstractmethod
... def foo(self):
... pass
...
>>> foo = IFoo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Cant instantiate abstract class IFoo with abstract methods foo
>>> class FooDerived(IFoo):
... pass
...
>>> foo = FooDerived()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Cant instantiate abstract class FooDerived with abstract methods foo
>>> class FooImplements(FooDerived):
... def foo(self):
... print "foo'ed"
...
>>> foo = FooImplements()
>>> foo.foo()
foo'ed
>>>
不过,关于“这算不算Python风格”的问题就有点难说了。如果你是想提供一个抽象基类,以便后面检查某些值是否继承自它,那这样做就不太符合Python的风格,尽管你可以让任意类型成为你基类的抽象子类。另一方面,提供一个抽象基类,让它根据具体子类的实现来实现一些功能,这样做是完全可以的。例如,collections.Sequence
和collections.Mapping
就是为类似列表和字典的类提供这样的功能;子类可以提供__getitem__
方法,并且可以免费获得__contains__
等其他功能。
另外,你绝对不应该使用assert()
来做其他事情,除了记录代码的预期。如果确实有可能导致assert失败,那就不应该使用assert。优化过的Python(python -O script.py
)是不会检查assert的。
补充说明:
如果你在检查一个值的类型:
def foo(bar):
if not isinstance(bar, AbstractBaz):
raise ValueError, ("bar must be an instance of AbstractBaz, "
"got %s" % type(bar))
如果因为某种原因你不能使用@abstractmethod
,但仍然想达到类似的效果,你应该抛出NotImplementedError
。你可能想这样做,因为你确实希望有这个类的实例,其中一些可能不需要实现可选的功能。你仍然需要考虑到这个函数可能是通过super()
调用的。大致上,这可能看起来像这样。
class Foo(object):
def bar(self, baz):
if self.bar.im_func == Foo.bar.im_func:
raise NotImplementedError, "Subclasses must implement bar"