Python方法覆盖,签名是否重要?

asd*_*dsa 48 python methods inheritance overriding

可以说我有

class Super():
  def method1():
    pass

class Sub(Super):
  def method1(param1, param2, param3):
      stuff
Run Code Online (Sandbox Code Playgroud)

它是否正确?对method1的调用总是会转到子类吗?我的计划是让2个子类重写方法1,使用不同的参数

Ign*_*ams 35

Python将允许这样做,但如果method1()打算从外部代码执行,那么您可能需要重新考虑这一点,因为它违反了LSP,因此无法始终正常工作.

  • 我知道了.但随后只是澄清一下.如果父方法1被定义为`Super.method1(param1 = None,param2 = None,param3 = None)`,如果在子类上它被定义为`Sub.method1(param1,param2,param3),那么它仍会违反LSP吗?由于属性在一种情况下是强制性的,而在另一种情 因此,根据我的理解,不改变子类接口,不违反LSP的唯一方法是在父节点上没有默认值的参数.我是正确的还是过度解释LSP? (3认同)
  • @Unode:也正确.使合同在子类中变得更少限制违反了LSP. (3认同)
  • Sub.method1 有 3 个参数而 Super.method1 没有,这是否违反了 LSP 使它们实际上是不同的接口? (2认同)
  • @Unode:正确。这可以通过让子类方法的参数都具有默认值来解决,但随后您将了解哪些默认值是合适的。 (2认同)

Shi*_*hah 16

在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
Run Code Online (Sandbox Code Playgroud)

上面将打印:

    你好爱丽丝
    你好鲍勃
    你好里克
    你的年龄是无
    你好约翰
    你30岁
玩这个代码

  • 感谢几年后的这次更新,提供了更清晰、更具可操作性的讨论,以及一个工作示例和婴儿围栏! (7认同)
  • 很好的答案,但在“LSP 保证如果人从基础实例切换到派生实例,反之亦然”中,“反之亦然”部分是不正确的。 (3认同)
  • 我们应该将年龄放在 hello 签名中的“*args”之后吗?如果没有,像`d.hello("John", "blue",age=30)`这样的代码将不起作用。我的意思是,一般来说,位置参数应该总是在 kwargs 之前定义 (2认同)
  • 如果您从派生类方法的签名中省略“*args,**kwargs”,那么它仍然有效吗? (2认同)

Zau*_*bov 2

在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
Run Code Online (Sandbox Code Playgroud)

方法签名确实很重要。你不能调用这样的方法:

sub = Sub()
sub.method1() 
Run Code Online (Sandbox Code Playgroud)