Может ли базовый класс Python Abstract Base подписывать функции?

Предположим, что я определяю абстрактный базовый класс следующим образом:

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 - неправильный язык, который нужно использовать, если я хочу получить такие виды гарантий, но это не так, возможно ли это?

Ответы

Ответ 1

Это хуже, чем вы думаете. Абстрактные методы отслеживаются только по имени, поэтому вам даже не нужно делать метод quack для создания экземпляра дочернего класса.

class SurrealDuck(Quacker):
    quack = 3

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

В системе нет ничего, что обеспечивало бы, что quack является даже вызываемым объектом, не говоря уже о том, чьи аргументы соответствуют оригиналу абстрактного метода. В лучшем случае вы можете подклассифицировать ABCMeta и добавить код самостоятельно, чтобы сравнить сигнатуры типов в дочернем по отношению к оригиналам в родительском объекте, но это было бы нетривиально для реализации.

(В настоящее время, отмечая что-то как "абстрактное", по существу просто добавляет имя в атрибут "замороженный набор" в родительском (Quacker.__abstractmethods__). Создание класса-экземпляра так же просто, как установка этого атрибута на пустой итерабельный, что полезно для тестирования.)

Ответ 2

Я рекомендую вам посмотреть на pylint. Я пропустил этот код через статический анализ, и в строке, где вы определили метод quack(), он сообщил:

Argument number differs from overridden method (arguments-differ)

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