Python方法重写,签名重要吗?


76

可以说我有

class Super():
  def method1():
    pass

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

它是否正确?将对method1的调用始终转到子类吗?我的计划是让2个子类分别覆盖具有不同参数的method1

Answers:


58

在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岁
玩这个代码


1
几年后感谢您进行此更新,进行了更清晰,更可行的讨论,并提供了一个有效的示例以及游戏围栏!
nealmcb

1
我们应该把年龄放在你好之后*args吗?如果没有,类似的代码d.hello("John", "blue", age=30)将无法工作。我的意思是,一般来说,位置args应该始终在kwargs之前定义
Quickbeam2k1

1
我认为您问题的答案取决于我们是否要允许将默认选项(age此处)的参数设置为位置还是仅设置为kwarg。请注意,您的建议仅在Python 3中有效,您可以在Python 3中使用来指定仅关键字参数*。参见例如this
Nerxis

1
很好的答案,但是在“ LSP保证人从基本实例切换到派生实例,反之亦然”中,“反之亦然”部分不正确。
–MichałJabłoński

43

Python将允许这样做,但是如果method1()打算从外部代码执行,则您可能需要重新考虑这一点,因为它违反了LSP,因此不一定总是能正常工作。


1
在Sub.method1接受3个参数而Super.method1不接受3个参数的情况下,是否违反了LSP?
取消播放

2
@Unode:正确。这可以通过让子类的方法的参数都具有默认值来解决,但是您将了解哪些默认值合适。
伊格纳西奥·巴斯克斯

3
我懂了。但是接下来只是要澄清一下。如果父方法1被定义为Super.method1(param1=None, param2=None, param3=None)如果在子类上被定义为Sub.method1(param1, param2, param3)正确,那么它仍然会违反LSP ?由于属性在一种情况下是必需的,而在另一种情况下则不是。因此,根据我的理解,在不更改子类接口的情况下,不违反LSP的唯一方法是在父级上使用没有默认值的参数。我对此是正确的还是对LSP的解释过度了?
取消播放

3
@Unode:也正确。使合同在子类中的限制较少,就会违反LSP。
伊格纳西奥·巴斯克斯

除外,当method1is时__init__,不适用于LSP
joel

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

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

sub = Sub()
sub.method1() 

2

如果可以使用默认参数,则可以执行以下操作:

>>> 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

1

它将起作用:

>>> 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的答案:具有相同名称和不同参数的方法是代码气味


-2

是。调用“ method1”将始终转到子类。Python中的方法签名仅包含名称,而不包含参数列表。


2
错误的答案。方法签名始终很重要,因为C / C ++不会发生函数重载。
Zaur Nasibov

我的意思是,Python不会考虑参数列表来决定要调用的方法,但是我认为从那个角度看它是正确的!
Elektito
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.