如何在python中实现接口?


182
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!'

这是一个好主意吗??请在您的答案中举例说明。


在您的情况下使用接口的目的是什么?
Bandi-T 2010年

23
坦白说根本没有目的!我只想了解在python中需要接口时该怎么做?
Pratik Deoghare,2010年

18
raise NotImplementedError就是它show的主体- Exception当Python定义一个完全特定的内置对象时,提出一个完全通用的对象是没有意义的!-)
Alex Martelli 2010年

2
不应在IInterface中初始化 show()调用(或引发异常本身),以便无法实例化抽象接口吗?
Katastic Voyage

1
我可以看到一些用处……假设您有一个要确保具有特定签名的对象。使用鸭子类型,您不能保证该对象将具有您期望的签名。有时,在动态类型的属性上强制进行某些键入可能会很有用。
由Design Prime设计

Answers:


150

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

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

也就是说,接口还有多种用途。其中一些被Python 2.6中引入的Pythons抽象基类覆盖。如果要创建无法实例化但提供特定接口或实现的一部分的基类,则它们很有用。

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


11
您能详细说明一下吗?1.如何实现这样的接口?2.如何将其用于查找组件?
大地测量学'18

42
“在Python中,接口不是必需的。接口除外。”
Baptiste Candellier

6
接口通常用于在传递对象时具有可预测的结果/强制成员正确。如果python支持此选项,那就太好了。它还将使开发工具具有更好的智能感知能力
Sonic Soul

1
一个例子将大大改善这个答案。
鲍勃,

3
“这是因为Python具有适当的多重继承”,谁说接口用于多重继承?
adnanmuttaleb

76

将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()

11
Yugghh,需要一些模块来作为语言本身的一部分,或者根本不使用IMO。
Mike de Klerk,

你是说:if not server.version() == '1.0': raise ...?我真的不明白这句话。欢迎解释。
Skandix

1
@MikedeKlerk我完全同意。就像Python对键入的回答;我不必导入一个模块就可以声明我想要一个类型为类型。对此的响应通常是“动态键入Python”,但这不是借口。Java + Groovy解决了这个问题。Java代表静态,Groovy代表动态。
ubiquibacon,

6
@ MikedeKlerk,abc模块确实是python内置的。设置其中一些模式还需要做更多的工作,因为在Python中,由于被认为是“更Pythonic”的替代模式,在很大程度上不需要它们。对于绝大多数开发人员来说,就像您所说的,“根本不使用”。但是,认识到确实有一些非常特殊的情况确实需要这些接口功能,Python创建者提供了易于使用的API来使之成为可能。
David Culbreth

39

介面支援Python 2.7和Python 3.4+。

安装界面,您必须

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

7
恕我直言,此库的主要优点是它为您带来了早期失败:如果您的类未正确实现指定的接口,则在读入该类后会立即获得异常-您甚至不必使用它。使用Python自己的抽象基类,您在首次实例化类时会遇到异常,这可能要晚得多。
汉斯(Hans)

不必要,ABC提供了类似的内置功能。
丹尼尔

@DanielCasares ABC是否提供了实际的接口,或者您是说没有状态或实现的抽象类是ABC提供的解决方案?
asaf92

36

在现代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()

(受此PyMOTW-3帖子启发。)


问题:模块名称“ ABC”代表什么?
塞巴斯蒂安·尼尔森

“ ABC”代表“抽象基类”,请参阅官方文档
mrts

31

有用于Python的第三方接口实现(最受欢迎的是Zope's,也用于Twisted中),但是更常见的是,Python编码人员更喜欢使用称为“抽象基类”(ABC)的更丰富的概念,该概念将接口与那里也有一些实施方面的可能性。在2.6和更高版本的Python 中,特别好地支持ABC,请参阅PEP,但即使在早期版本的Python中,它们也通常被视为“前进的道路”-只需定义一个类,某些类的方法将引发,NotImplementedError这样子类将成为注意,他们最好重写这些方法!-)


3
Python的接口有第三方实现,这是什么意思?您能解释一下ABC吗?
Pratik Deoghare,2010年

2
好吧,我会对美国广播公司的“富裕”表示质疑。;)zope.interface可以做的事情是ABC不能做到的,反之亦然。但除此之外,您还是一如既往的正确。+1
Lennart Regebro

1
@Alfred:这意味着zope.interface之类的模块未包含在标准库中,但可以从pypi获得。
Lennart Regebro

我仍然很难理解ABC的概念。有人可能会根据ABC 重写twistedmatrix.com/documents/current/core/howto/components.html(IMHO,对接口概念的出色解释)。有道理吗?
mcepl '16

21

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

class IInterface:
    def show(self): raise NotImplementedError

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

2
我应该如何__init__(self)处理构造函数?
Pratik Deoghare,2010年

1
由你决定。由于没有针对从抽象类构造对象的编译时检查,因此在编码/编译期间不会获得任何保护。将继承一个构造函数,因此创建对象,只是将其为“空”。由您决定是通过允许这种情况发生并在以后捕获故障,还是在此后通过实施类似的构造函数引发异常来明确停止程序,从而使状况更好。
Bandi-T

1
这就是为什么abc.ABC比引发更好的原因NotImplementedError- abc.ABC没有实现所有抽象方法的子类的实例化会尽早失败,因此可以防止错误。请参阅下面的错误答案。
mrts '18

8

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

例如,如果您具有观察者并且是可观察的,那么可观察的对象就是订阅支持IObserver接口的对象,而该对象又会起作用notify。这是在编译时检查的。

在Python中,没有这样的事情,compile time并且在运行时执行方法查找。而且,可以使用__getattr __()或__getattribute __()魔术方法覆盖查找。换句话说,您可以作为观察者传递可以在访问notify属性时返回可调用对象的任何对象。

这使我得出结论,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.