Python 中的抽象方法

293

我在使用Python的继承方面遇到了麻烦。虽然在Java中这个概念对我来说似乎非常简单,但直到现在我仍然无法理解Python中的继承,这让我感到惊讶。

我有一个如下的原型:

class Shape():
   def __init__(self, shape_name):
       self.shape = shape_name

class Rectangle(Shape):
   def __init__(self, name):
       self.shape = name
在上面的代码中,我如何创建一个需要在所有子类中实现的抽象方法?

6
顺便提一下,这在Python中真的是事后之举。抽象接口和类等概念最近才被引入。由于Python不是编译型语言,因此应该在文档中清楚地表达您的意图,而不是使用抽象类。 - Falmarri
这也是一个非常适合单元测试的优秀应用程序。由于Python不是编译型语言,因此在进行单元测试之前,我不会开始信任代码。 - Technophile
6个回答

357

在引入abc之前,你经常会看到这个。

class Base(object):
    def go(self):
        raise NotImplementedError("Please Implement this method")


class Specialized(Base):
    def go(self):
        print "Consider me implemented"

74
在我看来,这种方法比被接受的答案更符合Python的风格,因为它遵循了Python格言“我们都是成年人”——如果子类没有实现这种类型的抽象方法,它将不得不处理后果。 - Emmett Butler
2
@EmmettJ.Butler 但是 abc 应该在所有实现之后才能被执行。我是对的吗? - Nabin
2
@Spiderman 不是的,abc 不仅仅是用于简单的基类,当你需要 isinstance 检查时,就可以使用 abc,类似于 isinstance(x, number) 或 isinstance(x, collections.abc.Sequence)。 - Stan Prokop
29
但它并不提供即时反馈。它只在方法被使用后提供反馈,这使得这个机制作为一个抽象类完全无用。 - Błażej Michalik

324

添加这个会影响其他部分吗?我的类代码的其他部分需要改变吗? - user506710
请记住,这只是最近才引入的。早些时候,Python使用了“鸭子类型”。 - pyfunc
2
@user506710:你的代码中唯一可能需要更改的部分是如果你已经在使用元类。在这种情况下,只需让你的元类从abc.ABCMeta派生而来,而不是从type派生,那么你就可以了。如果你不知道什么是元类,不用担心。 :-) - kindall
1
这个链接的教程比Python abc文档(http://docs.python.org/2/library/abc.html)清晰得多,但它们说的是同样的事情。 - Jacob Marble
6
这个语法在Python 3中无法运行,请参考此问题/答案链接:https://dev59.com/s2Ml5IYBdhLWcg3wTlcl#18513858。 - Andy Hayden
2
在Python 3中,您应该使用此答案。https://dev59.com/0WYr5IYBdhLWcg3wdqGw#13646263 - Zhou Hongbo

48

请查看abc模块。基本上,您需要在类上定义__metaclass__ = abc.ABCMeta,然后使用@abc.abstractmethod装饰每个抽象方法。从此类派生的类将无法实例化,除非所有抽象方法都已被覆盖。

如果您的类已经使用元类,请从ABCMeta而不是type派生它,这样您就可以继续使用自己的元类。

一种便宜的替代方案(也是在引入abc模块之前最佳做法)是让所有抽象方法都引发异常(例如NotImplementedError),以便从中派生的类必须重写该方法才能有用。

但是,abc的解决方案更好,因为它完全防止了这样的类被实例化(即,“更快地失败”),而且还因为您可以提供每个方法的默认或基本实现,可以在派生类中使用super()函数调用。


你可以为每个方法提供一个默认或基本实现,这些实现可以在派生类中使用super()函数调用。但是如果没有使用abc模块,则无法使用super()函数。 - Andrea Bergonzo
它的意思是你必须使用super()来调用抽象基类中的方法实现,而不是需要使用abc才能使用super()super()适用于任何新式类。 - kindall
我只是有点困惑,因为它说“abc对于动机1和动机2更好”,但动机2并不特指abc :) - Andrea Bergonzo
动机2是针对abc的,因为“在基类方法中引发异常”的技术与让它们执行有用操作不兼容。 - kindall

12

抽象基类是一种深奥的魔法。我时不时地使用它们实现一些东西,为自己的聪明才智感到惊讶,但很快我就会因为自己的聪明而感到困惑(虽然这可能只是我的个人限制)。

另一种做法(如果你问我,应该在Python标准库中)是使用装饰器。

def abstractmethod(method):
    """
    An @abstractmethod member fn decorator.
    (put this in some library somewhere for reuse).

    """
    def default_abstract_method(*args, **kwargs):
        raise NotImplementedError('call to abstract method ' 
                                  + repr(method))
    default_abstract_method.__name__ = method.__name__    
    return default_abstract_method


class Shape(object):

    def __init__(self, shape_name):
       self.shape = shape_name

    @abstractmethod
    def foo(self):
        print "bar"
        return

class Rectangle(Shape):
    # note you don't need to do the constructor twice either
    pass  

r = Rectangle("x")
r.foo()

我没有编写这个装饰器。只是想到有人会写。你可以在这里找到它:http://code.activestate.com/recipes/577666-abstract-method-decorator/,不错的jimmy2times。请注意该页面底部有关于装饰器类型安全性的讨论。(如果有人愿意),可以使用inspect模块来修复这个问题。


3
这与abc.abstractmethod有何不同? - Beau B.
并没有太大的区别。我想在底层它们并不太不同。实用上,"使用abc.abstractmethod装饰器需要类的元类是ABCMeta或派生自它。" - demented hedgehog

7
您可以使用six和abc来高效构建适用于Python2和Python3的类,如下所示:
import six
import abc

@six.add_metaclass(abc.ABCMeta)
class MyClass(object):
    """
    documentation
    """

    @abc.abstractmethod
    def initialize(self, para=None):
        """
        documentation
        """
        raise NotImplementedError

这是一份非常棒的IT技术文档。


7
您无法使用语言基元实现此功能。正如已经指出的那样,abc包在Python 2.6及更高版本中提供了此功能,但是在Python 2.5及更早版本中没有任何选项。 abc包不是Python的新功能;相反,它通过添加显式的“这个类是否声明了它做了什么”检查并手动实现一致性检查来添加功能,在初始化期间如果假设这些声明是错误的,则会导致错误。
Python是一种强烈的动态类型语言。它不指定语言基元,以允许您防止程序编译,因为对象与类型要求不匹配;这只能在运行时发现。如果要求子类实现方法,请记录该方法,然后在盲目地调用该方法时希望它存在。
如果存在,那太棒了,它就可以正常工作;这称为鸭子类型,您的对象已经像鸭子一样嘎嘎叫,以满足接口。即使self是您调用此类方法的对象,也可以正常工作,以满足由于需要具体实现功能(通用函数)的基本方法而需要进行强制覆盖的目的,因为self是一种约定,并不是任何特殊的东西。
例外情况在于__init__,因为在调用初始化程序时,派生类型的初始化程序尚未运行,因此它还没有机会将自己的方法钉到对象上。
如果未实现该方法,则会收到AttributeError(如果根本不存在)或TypeError(如果存在这样的名称但不是函数或其签名不匹配)。由您决定如何处理它-将其称为程序员错误并让它崩溃程序(对于Python开发人员来说,这种错误应该很明显-未满足鸭子接口),或者在发现对象不支持您希望它支持的内容时捕获和处理这些异常。实际上,在许多情况下,捕获AttributeError和TypeError都很重要。

那么其他答案中提到的abc模块呢? - user506710
7
就这个问题而言,Python是强类型语言,只是动态类型的。 - Gintautas Miliauskas
1
确认。这是我第一次听说abc模块。尽管如此,我仍然保留这个答案,因为它适用于Python 2.6之前的版本(CentOS附带的是2.4),而且鸭子类型在这里仍然有效。 - Adam Norberg
1
是的,关于鸭子类型的观点很好。通常,在Python编程中,您只需调用您期望的方法而不是强制实施真正的类型约束,这样会更加愉快。abc是指定您想要的鸭子类型的一种不错的选择。 - kindall
Python确实是强类型语言。他指的是静态类型和动态类型。 - Corey Goldberg
我不认为鸭子类型是原因。PHP也是动态类型的,而且PHP5以正常方式支持抽象基类。这是一个(罕见的)例子,PHP而不是Python拥有简单而优雅的解决方案。 - Canuck

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