所以,首先,明确一点 - Python 有一个内置机制来测试派生类中方法和 属性 的存在性 - 它就是这样做的不检查他们的签名。
其次,一个不错的包是zope.interface。除了 zope 命名空间,它是一个完整的独立包,它允许拥有可以公开多个接口的对象的非常简洁的方法,但只是在需要时——然后释放命名空间。它肯定需要一些学习才能习惯它,但它可能非常强大,并为大型项目提供了非常好的模式。
它是为 Python 2 设计的,当时 Python 的功能比现在少得多——而且我认为它不执行自动接口检查(必须手动调用一个方法来确定一个类是否兼容)——但是尽管如此,自动执行此调用会很容易。
第三,How to enforce method signature for child classes? 的链接接受答案几乎可以工作,只需进行一次更改就足够了。该示例的问题在于它硬编码了对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)
回顾一下,我发现其中没有任何机制可以强制实际实施方法。 IE。如果派生类中存在具有相同名称的方法,则强制执行其签名,但如果派生类中根本不存在该方法,则上面的代码将不会发现它(并且超类上的方法将是调用 - 这可能是一种期望的行为)。
答案:
第四 -
虽然这会起作用,但它可能有点粗糙 - 因为它的 any 方法覆盖任何超类中的另一个方法必须符合其签名。甚至兼容的签名也会中断。也许建立在ABCMeta 和@abstractmethod 现有机制的基础上会很好,因为这些机制已经适用于所有极端情况。但是请注意,此示例基于上面的代码,并在 class 创建时检查签名,而 Python 中的 abstractclass 机制使其在类被实例化时检查。保持不变将使您能够使用大型类层次结构,这可能会在中间类中保留一些抽象方法,而最终的具体类必须实现所有方法。
只需使用它而不是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)