提问人:asdasasdsa 提问时间:5/18/2011 更新时间:11/18/2019 访问量:85855
Python 方法覆盖,签名重要吗?
Python Method overriding, does signature matter?
问:
假设我有
class Super():
def method1():
pass
class Sub(Super):
def method1(param1, param2, param3):
stuff
这是正确的吗?对 method1 的调用是否总是转到子类?我的计划是有 2 个子类,每个子类都覆盖具有不同参数的 method1
答:
2赞
Zaur Nasibov
5/18/2011
#1
在 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()
51赞
Ignacio Vazquez-Abrams
5/18/2011
#2
Python 将允许这样做,但如果打算从外部代码执行,那么您可能需要重新考虑这一点,因为它违反了 LSP,因此并不总是正常工作。method1()
评论
3赞
unode
5/19/2011
Sub.method1 接受 3 个参数而 Super.method1 不接受任何参数这一事实是否违反了 LSP,使它们实际上是不同的接口?
2赞
Ignacio Vazquez-Abrams
5/19/2011
@Unode:正确。这可以通过让子类方法的参数都具有默认值来解决,但随后你就会进入哪些默认值是合适的。
4赞
unode
5/19/2011
明白了。但后来只是为了澄清一下。如果父方法1被定义为它仍然会违反LSP,如果在子类上它被定义为正确?因为属性在一种情况下是必需的,但在另一种情况下不是。因此,据我所知,在不更改子类接口的情况下,不违反 LSP 的唯一方法是在父类上拥有没有默认值的参数。我在这一点上是正确的还是过度解读了 LSP?Super.method1(param1=None, param2=None, param3=None)
Sub.method1(param1, param2, param3)
3赞
Ignacio Vazquez-Abrams
5/19/2011
@Unode:也正确。在子类中减少合同的限制违反了 LSP。
0赞
joel
2/13/2020
除非 when is ,其中 LSP 不适用method1
__init__
2赞
quasistoic
5/18/2011
#3
它将起作用:
>>> 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 的回答:具有相同名称和不同参数的方法是一种代码味道。
4赞
Zitrax
8/13/2016
#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
151赞
Shital Shah
1/12/2019
#5
在 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
上面将打印:
Hello Alice Hello Bob Hello Rick Your age is None Hello John Your age is 30.玩转此代码
评论
6赞
nealmcb
2/26/2020
感谢几年后的更新,有了更清晰、更可操作的讨论,以及一个工作示例和 playpen!
2赞
Quickbeam2k1
3/5/2020
我们应该把年龄放在你好之后的签名中吗?否则,代码将不起作用。我的意思是,一般来说,位置参数应该始终在 kwargs 之前定义*args
d.hello("John", "blue", age=30)
1赞
Nerxis
7/3/2020
我认为您的问题的答案取决于我们是否允许带有默认选项(此处)的参数也按位置设置或仅设置为 kwarg。请注意,您的建议仅适用于 Python 3,您可以在其中使用 .例如,请参阅此。age
*
7赞
Michał Jabłoński
9/29/2020
很好的答案,但在“LSP”中,如果人们从基础实例切换到派生实例,反之亦然“,则”反之亦然“部分是不正确的。
1赞
dumbledad
5/7/2021
如果省略派生类方法的签名,那么它仍然有效吗?*args, **kwargs
评论