如何在Python中实现接口?

398 投票
8 回答
510754 浏览
提问于 2025-04-15 18:22
public interface IInterface
{
    void show();
}

 public class MyClass : IInterface
{

    #region IInterface Members

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

    #endregion
}

我该如何用Python实现这段C#代码呢?

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

这样做是个好主意吗?请在你的回答中给出一些例子。

8 个回答

118

使用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()
184

在现代的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的文章。)

254

正如这里其他人提到的:

在Python中,接口并不是必须的。这是因为Python支持多重继承,还有一种叫做鸭子类型的特性。这意味着在Java中你必须用到的接口,在Python中并不需要。

不过,接口还是有一些用处的。其中一些用法可以通过Python的抽象基类(Abstract Base Classes,简称ABC)来实现,这个特性是在Python 2.6中引入的。如果你想创建一些不能被实例化的基类,但又想提供特定的接口或实现的一部分,ABC就很有用。

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

撰写回答