Python-强制子类使用特定的方法签名?

时间:2019-03-23 01:35:52

标签: python class metaclass

我想创建一个定义特定接口的类,然后要求所有子类都符合该接口。例如,我想定义一个类

class Interface:
    def __init__(self, arg1):
       pass

    def foo(self, bar):
       pass

,然后放心,如果我拥有a类型为A的元素Interface的任何元素a.foo(2),那么我可以调用class Character { constructor(name, age, role, race) { this.name = document.getElementById("nameCharacter").value; this.age = document.getElementById("ageCharacter").value; this.role = document.getElementById("classCharacter").value; this.race = document.getElementById("raceCharacter").value; } } var hero; function create() { hero = new Character(); },它将起作用。

看起来this question几乎解决了这个问题,但是在这种情况下,必须由子类显式更改其元类。

理想情况下,我正在寻找与Rust中的Traits和Impls类似的东西,在这里我可以指定特定的Trait和trait需要定义的方法列表,然后可以确信具有该Trait的任何对象定义了那些方法。

在Python中有什么方法可以做到吗?

2 个答案:

答案 0 :(得分:1)

您可以遵循pyspark模式,其中基类的方法执行(可选)参数有效性检查,然后调用子类的“非公共”方法,例如:

class Regressor():

    def fit(self, X, y):
        self._check_arguments(X, y)
        self._fit(X, y)

    def _check_arguments(self, X, y):
        if True:
             pass

        else:
            raise ValueError('Invalid arguments.')

class LinearRegressor(Regressor):

    def _fit(self, X, y):
        # code here

答案 1 :(得分:1)

因此,首先,为了说明显而易见的事实-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)

在回顾这一点时,我发现其中没有机制可以强制实际上实现一种方法。即如果派生类中存在具有相同名称的方法,则将强制执行其签名,但是如果派生类中根本不存在该方法的签名,则上面的代码将找不到它(并且超类上的方法将是被称为-这可能是理想的行为。

答案:

第四- 尽管这样做可以,但是可能有点粗糙-因为它可以覆盖任何超类中的另一个方法的 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)