599

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

4

8 回答 8

650

您有时会看到以下内容:

class Abstract1:
    """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 没有(也不需要)正式的接口契约,所以不存在抽象和接口之间的 Java 风格区别。如果有人努力定义一个正式的接口,它也将是一个抽象类。唯一的区别在于文档字符串中声明的意图。

当你有鸭子打字时,抽象和接口之间的区别是一件令人毛骨悚然的事情。

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

因为Python有多重继承,你可能还会看到这样的东西

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

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

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

class Concrete1(SomeAbstraction, Mixin1):
    pass

class Concrete2(SomeAbstraction, Mixin2):
    pass

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

于 2008-12-16T17:59:23.253 回答
214

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.ABCMeta我们可以通过将元类设置为并在相关方法上使用abc.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')

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

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 中自定义对象的接口。

于 2015-07-15T19:15:20.010 回答
102

Python >= 2.6 有Abstract Base Classes

抽象基类(缩写为 ABCs)通过提供一种定义接口的方法来补充鸭子类型,而其他技术(如 hasattr())会很笨拙。Python 为数据结构(在 collections 模块中)、数字(在 numbers 模块中)和流(在 io 模块中)提供了许多内置的 ABC。您可以使用 abc 模块创建自己的 ABC。

还有Zope 接口模块,它被 zope 之外的项目使用,比如 twisted。我对它不是很熟悉,但是这里有一个 wiki 页面可能会有所帮助。

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

于 2008-12-16T17:51:35.343 回答
42

用更基本的方式来解释:界面有点像一个空的松饼盘。它是一个类文件,其中包含一组没有代码的方法定义。

抽象类是同样的东西,但并非所有函数都需要为空。有些可以有代码。它不是严格为空的。

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

于 2013-05-08T17:51:11.153 回答
41

Python 并没有这两个概念。

它使用鸭子打字,这消除了对接口的需求(至少对于计算机:-))

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

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

于 2008-12-16T17:38:22.687 回答
20

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

支持多重继承模型的语言倾向于只使用类或抽象基类而不是接口。由于 Python 支持多重继承,它不使用接口,您可能希望使用基类或抽象基类。

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

于 2008-12-16T18:19:55.660 回答
2

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

于 2019-07-23T06:40:44.877 回答
1

为了完整起见,我们应该提到 引入 ABC 并与接口进行比较的PEP3119 ,以及原始Talin 的评论。

抽象类不是完美的接口:

  • 属于继承层次
  • 是可变的

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

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
于 2020-04-05T18:17:00.063 回答