假设我这样定义一个抽象基类:
from abc import abstractmethod, ABCMeta
class Quacker(object):
__metaclass__ = ABCMeta
@abstractmethod
def quack(self):
return "Quack!"
这确保从Quacker
派生的任何类必须实现quack
方法。但是如果我定义以下内容:
class PoliteDuck(Quacker):
def quack(self, name):
return "Quack quack %s!" % name
d = PoliteDuck() # no error
我被允许实例化类,因为我已经提供了quack
方法,但是函数签名不匹配。我可以看到这在某些情况下是如何有用的,但我感兴趣的是确保我可以明确地调用抽象方法。如果函数签名不同,这可能会失败!
那么:我如何强制匹配函数签名?如果签名不匹配,我希望在创建对象时出现错误,就像我根本没有定义它一样。
我知道这不是习惯用语,如果我想要这些保证,Python是错误的语言,但这不是重点-这可能吗?
情况比你想象的还要糟。抽象方法仅按名称跟踪,因此您甚至不必为了实例化子类而使quack
成为一个方法。
class SurrealDuck(Quacker):
quack = 3
d = SurrealDuck()
print d.quack # Shows 3
系统中没有任何东西强制quack
是一个可调用对象,更不用说它的参数与抽象方法的原始参数匹配了。在最好的情况下,您可以创建ABCMeta
的子类,并自己添加代码,将子类中的类型签名与父类中的原始类型签名进行比较,但这很难实现。
(目前,将某些内容标记为"抽象"本质上只是将名称添加到父(Quacker.__abstractmethods__
)中的冻结集属性中。使一个类可实例化非常简单,只需将这个属性设置为一个空的可迭代对象,这对测试很有用。
我建议你看看pylint。我通过静态分析运行这段代码,在定义quack()方法的那一行,它报告说:
Argument number differs from overridden method (arguments-differ)
(https://en.wikipedia.org/wiki/Pylint)
我认为这在基础python
语言中没有改变,但我确实找到了一个可能有用的解决方案。mypy
包似乎在抽象基类及其具体实现上强制了签名一致性。基本上,如果你在抽象基类上定义签名,所有具体类都必须遵循相同的签名。
这是一个将在mypy
中中断的示例。代码取自mypy
网站,但我为这个答案进行了改编。
第一个示例是将通过的代码。注意,eat
方法的签名是相同的,mypy
没有报错。
from abc import ABCMeta, abstractmethod
class Animal(metaclass=ABCMeta):
@abstractmethod
def eat(self, food: str) -> None: pass
@property
@abstractmethod
def can_walk(self) -> bool: pass
class Cat(Animal):
def eat(self, food: str) -> None:
pass # Body omitted
@property
def can_walk(self) -> bool:
return True
y = Cat() # OK
让我们稍微调整一下这段代码,现在mypy
抛出一个错误:
from abc import ABCMeta, abstractmethod
class Animal(metaclass=ABCMeta):
@abstractmethod
def eat(self, food: str) -> None: pass
@property
@abstractmethod
def can_walk(self) -> bool: pass
class Cat(Animal):
def eat(self, food: str, drink: str) -> None:
pass # Body omitted
@property
def can_walk(self) -> bool:
return True
y = Cat() # Error
Mypy
仍然是一个正在进行的工作,但它确实在这种情况下工作。在一些极端情况下,一些签名变体可能不会被捕获,但其他情况似乎适用于大多数实际应用。