36

假设我定义了一个这样的抽象基类

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 是错误的语言,但这不是重点——有可能吗?

4

3 回答 3

31

它比你想象的还要糟糕。抽象方法仅按名称跟踪,因此您甚至不必创建quack方法来实例化子类。

class SurrealDuck(Quacker):
    quack = 3

d = SurrealDuck()
print d.quack   # Shows 3

系统中没有任何东西可以强制执行quack甚至是可调用对象,更不用说参数与抽象方法的原始参数匹配的对象了。充其量,您可以ABCMeta自己子类化并添加代码,以将子代中的类型签名与父代中的原始类型签名进行比较,但这并不容易实现。

(目前,将某些内容标记为“抽象”实际上只是将名称添加到父级(Quacker.__abstractmethods__)中的冻结集属性。使类可实例化就像将此属性设置为空的迭代一样简单,这对于测试很有用。)

于 2014-08-07T13:30:09.010 回答
8

我建议你看看 pylint。我通过它的静态分析运行了这段代码,在你定义 quack() 方法的那一行,它报告:

Argument number differs from overridden method (arguments-differ)

https://en.wikipedia.org/wiki/Pylint

于 2014-08-07T13:28:14.087 回答
3

我认为这在基本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仍然是一项正在进行中的工作,但在这种情况下确实有效。在某些极端情况下,某些签名变体可能不会被捕获,但其他情况似乎适用于大多数实际应用。

于 2019-03-12T16:36:37.780 回答