我正在制作一个国际象棋游戏,希望能够创建一个标准的棋子接口/协议。Python语言本身没有这些功能,那么我应该使用什么呢?我了解过一些关于工厂模式的知识,但不确定它们如何有所帮助。先提前感谢您的回答!
我正在制作一个国际象棋游戏,希望能够创建一个标准的棋子接口/协议。Python语言本身没有这些功能,那么我应该使用什么呢?我了解过一些关于工厂模式的知识,但不确定它们如何有所帮助。先提前感谢您的回答!
Python 3.8 新特性:
接口和协议的一些优点是使用 IDE 内置的工具进行类型提示和静态类型分析,以便在运行时之前检测错误。这样,静态分析工具可以告诉您在检查代码时是否正在尝试访问未定义的对象成员,而不仅仅在运行时发现。
Python 3.8 引入了 typing.Protocol
类,作为 "结构子类型" 的机制。其威力在于它可用作 隐式基类。也就是说,如果任何类的成员与 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
包 为 Python 3.5+ 回溯了 typing.Protocol
。typing.Protocol
如何用于支持静态类型分析和类型提示。出于这些目的,类是否显式或隐式地实现了Protocol
都是无关紧要的。实际上,我更喜欢不显式继承Protocol
,因为这可能会削弱其有用性。如果子类对协议定义的方法进行显式实现,并且在重构中故意将其删除,则其使用者仍然可以调用此方法,而不会被静态类型提示工具警告该方法已不存在。 - gooberwonder简而言之,您可能根本不需要担心它。由于Python使用鸭子类型 - 另请参阅维基百科文章获取更广泛的定义 - 如果一个对象有正确的方法,它将简单地工作,否则将引发异常。
您可以可能有一个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
或者,您可以显式验证接收到的对象,以确保它具有所有正确的方法,或者它是Piece
的子类,通过使用isinstance或issubclass。
请注意,检查类型可能不被某些人认为是“Pythonic”,并且使用NotImplementedError
方法或abc
模块-如在这个非常好的答案中提到的-可能更可取。
您的工厂只需要生成具有正确方法的对象实例。
我通常不在Python中使用接口,但是如果你必须这样做,你可以使用zope.interface
。然后,你可以验证类或对象是否实现了某些接口。此外,它还可以在类没有实现所有方法或属性时引发错误。Twisted和其他框架使用这个库。
pass
或 raise NotImplementedError
。子类从您的 ABC 继承并像任何其他子类一样覆盖这些方法。(由于 Python 具有多重继承,它们可以从您的 ABC 加上任何其他类继承。)metaclass = ABCMeta
,并对于所有必须为此接口实现的方法使用@abstractmethod
装饰器。 两者都来自于abc
类。(如果不实现任何这样的@abstractmethod
修饰的方法,并且同时继承了实现“I接口”类,则生成此类的实例后将引发NotImplementedError
。)I
(代表Interface)开头命名所有这样的类。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
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,…” - dcsanboard.pieces.move()
,如果 board.pieces
是一个具有 move()
方法的对象,该方法循环遍历一组可以是任何实现调用它的方法的类的实例的 piece。 - Code-Apprenticetyping.Protocol
的具体mypy
示例(静态类型检查)
https://dev59.com/Gl4b5IYBdhLWcg3whB-V#50255847提供了一个示例,但这里有一个稍微具体一些的示例,包括样本mypy
输出。相关:如何在Python中实现虚方法?
protocol.py
from typing import Protocol
class CanFly(Protocol):
def fly(self) -> str:
pass
def fly_fast(self) -> str:
return 'CanFly.fly_fast'
class Bird(CanFly):
def fly(self):
return 'Bird.fly'
def fly_fast(self):
return 'Bird.fly_fast'
class FakeBird(CanFly):
pass
assert Bird().fly() == 'Bird.fly'
assert Bird().fly_fast() == 'Bird.fly_fast'
# mypy error
assert FakeBird().fly() is None
# mypy error
assert FakeBird().fly_fast() == 'CanFly.fly_fast'
python protocol.py
但如果我们使用 mypy
进行类型检查:
python -m pip install --user mypy
mypy protocol.py
正如预期,我们遇到了一个错误:
protocol.py:22: error: Cannot instantiate abstract class "FakeBird" with abstract attribute "fly"
protocol.py:24: error: Cannot instantiate abstract class "FakeBird" with abstract attribute "fly"
有点不幸的是,错误检查只在实例化时捕获错误,而不是在类定义时。
typing.Protocol
在方法体“为空”时将其视为抽象方法
我不确定它们认为什么是空的,但以下所有内容都被视为为空:
pass
...
省略号对象raise NotImplementedError()
因此,最好的可能性是:
protocol_empty.py
from typing import Protocol
class CanFly(Protocol):
def fly(self) -> None:
raise NotImplementedError()
class Bird(CanFly):
def fly(self):
return None
class FakeBird(CanFly):
pass
Bird().fly()
FakeBird().fly()
达到预期的失败状态:
protocol_empty.py:15: error: Cannot instantiate abstract class "FakeBird" with abstract attribute "fly"
protocol_empty.py:15: note: The following method was marked implicitly abstract because it has an empty function body: "fly". If it is not meant to be abstract, explicitly return None.
但是,如果例如我们替换:
raise NotImplementedError()
带有一些随机的“非空”语句,例如:
x = 1
那么mypy
不会将它们视为虚拟的,并且不会产生任何错误。
在Python 3.10.7、mypy 0.982和Ubuntu 21.10上进行了测试。