如何强制子类的方法签名?

时间:2014-04-23 21:44:57

标签: python polymorphism

像C#这样的语言,Java有方法重载,这意味着如果子类没有实现具有精确签名的方法,则不会覆盖父方法。

我们如何在python中的子类中强制执行方法签名?以下代码示例显示子类使用不同的方法签名覆盖父方法:

>>> class A(object):
...   def m(self, p=None):
...     raise NotImplementedError('Not implemented')
... 
>>> class B(A):
...   def m(self, p2=None):
...     print p2
... 
>>> B().m('123')
123

虽然这不是非常重要,或者可能是python的设计(例如* args,** kwargs)。我这样做是为了清楚,如果可能的话。

请注意:

我已尝试过@abstractmethodABC

6 个答案:

答案 0 :(得分:3)

下面是一个完整的运行示例,展示了如何使用元类来确保子类方法与其基类具有相同的签名。请注意inspect模块的使用。我在这里使用它的方式确保签名完全相同,这可能不是你想要的。

import inspect

class BadSignatureException(Exception):
    pass


class SignatureCheckerMeta(type):
    def __new__(cls, 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).__func__
                    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 type(name, baseClasses, d)


def main():

    class A(object):
        def foo(self, x):
            pass

    try:
        class B(A):
            __metaclass__ = SignatureCheckerMeta
            def foo(self):
                """This override shouldn't work because the signature is wrong"""
                pass
    except BadSignatureException:
        print("Class B can't be constructed because of a bad method signature")
        print("This is as it should be :)")

    try:
        class C(A):
            __metaclass__ = SignatureCheckerMeta
            def foo(self, x):
                """This is ok because the signature matches A.foo"""
                pass
    except BadSignatureException:
        print("Class C couldn't be constructed. Something went wrong")


if __name__ == "__main__":
    main()

答案 1 :(得分:2)

更新使用python 3.5的已接受答案。

import inspect
from types import FunctionType

class BadSignatureException(Exception):
    pass


class SignatureCheckerMeta(type):
    def __new__(cls, 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]

            if not isinstance(f, FunctionType):
                continue
            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 type(name, baseClasses, d)


def main():
    class A(object):
        def foo(self, x):
            pass

    try:
        class B(A, metaclass=SignatureCheckerMeta):
            def foo(self):
                """This override shouldn't work because the signature is wrong"""
                pass
    except BadSignatureException:
        print("Class B can't be constructed because of a bad method signature")
        print("This is as it should be :)")

    try:
        class C(A):
            __metaclass__ = SignatureCheckerMeta
            def foo(self, x):
                """This is ok because the signature matches A.foo"""
                pass
    except BadSignatureException:
        print("Class C couldn't be constructed. Something went wrong")


if __name__ == "__main__":
    main()

答案 2 :(得分:1)

根据设计,该语言不支持检查签名。有趣的阅​​读,请查看:

http://grokbase.com/t/python/python-ideas/109qtkrzsd/abc-what-about-the-method-arguments

从这个帖子中,听起来你可能会用abc.same_signature(method1,method2)来编写一个装饰器来检查签名,但我从来没有尝试过。

答案 3 :(得分:0)

它被覆盖的原因是因为它们实际上具有相同的方法签名。在那里写的是类似于在Java中做这样的事情:

public class A
{
    public void m(String p)
    {
        throw new Exception("Not implemented");
    }
}

public class B extends A
{
    public void m(String p2)
    {
        System.out.println(p2);
    }
}

请注意,即使参数名称不同,类型也相同,因此它们具有相同的签名。在像这样的强类型语言中,我们可以提前明确说出类型将会是什么类型。

在python中,当您使用该方法时,在运行时动态确定参数的类型。这使得python解释器无法在您说B().m('123')时告诉您实际希望调用哪种方法。因为这两个方法签名都没有指定他们期望的那种类型的参数,他们只是说我正在寻找一个带有一个参数的调用。所以有意义的是调用最深的(和你的实际对象最相关),这将是B类的方法,因为它是B类的一个实例。

如果你只想在子类方法中处理cetain类型,并将所有其他类型传递给父类,可以这样做:

class A(object):
    def m(self, p=None):
        raise NotImplementedError('Not implemented')

class B(A):
    def m(self, p2=None):
        if isinstance(p2, int):
            print p2
        else:
            super(B, self).m(p2)

然后使用b为您提供所需的输出。也就是说,类b处理整数,并将任何其他类型传递给它的父类。

>>> b = B()
>>> b.m(2)
2
>>> b.m("hello")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in m
  File "<stdin>", line 3, in m
NotImplementedError: Not implemented

答案 4 :(得分:0)

我在我的代码中使用元类用于其他目的,所以我推出了一个使用类装饰器的版本。以下版本适用于python3。并且还支持装饰方法(是的,这会产生一个潜在的漏洞,但是如果你使用改变实际签名的装饰器,那么你就会感到羞耻)。要使它与python2一起使用,请将inspect.isfunction更改为inspect.ismethod

import inspect
from functools import wraps

class BadSignatureException(Exception):
    pass

def enforce_signatures(cls):
    for method_name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
        if method_name == "__init__":
            continue
        for base_class in inspect.getmro(cls):
            if base_class is cls:
                continue

            try:
                base_method = getattr(base_class, method_name)
            except AttributeError:
                continue

            if not inspect.signature(method) == inspect.signature(base_method):
                raise BadSignatureException("%s.%s does not match base class %s.%s" % (cls.__name__, method_name,
                                                                                       base_class.__name__, method_name))

    return cls

if __name__ == "__main__":
    class A:
        def foo(self, x):
            pass

    def test_decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            pass
        return decorated_function

    @enforce_signatures
    class B(A):
        @test_decorator
        def foo(self):
            """This override shouldn't work because the signature is wrong"""
            pass

答案 5 :(得分:0)

mypy 以及我希望其他静态类型检查器会抱怨您的子类中的方法是否与它们覆盖的方法具有不同的签名。在我看来,在子类上强制执行类型签名的最佳方法是强制执行 mypy(或其他)。