Python是否支持纯虚函数,以及是否值得使用?

22

我可能来自不同的思维方式,主要是C++程序员。这个问题涉及Python中的面向对象编程,更具体地说是纯虚方法。因此,从这个问题中我改编的代码,我正在查看这个基本示例。

class Animal():
    def speak(self):
        print("...")

class Cat(Animal):
    def speak(self):
        print("meow")

class Dog(Animal):
    def speak(self):
        print("woof")

my_pets = [Dog(), Cat(), Dog()]

for _pet in my_pets:
     _pet.speak()

所以你可以看到它调用不同的派生类的speak函数。现在我的问题是,鸭子类型很好,我认为我已经掌握了它。然而,在Python中追求更严格的面向对象编程是否有错?所以我看了看抽象基类,特别是abstractmethod。对我来说,这似乎只是允许我使用super调用基类方法。在Python中有没有办法/原因使speak()变成纯函数,这样实现一个没有speak方法的派生动物会抛出错误?

我这样追求的理由是,在编写模块和框架时,如果你希望其他人进行子类化,这将为他们自我记录需要实现该函数的事实。一个可能非常糟糕的想法是像这样使基类的 "纯" 函数抛出异常。问题是这个错误是在运行时发现的!

class VirtualException(BaseException):
    def __init__(self, _type, _func):
        BaseException(self)

class Animal():
    def speak(self):
        raise VirtualException()

class Cat(Animal):
    def speak(self):
        print("meow")

class Dog(Animal):
    def speak(self):
        print("woof")

class Wildebeest(Animal):
    def function2(self):
        print("What!")

my_pets = [Dog(), Cat(), Dog(), Wildebeest()]

for _pet in my_pets:
    _pet.speak()

1
据我所见,您所描述的正是abstractmethod的作用。您能否澄清一下您需要什么,而abstractmethod无法提供呢?对于任何东西,您都不会得到“编译时”解决方案,因为Python在C++意义上并没有真正的编译时。但是,abstractmethod允许您在类定义时(在实例化之前)就获得错误提示。 - BrenBarn
@BrenBarn 对不起,我的陈述有些笨拙。我知道我不会在编译时出现错误。只是很好知道当抽象基类对象或无效的派生类对象被实例化时,我可以得到一个错误。我的错误是使用了Python 3解释器,因为我没有按照正确的Python3方式定义类,所以abstractmethod似乎什么也没做。请参见已接受的答案。 - benzeno
相关:https://dev59.com/pG445IYBdhLWcg3wwcyg#38717503 - Ciro Santilli OurBigBook.com
2个回答

38

抽象基类已经实现了你想要的功能。abstractmethod与让你使用super调用方法无关;你可以随意使用它。相反,任何使用abstractmethod装饰的方法都必须被子类重写,以使子类可实例化:

Python 3:

>>> class Foo(metaclass=abc.ABCMeta):
...     @abc.abstractmethod
...     def foo(self):
...         pass
...
>>> class Bar(Foo):
...     pass
...
>>> class Baz(Bar):
...     def foo(self):
...         return super(Baz, self).foo()
...
>>> Foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Foo with abstract methods foo
>>> Bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Bar with abstract methods foo
>>> Baz()
<__main__.Baz object at 0x00000210D702E2B0>

Python 2:

>>> class Foo(object):
...     __metaclass__ = abc.ABCMeta
...     @abc.abstractmethod
...     def foo(self): pass
...
>>> class Bar(Foo): pass
...
>>> class Baz(Bar):
...     def foo(self): return super(Baz, self).foo()
...
>>> Foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Foo with abstract methods foo
>>> Bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Bar with abstract methods foo
>>> Baz()
<__main__.Baz object at 0x0000000001EC10B8>

好的,这就是我一直在寻找的。我的错误在于认为Python2和3并没有非常大的区别。我可能误标记了这个问题,但是Python 3只是让它运行而不会引发任何异常,除非你调用class Foo(metaclass=ABCMeta):。然后我误解了abstractmethod的作用,因为Python2文档中评论了super,我看不出它是否还有其他作用。谢谢。 - benzeno
如果有人能够将回溯行的格式设置为不以Python代码颜色显示,但仍在同一代码块中,我将不胜感激地学习如何做到这一点。 - Eli_B

10
问题在于这个错误是在运行时发现的!嗯,这是Python…大多数错误都会在运行时显示。据我所知,在Python中处理最常见的模式基本上就像你描述的那样:只需让基类的speak方法抛出异常即可。
class Animal():
    def speak(self):
        raise NotImplementedError('You need to define a speak method!')

7
+1 表示“大多数错误将在运行时显示”! - Crowman
好的... 抱歉那样不够流畅。我从未考虑过其他方面。我只是希望第二好的,所以当一个对象被实例化时而不是之后进行调用时说话。 - benzeno
1
@Crowman 这基本上是我对Python的主要不满之处。除此之外,它是一门很棒的语言。 - HelloGoodbye

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接