【问题标题】:How do I implement interfaces in python?如何在python中实现接口?
【发布时间】:2011-01-08 14:42:57
【问题描述】:
public interface IInterface
{
    void show();
}

 public class MyClass : IInterface
{

    #region IInterface Members

    public void show()
    {
        Console.WriteLine("Hello World!");
    }

    #endregion
}

如何实现此 C# 代码的 Python 等效项?

class IInterface(object):
    def __init__(self):
        pass

    def show(self):
        raise Exception("NotImplementedException")


class MyClass(IInterface):
   def __init__(self):
       IInterface.__init__(self)

   def show(self):
       print 'Hello World!'

这是个好主意吗?请在你的回答中举例说明。

【问题讨论】:

  • 在您的案例中使用接口的目的是什么?
  • 坦白说根本没有目的!我只是想学习在python中需要接口的时候怎么办?
  • raise NotImplementedError 应该是 show 的主体——当 Python 定义了一个完全特定的内置函数时,提出一个完全通用的 Exception 是没有意义的!-)
  • 不应该 init 在 IInterface 中调用 show() (或引发异常本身),这样您就无法实例化抽象接口?
  • 我可以看到它的一些用途......假设您有一个要确保具有特定签名的对象。使用鸭子类型,您不能保证对象将具有您期望的签名。有时对动态类型的属性强制执行一些类型可能很有用。

标签: python oop


【解决方案1】:

Python 接口有第三方实现(最流行的是Zope's,也用于Twisted),但更常见的 Python 编码人员更喜欢使用更丰富的概念,即“抽象基类”(ABC ),它将接口与那里也有一些实现方面的可能性结合在一起。 ABC 在 Python 2.6 及更高版本中得到特别好的支持,请参阅 the PEP,但即使在早期版本的 Python 中,它们通常也被视为“可行的方法”——只需定义一个类,其中一些方法引发 NotImplementedError 所以子类会注意到它们最好覆盖这些方法!-)

【讨论】:

  • Python接口有第三方实现什么意思?你能解释一下ABC吗?
  • 好吧,我会反对 ABC 变得“更富有”。 ;) 有些事情 zope.interface 可以做 ABC 做不到的事情,反之亦然。但除此之外,你和往常一样是对的。 +1
  • @Alfred:这意味着标准库中不包含 zope.interface 之类的模块,但可以从 pypi 获得。
  • 我仍然很难理解 ABC 的概念。有人可以根据 ABC 重写twistedmatrix.com/documents/current/core/howto/components.html(恕我直言,接口概念的一个很好的解释)。有意义吗?
【解决方案2】:

这样的事情(可能无法正常工作,因为我没有 Python):

class IInterface:
    def show(self): raise NotImplementedError

class MyClass(IInterface):
    def show(self): print "Hello World!"

【讨论】:

  • 构造函数__init__(self)怎么办?
  • 由你决定。由于没有针对从抽象类构造对象的编译时检查,因此您在编码/编译期间不会获得任何保护。将继承一个构造函数,因此对象被创建,只是“空”。由您决定是允许这种情况发生并稍后捕获故障,还是通过实现类似的构造函数抛出异常来显式地立即停止程序。
  • 这就是为什么abc.ABC 比引发NotImplementedError 好得多的原因- 不实现所有抽象方法的abc.ABC 子类的实例化会提前失败,因此您可以避免错误。请参阅下面的答案以了解错误的外观。
【解决方案3】:

我的理解是,在 Python 等动态语言中,接口并不是必需的。在 Java(或带有抽象基类的 C++)中,接口是确保例如您传递了正确的参数,能够执行一组任务。

例如如果你有观察者和可观察者,可观察者有兴趣订阅支持 IObserver 接口的对象,而后者又具有notify 动作。这是在编译时检查的。

在 Python 中,没有 compile time 这样的东西,方法查找是在运行时执行的。此外,可以使用 __getattr__() 或 __getattribute__() 魔术方法覆盖查找。换句话说,您可以作为观察者传递任何可以在访问notify 属性时返回可调用的对象。

这使我得出结论,Python 中的接口确实存在 - 只是它们的执行被推迟到它们实际使用的那一刻

【讨论】:

    【解决方案4】:

    正如其他人在这里提到的:

    Python 中不需要接口。这是因为 Python 有适当的多重继承,还有鸭式,这意味着在 Java 中你必须有接口的地方,你不必在 Python 中有它们。

    也就是说,接口仍有多种用途。其中一些包含在 Python 2.6 中引入的 Python 抽象基类中。如果您想创建无法实例化但提供特定接口或实现的一部分的基类,它们很有用。

    另一种用法是,如果你想以某种方式指定一个对象实现一个特定的接口,你也可以通过从它们继承来使用 ABC。另一种方法是 zope.interface,它是 Zope 组件架构的一部分,一个非常酷的组件框架。在这里,您不是从接口子类化,而是将类(甚至实例)标记为实现接口。这也可用于从组件注册表中查找组件。超酷!

    【讨论】:

    • 您能详细说明一下吗? 1.如何实现这样的接口? 2、如何使用它来查找组件?
    • “接口在 Python 中不是必需的。除非是。”
    • 接口主要用于产生可预测的结果/在传递对象时强制成员的正确性。如果 python 支持这个选项,那就太好了。它还可以让开发工具拥有更好的智能感知
    • “这是因为Python有正确的多重继承”,谁说接口是多重继承的?
    • 人们在多继承、内存占用方面谈论接口:如果我有一个抽象文件系统的模块会怎样。后端可以是本地文件系统、内存或众多云存储系统之一。从这样的模块实例化一个类并且知道该对象将具有某些方法,而不管后端是否实现,这将是很好的。 IOW,接口。与多重继承无关,与内存无关。
    【解决方案5】:

    对抽象基类使用 abc 模块似乎可以解决问题。

    from abc import ABCMeta, abstractmethod
    
    class IInterface:
        __metaclass__ = ABCMeta
    
        @classmethod
        def version(self): return "1.0"
        @abstractmethod
        def show(self): raise NotImplementedError
    
    class MyServer(IInterface):
        def show(self):
            print 'Hello, World 2!'
    
    class MyBadServer(object):
        def show(self):
            print 'Damn you, world!'
    
    
    class MyClient(object):
    
        def __init__(self, server):
            if not isinstance(server, IInterface): raise Exception('Bad interface')
            if not IInterface.version() == '1.0': raise Exception('Bad revision')
    
            self._server = server
    
    
        def client_show(self):
            self._server.show()
    
    
    # This call will fail with an exception
    try:
        x = MyClient(MyBadServer)
    except Exception as exc:
        print 'Failed as it should!'
    
    # This will pass with glory
    MyClient(MyServer()).client_show()
    

    【讨论】:

    • Yugghh,需要一些模块来作为语言本身的一部分,或者根本不使用,IMO。
    • 你的意思是:if not server.version() == '1.0': raise ...?我真的不明白这条线。欢迎提供解释。
    • @MikedeKlerk 我完全同意。就像 Python 对打字的回答一样;我不应该导入一个模块来声明我希望一个类型成为一个类型。对此的回应通常是“Python 是动态类型的”,但这不是借口。 Java + Groovy 解决了这个问题。 Java 用于静态的东西,Groovy 用于动态的东西。
    • @MikedeKlerk,abc 模块确实是 python 内置的。设置其中一些模式需要做更多的工作,因为它们在 Python 中基本上不需要,因为它们被认为是“更 Pythonic”的替代模式。对于绝大多数开发者来说,就像你说的,“根本没用过”。然而,承认有一些非常特殊的情况确实需要这些接口功能,Python 的创建者提供了一个易于使用的 API 来实现这一点。
    【解决方案6】:

    在现代 Python 3 中使用抽象基类实现接口要简单得多,它们的目的是作为插件扩展的接口契约。

    创建接口/抽象基类:

    from abc import ABC, abstractmethod
    
    class AccountingSystem(ABC):
    
        @abstractmethod
        def create_purchase_invoice(self, purchase):
            pass
    
        @abstractmethod
        def create_sale_invoice(self, sale):
            log.debug('Creating sale invoice', sale)
    

    创建一个普通的子类并覆盖所有抽象方法:

    class GizmoAccountingSystem(AccountingSystem):
    
        def create_purchase_invoice(self, purchase):
            submit_to_gizmo_purchase_service(purchase)
    
        def create_sale_invoice(self, sale):
            super().create_sale_invoice(sale)
            submit_to_gizmo_sale_service(sale)
    

    您可以选择在抽象方法中使用公共实现,如create_sale_invoice(),在上面的子类中显式调用super()

    不实现所有抽象方法的子类的实例化失败:

    class IncompleteAccountingSystem(AccountingSystem):
        pass
    
    >>> accounting = IncompleteAccountingSystem()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: Can't instantiate abstract class IncompleteAccountingSystem with abstract methods
    create_purchase_invoice, create_sale_invoice
    

    你还可以通过@abstractmethod组合对应的注解来拥有抽象属性、静态和类方法。

    抽象基类非常适合实现基于插件的系统。类的所有导入子类都可以通过__subclasses__() 访问,因此如果您使用importlib.import_module() 从插件目录加载所有类,并且如果它们是基类的子类,您可以通过__subclasses__() 直接访问它们,您可以确定接口契约在实例化期间对所有这些都强制执行。

    这是上面 AccountingSystem 示例的插件加载实现:

    ...
    from importlib import import_module
    
    class AccountingSystem(ABC):
    
        ...
        _instance = None
    
        @classmethod
        def instance(cls):
            if not cls._instance:
                module_name = settings.ACCOUNTING_SYSTEM_MODULE_NAME
                import_module(module_name)
                subclasses = cls.__subclasses__()
                if len(subclasses) > 1:
                    raise InvalidAccountingSystemError('More than one '
                            f'accounting module: {subclasses}')
                if not subclasses or module_name not in str(subclasses[0]):
                    raise InvalidAccountingSystemError('Accounting module '
                            f'{module_name} does not exist or does not '
                            'subclass AccountingSystem')
                cls._instance = subclasses[0]()
            return cls._instance
    

    然后就可以通过AccountingSystem类访问会计系统插件对象了:

    >>> accountingsystem = AccountingSystem.instance()
    

    (灵感来自this PyMOTW-3 post。)

    【讨论】:

    • 问题:模块名称“ABC”代表什么?
    • “ABC”代表“抽象基类”,见official docs
    【解决方案7】:

    interface 支持 Python 2.7 和 Python 3.4+。

    install接口你必须

    pip install python-interface
    

    示例代码:

    from interface import implements, Interface
    
    class MyInterface(Interface):
    
        def method1(self, x):
            pass
    
        def method2(self, x, y):
            pass
    
    
    class MyClass(implements(MyInterface)):
    
        def method1(self, x):
            return x * 2
    
        def method2(self, x, y):
            return x + y
    

    【讨论】:

    • 这个库的一个主要优点,恕我直言,它给你的早期故障是:如果你的类没有正确实现指定的接口,一旦读入类,你就会得到一个异常 - 你甚至不必使用它。使用 Python 自己的抽象基类,当您第一次实例化您的类时会出现异常,这可能要晚得多。
    • 没必要,ABC 提供了类似的内置功能。
    • @DanielCasares ABC 是否提供实际接口,或者您的意思是没有状态或实现的抽象类是 ABC 提供的解决方案?
    【解决方案8】:

    我邀请您以Structural subtyping (static duck typing)(PEP 544) 的形式探索 Python 3.8 为主题提供的内容

    查看简短描述https://docs.python.org/3/library/typing.html#typing.Protocol

    这里的简单例子是这样的:

    from typing import Protocol
    
    class MyShowProto(Protocol):
        def show(self):
            ...
    
    
    class MyClass:
        def show(self):
            print('Hello World!')
    
    
    class MyOtherClass:
        pass
    
    
    def foo(o: MyShowProto):
        return o.show()
    
    foo(MyClass())  # ok
    foo(MyOtherClass())  # fails
    

    foo(MyOtherClass()) 将无法通过静态类型检查:

    $ mypy proto-experiment.py 
    proto-experiment.py:21: error: Argument 1 to "foo" has incompatible type "MyOtherClass"; expected "MyShowProto"
    Found 1 error in 1 file (checked 1 source file)
    

    另外,你可以明确指定基类,例如:

    class MyOtherClass(MyShowProto):
    

    但请注意,这使得基类的方法实际上在子类上可用,因此静态检查器不会报告MyOtherClass 上缺少方法定义。 所以在这种情况下,为了获得有用的类型检查,我们想要显式实现的所有方法都应该用@abstractmethod装饰:

    from typing import Protocol
    from abc import abstractmethod
    
    class MyShowProto(Protocol):
        @abstractmethod
        def show(self): raise NotImplementedError
    
    
    class MyOtherClass(MyShowProto):
        pass
    
    
    MyOtherClass()  # error in type checker
    

    【讨论】:

    • 我们如何装饰静态方法以获得相同的效果?请注意,abstractstaticmethod 已弃用,staticmethod 不会引发类型错误
    • 感谢您展示新功能。
    猜你喜欢
    • 2023-03-10
    • 2020-08-30
    • 2020-04-30
    • 2010-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多