如何在 python 中实现接口?

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

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

420929 次浏览

Python有接口的第三方实现(最流行的是Zope的,也在扭曲的中使用),但更常见的Python编码器更喜欢使用更丰富的概念,称为“抽象基类”(ABC),它将接口与一些实现方面的可能性结合在一起。abc在Python 2.6及以后版本中得到了很好的支持,请参阅PEP,但即使在Python的早期版本中,它们通常也被视为“可行的方法”——只需定义一个类,其中一些方法会引发NotImplementedError,这样子类就会注意到他们最好重写这些方法!-)

类似这样的东西(可能不起作用,因为我周围没有Python):

class IInterface:
def show(self): raise NotImplementedError


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

我的理解是,在Python这样的动态语言中,接口并不是那么必要。在Java(或具有抽象基类的c++)中,接口是确保传递正确参数,能够执行一组任务的手段。

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

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

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

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

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

也就是说,接口仍然有多种用途。其中一些由Python 2.6中引入的Python抽象基类覆盖。如果你想让基类不能被实例化,但是提供了一个特定的接口或者实现的一部分,它们是很有用的。

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

对抽象基类使用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()

在现代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帖子。)

接口支持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

我邀请你探索Python 3.8以Structural subtyping (static duck typing) (PEP 544)的形式为这个主题提供了什么

参见简短描述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