Python 方法覆盖,签名重要吗?

Python Method overriding, does signature matter?

提问人:asdasasdsa 提问时间:5/18/2011 更新时间:11/18/2019 访问量:85855

问:

假设我有

class Super():
  def method1():
    pass

class Sub(Super):
  def method1(param1, param2, param3):
      stuff

这是正确的吗?对 method1 的调用是否总是转到子类?我的计划是有 2 个子类,每个子类都覆盖具有不同参数的 method1

Python 继承 方法 重写

评论


答:

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 之前定义*argsd.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