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

就算是吧

class Super():
def method1():
pass


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

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

72026 次浏览

在 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()

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

It will work:

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

You could do something like this if it's ok to use default arguments:

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

在 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
. Play with this code