Python 如何强制子类的方法签名?
像C#,Java这样的语言有方法重载,这意味着如果子类没有实现具有精确签名的方法,则不会覆盖父方法 我们如何在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
>>> 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行:)这种方法的一个问题是强制执行是由基类完成的,因此这不会阻止毫无戒心的用户获得错误的签名。