ChatGPT解决这个技术问题 Extra ChatGPT

Python中抽象类和接口的区别

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


N
Nico Schlömer

您有时会看到以下内容:

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

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


S. Lott,您的意思是因为鸭子类型,has-a(接口)和 is-a(继承)之间的区别并不重要吗?
当你有鸭子打字时,抽象和接口之间的区别是一件令人毛骨悚然的事情。我不知道“实质性”是什么意思。从设计的角度来看,它是“真实的”——它有实质内容。但从语言的角度来看,可能没有支持。您可以采用约定来区分 Python 中的抽象类和接口类定义。
@L.DeLeo - 你确定你的 has-a 与 is-a 的概念是正确的吗?我通常将区别视为 has-a = 成员变量与 is-a = 继承(父类或接口)。在 Java 中思考 Comparable 或 List;这些都是 is-a 关系,无论它们是接口还是抽象类。
NotImplementedError("Class %s doesn't implement aMethod()" % (self.__class__.__name__)) 是更多信息错误消息 :)
@Lorenzo has-a 关系与继承、鸭子类型、接口和抽象类没有任何关系(这四个都指 is-a 关系)。
R
Russia Must Remove Putin

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__。让我们使用 documentation 中的这个实现示例:

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


C
Chris

Python >= 2.6 有 Abstract Base Classes

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

还有 Zope Interface 模块,它被 zope 之外的项目使用,例如 twisted。我不太熟悉它,但有一个 wiki 页面 here 可能会有所帮助。

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


在 Python 中使用 ABC 可以获得什么?
S
SilentSteel

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

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

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


+1 区别于 ABC 可以拥有自己的实现 - 这似乎是一种非常酷的智取自己的方式
d
djvg

Python 并没有这两个概念。

它使用 duck typing,它消除了对接口的需求(至少对于计算机来说 :-))

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

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


Python 3.0 确实添加了真正的抽象基类。它们用于集合模块以及其他地方。 docs.python.org/3.0/library/abc.html
关于为什么鸭子类型消除了对接口的需求的参考会很有帮助。在我看来,鸭子类型(我理解为能够“戳”任何对象上的任何方法或属性的能力)似乎并不明显,这意味着您不需要指定所需的行为(并让编译器提醒您来实现它们),这就是我理解抽象基类的方式。
与其说是鸭子类型,不如说是对多重继承的支持消除了接口和抽象类之间的人为界限,例如Java 已经绘制。
L
Lara Dougan

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

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

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


A
Akash Ukarande

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


S
Sławomir Lenart

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

抽象类不是完美的接口:

属于继承层次

是可变的

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

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