Python方法覆盖,签名重要吗?
Posted
技术标签:
【中文标题】Python方法覆盖,签名重要吗?【英文标题】:Python Method overriding, does signature matter? 【发布时间】:2011-08-27 10:33:04 【问题描述】:假设我有
class Super():
def method1():
pass
class Sub(Super):
def method1(param1, param2, param3):
stuff
这是正确的吗?对method1的调用是否总是转到子类?我的计划是有 2 个子类,每个子类都使用不同的参数覆盖 method1
【问题讨论】:
【参考方案1】:在 Python 中,方法只是附加到类的字典中的键值对。当您从基类派生一个类时,您实际上是在说方法名称将在第一个派生类字典中查找,然后在基类字典中查找。为了“覆盖”一个方法,您只需在派生类中重新声明该方法即可。
那么,如果在派生类中更改被覆盖方法的签名会怎样?如果在派生实例上调用一切正常,但如果在基实例上进行调用,则会收到错误消息,因为基类对同一方法名称使用不同的签名。
然而,在某些情况下,您希望派生类方法具有 附加 参数,并且您希望方法调用在基础上也能正常工作。这被称为“Liskov 替换原则”(或LSP),它保证如果人们从基础实例切换到派生实例,反之亦然,他们不必修改他们的代码。要在 Python 中执行此操作,您需要使用以下技术设计基类:
class Base:
# simply allow additional args in base class
def hello(self, name, *args, **kwargs):
print("Hello", name)
class Derived(Base):
# derived class also has unused optional args so people can
# derive new class from this class as well while maintaining LSP
def hello(self, name, age=None, *args, **kwargs):
super(Derived, self).hello(name, age, *args, **kwargs)
print('Your age is ', age)
b = Base()
d = Derived()
b.hello('Alice') # works on base, without additional params
b.hello('Bob', age=24) # works on base, with additional params
d.hello('Rick') # works on derived, without additional params
d.hello('John', age=30) # works on derived, with additional params
上面将打印:
你好爱丽丝 你好鲍勃 你好瑞克 你的年龄没有 你好约翰 你的年龄是 30 。 Play with this code【讨论】:
感谢几年后的这次更新,提供了更清晰、更可行的讨论,以及一个工作示例和游戏笔! 我们应该在*args
之后的hello的签名中加入年龄吗?如果不是这样,d.hello("John", "blue", age=30)
之类的代码将不起作用。我的意思是,一般来说,位置参数应该总是在 kwargs 之前定义
我认为您的问题的答案取决于我们是否要允许使用默认选项的参数(age
这里)也可以按位置设置或仅设置为 kwarg。请注意,您的建议仅适用于 Python 3,您可以在其中使用 *
指定仅关键字参数。参见例如this.
很好的答案,但在“LSP 保证如果人从基础实例切换到派生实例或反之亦然”中,“反之亦然”部分不正确。
如果你在派生类方法的签名中省略了*args, **kwargs
,那么它仍然有效吗?【参考方案2】:
Python 将允许这样做,但如果打算从外部代码执行 method1()
,那么您可能需要重新考虑这一点,因为它违反了 LSP,因此不会总是正常工作。
【讨论】:
Sub.method1 接受 3 个参数,而 Super.method1 不接受,从而使它们实际上是不同的接口,这是否违反了 LSP? @Unode:正确。这可以通过让子类方法的参数都具有默认值来解决,但随后您会了解哪些默认值是合适的。 我明白了。但只是为了澄清。如果父方法 1 被定义为Super.method1(param1=None, param2=None, param3=None)
它仍然会违反 LSP 如果在子类上它被定义为 Sub.method1(param1, param2, param3)
对吗?因为属性在一种情况下是强制性的,但在另一种情况下不是。因此,据我了解,在不更改子类接口的情况下,不违反 LSP 的唯一方法是在父类上使用没有默认值的参数。我对此是否正确还是过度解释了 LSP?
@Unode:也正确。让合约在子类中变得更少限制违反了 LSP。
method1
为 __init__
时除外,此时 LSP 不适用【参考方案3】:
在 python 中,所有类方法都是“虚拟的”(就 C++ 而言)。因此,对于您的代码,如果您想在超类中调用 method1()
,它必须是:
class Super():
def method1(self):
pass
class Sub(Super):
def method1(self, param1, param2, param3):
super(Sub, self).method1() # a proxy object, see http://docs.python.org/library/functions.html#super
pass
而且方法签名确实很重要。你不能调用这样的方法:
sub = Sub()
sub.method1()
【讨论】:
【参考方案4】:如果可以使用默认参数,您可以这样做:
>>> class Super():
... def method1(self):
... print("Super")
...
>>> class Sub(Super):
... def method1(self, param1="X"):
... super(Sub, self).method1()
... print("Sub" + param1)
...
>>> sup = Super()
>>> sub = Sub()
>>> sup.method1()
Super
>>> sub.method1()
Super
SubX
【讨论】:
【参考方案5】:它会起作用的:
>>> class Foo(object):
... def Bar(self):
... print 'Foo'
... def Baz(self):
... self.Bar()
...
>>> class Foo2(Foo):
... def Bar(self):
... print 'Foo2'
...
>>> foo = Foo()
>>> foo.Baz()
Foo
>>>
>>> foo2 = Foo2()
>>> foo2.Baz()
Foo2
但是,通常不建议这样做。看看S.Lott的回答:Methods with the same name and different arguments are a code smell。
【讨论】:
以上是关于Python方法覆盖,签名重要吗?的主要内容,如果未能解决你的问题,请参考以下文章