Python中抽象类和接口之间的区别


Answers:


618

有时您会看到以下内容:

class Abstract1( object ):
    """Some description that tells you it's abstract,
    often listing the methods you're expected to supply."""
    def aMethod( self ):
        raise NotImplementedError( "Should have implemented this" )

由于Python没有(也不需要)正式的Interface协定,因此不存在抽象和接口之间的Java风格区别。如果有人努力定义一个正式的接口,它也将是一个抽象类。唯一的区别在于文档字符串中所述的意图。

当您进行鸭类打字时,抽象和接口之间的区别是令人不解的事情。

Java使用接口是因为它没有多重继承。

由于Python具有多重继承,因此您可能还会看到类似这样的内容

class SomeAbstraction( object ):
    pass # lots of stuff - but missing something

class Mixin1( object ):
    def something( self ):
        pass # one implementation

class Mixin2( object ):
    def something( self ):
        pass # another

class Concrete1( SomeAbstraction, Mixin1 ):
    pass

class Concrete2( SomeAbstraction, Mixin2 ):
    pass

这使用一种带有混合类的抽象超类来创建不相交的具体子类。


5
S. Lott,您的意思是说,由于鸭子输入的原因,has-a(接口)和is-a(继承性)之间的区别并不大?
洛伦佐

3
当您进行鸭类输入时,摘要和接口之间的区别是令人不解的事情。我不知道“实质”是什么意思。从设计的角度来看,它是“真实的”-具有实质性。但是从语言角度来看,可能没有任何支持。您可以采用约定来区分Python中的抽象类和接口类定义。
S.Lott

26
@ L.DeLeo-您确定关于has-a与is-a的概念正确吗?我通常将区别视为has-a =成员变量与is-a =继承(父类接口)。用Java认为可比或列表;不管它们是接口还是抽象类,它们都是一个关系。
dimo414 2012年

43
NotImplementedError("Class %s doesn't implement aMethod()" % (self.__class__.__name__))是更翔实的错误消息:)
naught101

9
@Lorenzo a has-a关系与继承,duck类型,接口和抽象类无关(所有四个都称为is-a关系)。
2015年

196

Python中的抽象类和接口有什么区别?

对象的接口是该对象上的一组方法和属性。

在Python中,我们可以使用抽象基类来定义和执行接口。

使用抽象基类

例如,假设我们要使用collections模块中的抽象基类之一:

import collections
class MySet(collections.Set):
    pass

如果尝试使用它,则会得到一个,TypeError因为我们创建的类不支持集合的预期行为:

>>> MySet()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MySet with abstract methods
__contains__, __iter__, __len__

因此,我们必须执行在至少 __contains____iter____len__。让我们使用文档中的实现示例:

class ListBasedSet(collections.Set):
    """Alternate set implementation favoring space over speed
    and not requiring the set elements to be hashable. 
    """
    def __init__(self, iterable):
        self.elements = lst = []
        for value in iterable:
            if value not in lst:
                lst.append(value)
    def __iter__(self):
        return iter(self.elements)
    def __contains__(self, value):
        return value in self.elements
    def __len__(self):
        return len(self.elements)

s1 = ListBasedSet('abcdef')
s2 = ListBasedSet('defghi')
overlap = s1 & s2

实现:创建抽象基类

我们可以通过将元类设置为abc.ABCMetaabc.abstractmethod在相关方法上使用装饰器来创建自己的抽象基类。元类将被装饰的函数添加到__abstractmethods__属性中,从而防止实例化直到定义它们。

import abc

例如,“有效的”被定义为可以用词表达的东西。假设我们想在Python 2中定义一个有效的抽象基类:

class Effable(object):
    __metaclass__ = abc.ABCMeta
    @abc.abstractmethod
    def __str__(self):
        raise NotImplementedError('users must define __str__ to use this base class')

或在Python 3中,在元类声明中稍有变化:

class Effable(object, metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def __str__(self):
        raise NotImplementedError('users must define __str__ to use this base class')

现在,如果我们尝试在不实现接口的情况下创建有效对象:

class MyEffable(Effable): 
    pass

并尝试实例化它:

>>> MyEffable()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyEffable with abstract methods __str__

我们被告知我们尚未完成工作。

现在,如果我们通过提供预期的接口来遵守:

class MyEffable(Effable): 
    def __str__(self):
        return 'expressable!'

然后,我们可以使用从抽象类派生的类的具体版本:

>>> me = MyEffable()
>>> print(me)
expressable!

我们可以做其他事情,例如注册已经实现这些接口的虚拟子类,但是我认为这超出了这个问题的范围。但是,此处演示的其他方法必须使用abc模块来适应此方法。

结论

我们已经证明了抽象基类的创建为Python中的自定义对象定义了接口。


101

Python> = 2.6具有抽象基类

当诸如hasattr()之类的其他技术笨拙时,抽象基类(缩写为ABC)通过提供一种定义接口的方式来补充鸭式输入。Python随附了许多内置的ABC,用于数据结构(在collections模块中),数字(在numbers模块中)和流(在io模块中)。您可以使用abc模块创建自己的ABC。

还有一个Zope接口模块,该模块由zope外部的项目使用,例如扭曲。我不是很熟悉,但有一个wiki页面在这里可能会有帮助。

通常,您不需要抽象类或python中的接口的概念(已编辑-有关详细信息,请参见S.Lott的答案)。


2
在Python中使用ABC有什么好处?
CpILL

38

Python实际上没有任何一个概念。

它使用鸭子类型,从而消除了对接口的需求(至少对于计算机:-)。

Python <= 2.5:基类显然存在,但是没有明确的方法将方法标记为“纯虚拟”,因此该类并不是真正的抽象。

Python> = 2.6:确实存在抽象基类(http://docs.python.org/library/abc.html)。并允许您指定必须在子类中实现的方法。我不太喜欢语法,但是功能在那里。在大多数情况下,最好从“使用”客户端使用鸭子类型。


3
Python 3.0确实添加了真正的抽象基类。它们用于收藏模块以及其他地方。docs.python.org/3.0/library/abc.html
Lara

关于为什么鸭子输入消除了对接口的需求的参考将很有帮助。在我看来,鸭子类型并不是很明显,我理解为可以“戳”任何对象上的任何方法或属性,这意味着您不需要指定必需的行为(并让编译器提醒您)来实现它们),这就是我对抽象基类的理解。
Reb.Cabin

与其说是鸭子类型,不如说是对多重继承的支持,它消除了接口与例如Java绘制的抽象类之间的人为界限。
masi

35

用更基本的方式解释:接口有点像一个空的松饼锅。这是一个类文件,带有一组没有代码的方法定义。

抽象类是一回事,但并非所有功能都必须为空。有些可以有代码。并非严格意义上是空的。

为什么要区分:Python并没有太大的实际区别,但是在大型项目的计划级别上,谈论接口可能更常见,因为没有代码。尤其是在您与习惯该术语的Java程序员一起工作时。


+1是ABC可以自己实施的区别-似乎是一种
超乎寻常

17

通常,仅在使用单继承类模型的语言中使用接口。在这些单继承语言中,如果任何类可以使用特定方法或方法集,则通常使用接口。同样在这些单继承语言中,抽象类用于除了没有一个或多个方法之外还具有定义的类变量,或者用于利用单继承模型来限制可以使用一组方法的类的范围。

支持多重继承模型的语言倾向于仅使用类或抽象基类,而不使用接口。由于Python支持多重继承,因此它不使用接口,而您想使用基类或抽象基类。

http://docs.python.org/library/abc.html


2

抽象类是包含一个或多个抽象方法的类。除抽象方法外,抽象类还可以具有静态方法,类方法和实例方法。但是在接口的情况下,它将仅具有抽象方法,而没有其他方法。因此,继承抽象类不是强制性的,但是继承接口是强制性的。


1

为了完整起见,我们应该提到PEP3119 ,其中引入了ABC并与接口进行了比较,还有原始的塔林评论。

抽象类不是完美的接口:

  • 属于继承层次
  • 易变

但是,如果您考虑以自己的方式编写它:

def some_function(self):
     raise NotImplementedError()

interface = type(
    'your_interface', (object,),
    {'extra_func': some_function,
     '__slots__': ['extra_func', ...]
     ...
     '__instancecheck__': your_instance_checker,
     '__subclasscheck__': your_subclass_checker
     ...
    }
)

ok, rather as a class
or as a metaclass
and fighting with python to achieve the immutable object
and doing refactoring
...

您会很快意识到自己正在发明轮子以最终实现 abc.ABCMeta

abc.ABCMeta 被提议作为缺少接口功能的有用补充,并且在像python这样的语言中已经足够了。

当然,在编写版本3并添加新语法和不可变接口概念时,它可以得到更好的增强。

结论:

The abc.ABCMeta IS "pythonic" interface in python
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.