Итак, во-первых, просто констатирую очевидное: в Python есть встроенный механизм для проверки существования методов и атрибутов в производных классах - он просто не проверяет их подпись.
Во-вторых, приятно посмотреть на пакет zope.interface
. Отбросьте пространство имен zope
, это полный автономный пакет, который позволяет действительно аккуратные методы иметь объекты, которые могут предоставлять несколько интерфейсов, но только когда это необходимо, и затем освобождает пространства имен. Это, конечно, требует некоторого обучения, пока вы не привыкнете к нему, но оно может быть довольно мощным и обеспечивать очень хорошие шаблоны для больших проектов.
Он был разработан для Python 2, когда в Python было намного меньше возможностей, чем в настоящее время - и я думаю, что он не выполняет автоматическую проверку интерфейса (нужно вручную вызывать метод, чтобы выяснить, соответствует ли класс), - но тем не менее, автоматизировать этот вызов будет легко.
В-третьих, связанный принятый ответ на Как применить сигнатуру метода для дочерних классов? почти работает и может быть достаточно хорош только с одним изменением. Проблема с этим примером заключается в том, что он жестко кодирует вызов type
для создания нового класса и не передает type.__new__
информацию о самом метаклассе. Заменить строку:
return type(name, baseClasses, d)
для:
return super().__new__(cls, name, baseClasses, d)
И затем, сделайте, чтобы базовый класс - тот, который определяет ваши требуемые методы, использовал метакласс - он будет обычно наследоваться любыми подклассами. (просто используйте синтаксис Python 3 для указания метаклассов).
Извините - этот пример - Python 2 - он также требует изменения в другой строке, я лучше перепостил:
from types import FunctionType
# from https://stackoverflow.com/a/23257774/108205
class SignatureCheckerMeta(type):
def __new__(mcls, name, baseClasses, d):
#For each method in d, check to see if any base class already
#defined a method with that name. If so, make sure the
#signatures are the same.
for methodName in d:
f = d[methodName]
for baseClass in baseClasses:
try:
fBase = getattr(baseClass, methodName)
if not inspect.getargspec(f) == inspect.getargspec(fBase):
raise BadSignatureException(str(methodName))
except AttributeError:
#This method was not defined in this base class,
#So just go to the next base class.
continue
return super().__new__(mcls, name, baseClasses, d)
При рассмотрении этого я вижу, что в нем нет механизма, обеспечивающего реализацию метода на самом деле . То есть если метод с тем же именем существует в производном классе, его сигнатура применяется, но если он вообще не существует в производном классе, приведенный выше код не узнает об этом (и метод в суперклассе будет звонил - это может быть желаемое поведение).
Ответ:
Четвертый -
Хотя это будет работать, это может быть немного грубым - поскольку он любой метод, который переопределяет другой метод в любом суперклассе, должен будет соответствовать его сигнатуре. И даже совместимые подписи сломались бы. Возможно, было бы неплохо использовать механизмы ABCMeta
и @abstractmethod
existind, поскольку они уже работают во всех случаях. Однако обратите внимание, что этот пример основан на приведенном выше коде, и проверьте подписи во время создания class , в то время как механизм abstractclass в Python заставляет его проверять, когда создается экземпляр класса. Оставляя это нетронутым, вы сможете работать с большой иерархией классов, которая может содержать некоторые абстрактные методы в промежуточных классах, и только конечные, конкретные классы должны реализовывать все методы.
Просто используйте это вместо ABCMeta
в качестве метакласса для ваших классов интерфейса и пометьте методы, которые вы хотите проверить, как @abstractmethod
как обычно.
class M(ABCMeta):
def __init__(cls, name, bases, attrs):
errors = []
for base_cls in bases:
for meth_name in getattr(base_cls, "__abstractmethods__", ()):
orig_argspec = inspect.getfullargspec(getattr(base_cls, meth_name))
target_argspec = inspect.getfullargspec(getattr(cls, meth_name))
if orig_argspec != target_argspec:
errors.append(f"Abstract method {meth_name!r} not implemented with correct signature in {cls.__name__!r}. Expected {orig_argspec}.")
if errors:
raise TypeError("\n".join(errors))
super().__init__(name, bases, attrs)