Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/css/34.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 如何强制子类的方法签名?_Python_Polymorphism - Fatal编程技术网

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

Python 如何强制子类的方法签名?,python,polymorphism,Python,Polymorphism,像C#,Java这样的语言有方法重载,这意味着如果子类没有实现具有精确签名的方法,则不会覆盖父方法 我们如何在python的子类中强制方法签名?以下代码示例显示,子类使用不同的方法签名覆盖父方法: >>> class A(object): ... def m(self, p=None): ... raise NotImplementedError('Not implemented') ... >>> class B(A): ... def m

像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)。我这样问是为了澄清这是否可能

请注意:


我已经尝试了
@abstractmethod
ABC

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


从这个线程来看,听起来您可能可以编写一个装饰程序来检查签名,使用abc.same_签名(method1,method2),但我从未尝试过。下面是一个完整的运行示例,展示了如何使用元类来确保子类方法具有与其基类相同的签名。注意
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()

被重写的原因是它们实际上具有相同的方法签名。这里所写的内容类似于在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处理INT,并将任何其他类型传递给其父类

>>> 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
>b=b()
>>>b.m(2)
2.
>>>b.m.“你好”
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
文件“”,第6行,单位为m
文件“”,第3行,单位为m
未实现错误:未实现

更新已接受的答案以使用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()

在我的代码中,我使用元类用于其他目的,因此我推出了一个使用类装饰器的版本。下面的版本适用于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

mypy和我期望其他静态类型检查器会抱怨,如果子类上的方法与它们覆盖的方法具有不同的签名。在我看来,在子类上强制执行类型签名的最佳方法是强制执行mypy(或其他任何东西)。

您的示例比它应该的复杂得多。abstractmethod的使用与你真正的要求无关。我强烈建议您不要在示例中使用abstractmethod。@DanielSank感谢您的编辑,但您不应该在问题中对示例代码进行大的更改,缩进之类的事情是可以的,只要缩进不会改变代码的含义,但更大的更改会改变问题的含义。你也可以编辑掉OP偶然遇到的实际问题。我想最好的办法是通知提出问题的人代码中有不必要的错误?是的,我同意,我没有使用staticmethod decorator就开始了,我把它放进去,以防有人问“你试过ABC了吗?”@DanielSank是的,你可以在评论中指出,或者在回答中当然。看起来我误读了线程,唉,在abc模块中没有这样的方法。在inspect模块下,有一个getargspec(method),它将显示arg信息。如果需要,您可以对参数的数量(和名称)进行简单的比较。对不起,没有先检查一下!您不能键入check参数,但是我们有inspect.getargspec.inspect模块,看起来需要很多代码才能实现done@JamesLin:大量代码?该元类有12行:)这种方法的一个问题是强制执行是由基类完成的,因此这不会阻止毫无戒心的用户获得错误的签名。