Can a Python Abstract Base Class enforce function signatures?

妖精的绣舞 提交于 2019-11-28 02:48:37

问题


Suppose I define an abstract base class like this:

from abc import abstractmethod, ABCMeta

class Quacker(object):
  __metaclass__ = ABCMeta

  @abstractmethod
  def quack(self):
    return "Quack!"

This ensures any class deriving from Quacker must implement the quack method. But if I define the following:

class PoliteDuck(Quacker):
   def quack(self, name):
     return "Quack quack %s!" % name

d = PoliteDuck()  # no error

I'm allowed to instantiate the class because I've provided the quack method, but the function signatures don't match. I can see how this might be useful in some situations, but I'm in interested in ensuring I can definitely call the abstract methods. This might fail if the function signature is different!

So: how can I enforce a matching function signature? I would expect an error when creating the object if the signatures don't match, just like if I hadn't defined it at all.

I know that this is not idiomatic, and that Python is the wrong language to be using if I want these sorts of guarantees, but that's beside the point - is it possible?


回答1:


It's worse than you think. Abstract methods are tracked by name only, so you don't even have to make quack a method in order to instantiate the child class.

class SurrealDuck(Quacker):
    quack = 3

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

There is nothing in the system that enforces that quack is even a callable object, let alone one whose arguments match the abstract method's original. At best, you could subclass ABCMeta and add code yourself to compare type signatures in the child to the originals in the parent, but this would be nontrivial to implement.

(Currently, marking something as "abstract" essentially just adds the name to a frozen set attribute in the parent (Quacker.__abstractmethods__). Making a class instantiable is as simple as setting this attribute to an empty iterable, which is useful for testing.)




回答2:


I recommend you look at pylint. I ran this code through it's static analysis, and on the line where you defined the quack() method, it reported:

Argument number differs from overridden method (arguments-differ)

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




回答3:


I don't think this has changed in the base python language, but I did find one workaround that might be useful. The mypy package does seem to enforce signature conformity on abstract base classes and their concrete implementation. So basically if you define a signature on the abstract base class, all concrete classes have to follow the same exact signature.

Here is an example that will break in mypy. The code is taken from the mypy website, but I adapted it for this answer.

The first example is code that will pass. Note that the signatures for the eat method are the same and mypy does not complain.

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

But let's adapt this code a bit and now mypy throws an error:

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 is still very much a work-in-progress but it does work in this case. There are some corner cases where some signature variants might not get caught, but otherwise seems to work for most practical applications.



来源:https://stackoverflow.com/questions/25183424/can-a-python-abstract-base-class-enforce-function-signatures

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!