用啥来替换python中的接口/协议

Posted

技术标签:

【中文标题】用啥来替换python中的接口/协议【英文标题】:What to use in replacement of an interface/protocol in python用什么来替换python中的接口/协议 【发布时间】:2015-05-15 09:01:54 【问题描述】:

我正在制作一个国际象棋游戏,并想制作一个标准的棋子接口/协议。 Python 语言中没有这些,那么我应该使用什么?我读了一些关于工厂的文章,但我不确定它们会如何提供帮助。提前致谢!

【问题讨论】:

Python 中的接口有什么意义,因为它不能强制你实现所有方法? 接口对于静态类型的语言很重要,并且您希望强制继承类实现接口。对于像 python 这样的动态语言,类或类的客户端可以查找函数。如果该类有一个,它可以执行它,无论它是否继承它。 @MalikBrahimi 是的,在 python 中它不能被强制执行,但接口也可以用来记录应该实现哪些方法。更像是君子协定。对于 ABC,这正是 python 所做的。 【参考方案1】:

Python 3.8 中的新功能:

接口和协议的一些好处是在开发过程中使用内置于 IDE 中的工具进行类型提示,以及在运行前检测错误的静态类型分析。这样,当您检查代码时,静态分析工具可以告诉您是否尝试访问未在对象上定义的任何成员,而不是仅在运行时发现。

typing.Protocol 类作为“结构子类型化”的机制添加到 Python 3.8。这背后的强大之处在于它可以用作隐式基类。也就是说,任何具有与Protocol 定义的成员匹配的成员的类都被认为是它的子类,以便进行静态类型分析。

PEP 544 中给出的基本示例展示了如何使用它。

from typing import Protocol

class SupportsClose(Protocol):
    def close(self) -> None:
        # ...

class Resource:
    # ...
    def close(self) -> None:
        self.file.close()
        self.lock.release()

def close_all(things: Iterable[SupportsClose]) -> None:
    for thing in things:
        thing.close()

file = open('foo.txt')
resource = Resource()
close_all([file, resource])  # OK!
close_all([1])     # Error: 'int' has no 'close' method

注意:typing-extensions package 向后移植 typing.Protocol 用于 Python 3.5+。

【讨论】:

嗯...这里遗漏的是直接告诉读者您的代码,Resource 实现了名为 SupportsClose 的接口。这在python中可能吗?是的,有些程序可能可以解决这个问题,但是当我阅读资源时,我想直接看到“啊资源实现了名为supportsclose的接口”......这在python中可能吗? 是的,可以显式声明一个实现,as shown later in PEP 544。 好吧,PEP 544 给出的示例展示了typing.Protocol 如何用于支持静态类型分析和类型提示。为此,一个类是显式还是隐式实现Protocol 是无关紧要的。我实际上不喜欢显式继承Protocol,因为这会破坏它的实用性。如果子类对协议上定义的方法的显式实现作为重构的一部分被故意删除,那么它的使用者仍然可以调用此方法,而不会被静态类型提示工具警告该方法不再存在。 是的,它很棒:定义一个协议并实现它,让你很难弄清楚你刚刚做了什么,借口是“接口可能被其他人错误地删除了”。您的示例和答案与 OP 的问题无关,因为您自己就是消费者。 Because the example that you give is an anti pattern.。这不是真的。协议允许您让您的类型检查器为您进行鸭式输入验证。它类似于 Go,其中 interfaces are implicitly implemented.【参考方案2】:

简而言之,您可能根本不需要担心它。由于 Python 使用 duck typing - 另请参阅 Wikipedia article 以获得更广泛的定义 - 如果对象具有正确的方法,它将简单地工作,否则将引发异常。

您可能有一个 Piece 基类,其中一些方法抛出 NotImplementedError 以表明它们需要重新实现:

class Piece(object):

    def move(<args>):
        raise NotImplementedError(optional_error_message) 

class Queen(Piece):

    def move(<args>):
        # Specific implementation for the Queen's movements

# Calling Queen().move(<args>) will work as intended but 

class Knight(Piece):
    pass

# Knight().move() will raise a NotImplementedError

或者,您可以使用isinstance 或isubclass 显式验证您收到的对象以确保它具有所有正确的方法,或者它是Piece 的子类。 请注意,检查类型可能不会被某些人认为是“Pythonic”,使用NotImplementedError 方法或abc 模块(如this very good answer 中所述)可能更可取。

您的工厂只需要生成具有正确方法的对象实例。

【讨论】:

【参考方案3】:

我通常不使用 Python 中的接口,但如果你必须这样做,你可以使用zope.interface。然后,您可以验证类或对象是否实现了某些接口。此外,如果类没有实现所有方法或属性,它也会引发错误。 Twisted 和其他框架使用这个库。

【讨论】:

【参考方案4】:

我用 Python(使用 tkinter)编写了一个国际象棋游戏,我这样做的方式是创建一个 Piece 类,Queen/Knight/等。从 Piece 类、Player 类、Square 类和 tkinter 主循环的主程序类继承的类。每个棋子都有一个颜色和位置,以及一种帮助为直线移动直到被阻止的棋子生成移动集的方法。每个特定的 Piece 子类都包含一个确定其移动集的方法。 Square 对象包含棋子和棋盘在棋盘上的位置。

主程序类有一个__init__,用于设置棋盘、放置棋子、加载棋子图标和音效以及初始化选项。 draw_board 方法重绘棋盘,重置所有部分并重新绑定热键。然后还有其他各种方法来加载新图标、开始新游戏、设置音量、保存、撤消、城堡等等。

我还没有完成第 10 版,但您可以获取第 9 版的源代码和资产here。

您也可以查看开源Shane's Chess Information Database。没用过,感觉挺好看的。

【讨论】:

【参考方案5】:

尽管 Python 是动态的,可以使用鸭子类型,但仍然可以实现 Java 和 C# 所称的“接口”。这是通过声明一个抽象基类来完成的。 https://docs.python.org/2/library/abc.html 或 https://docs.python.org/3.4/library/abc.html

定义 ABC 时,将所有类接口方法放入其中,并在其主体中包含 passraise NotImplementedError。子类从您的 ABC 继承并覆盖这些方法,就像任何其他子类覆盖父类方法一样。 (由于 Python 具有多重继承,它们可以从您的 ABC 以及您喜欢的任何其他类继承。)

【讨论】:

【参考方案6】:

Python 的美妙之处在于不需要接口。由于鸭子类型,您可以创建几个具有相同方法签名的类:

class Queen:
  def move(self, x, y):
    #do stuff

class Pawn:
  def move(self, x, y):
    # do stuff

这些类的实例可以互换使用:

def make_move(piece, x, y):
  piece.move(x, y)

q = Queen()
make_move(q, 0, 0)
p = Pawn()
make_move(p, 4, 5)

请注意,对于成熟的国际象棋游戏来说,上述设计绝不是一个好的设计。它仅用于说明目的。

【讨论】:

但是接口允许您在编辑时检查您是否记得实现所有必需的方法。此外,如果您有一个像board.pieces.move( .. ) 这样的循环,那么您添加到pieces 的所有内容都会实现所需的东西。想象每件作品都是独一无二的,但有多种方法,如take, retreat, ... @dcsan 我同意,如果您正在实现需要符合正确协议的新“片段”类,则接口可以提供帮助。或者,您可以在参数、变量等上使用类型注释。我不确定“如果你有一个循环......”你会得到什么。如果board.pieces 是一个具有move() 方法的对象,那么鸭子类型将允许您实现理论上的board.pieces.move(),该方法循环遍历可以是实现在其上调用的方法的任何类的实例的片段集合。 但是鸭式打字并没有给你任何现代 IDE 将帮助的开发时间细节。它使编码变得乏味,尤其是重构。 @dcsan 是的,这就是权衡。类型注释有帮助,但感觉就像是一个黑客,接口或抽象基类更合适。【参考方案7】:

有一种模仿 Python 接口的好方法。 在 Python 中生成“接口”类时使用metaclass=ABCMeta 以及必须为此接口实现的所有方法的 @abstractmethod 装饰器。两者都来自abc 班级。 (如果没有实现任何这样的@abstractmethod-decorated 方法,在继承时实现“接口”类,一旦从这样的具体类生成实例,就会引发NotImplementedError。)

作为命名约定,所有此类类都以大写 I 开头(表示I接口)。

from abc import ABCMeta, abstractmethod


class IPiece(metaclass=ABCMeta):
    "The Piece Interface"
    
    @abstractmethod
    def move(<args>):
        "NotImplementedError is superfluous, instead, one can use this space"
        "To write some remarks, comments, annotations..."

class Queen(Piece):

    def move(<args>):
        # Specific implementation for the Queen's movements

【讨论】:

以上是关于用啥来替换python中的接口/协议的主要内容,如果未能解决你的问题,请参考以下文章

我可以用啥来替换嵌套的异步回调?

用啥来代替 JavaScript 中的类? [复制]

java web spring jpa 在以接口为dao的方法里使用原生sql,联合查找没有对应实体,用啥来接收? 求大神

在android中每隔几分钟我应该用啥来读取json?

为啥要用dbus,如果不用dbus要用啥来代替?

BaseJQueryEventObject、JQueryEventObject 等已弃用。我们用啥来代替它?