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

发布于 2024-11-08 00:33:50 字数 220 浏览 0 评论 0原文

可以说我有

class Super():
  def method1():
    pass

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

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

Lets say I have

class Super():
  def method1():
    pass

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

Is this correct? Will calls to method1 always go to the sub class? My plan is to have 2 sub classes each override method1 with different params

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

时光是把杀猪刀 2024-11-15 00:33:50

在Python中,方法只是附加到类的字典中的键值对。当您从基类派生类时,实质上是说方法名称将首先查看派生类字典,然后查看基类字典。为了“重写”一个方法,您只需在派生类中重新声明该方法即可。

那么,如果更改派生类中重写方法的签名会怎样?如果调用是在派生实例上,则一切正常,但如果在基实例上进行调用,则会收到错误,因为基类对同一方法名称使用不同的签名。

然而,在常见的情况下,您希望派生类方法具有附加参数,并且您希望方法调用在基类上也不会出现错误。这称为“里氏替换原则”(或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

In Python, methods are just key-value pairs in the dictionary attached to the class. When you are deriving a class from a base class, you are essentially saying that method name will be looked into first derived class dictionary and then in the base class dictionary. In order to "override" a method, you simply re-declare the method in the derived class.

So, what if you change the signature of the overridden method in the derived class? Everything works correctly if the call is on the derived instance but if you make the call on the base instance, you will get an error because the base class uses a different signature for that same method name.

There are however frequent scenarios where you want derived class method have additional parameters and you want method call work without error on base as well. This is called "Liskov substitution principle" (or LSP) which guarantees that if person switches from base to derived instance or vice versa, they don't have to revamp their code. To do this in Python, you need to design your base class with the following technique:

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

Above will print:

    Hello Alice
    Hello Bob
    Hello Rick
    Your age is  None
    Hello John
    Your age is  30

.
Play with this code

祁梦 2024-11-15 00:33:50

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

Python will allow this, but if method1() is intended to be executed from external code then you may want to reconsider this, as it violates LSP and so won't always work properly.

江南烟雨〆相思醉 2024-11-15 00:33:50

如果可以使用默认参数,你可以这样做:

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

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
故人爱我别走 2024-11-15 00:33:50

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

In python, all class methods are "virtual" (in terms of C++). So, in the case of your code, if you'd like to call method1() in super class, it has to be:

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

And the method signature does matter. You can't call a method like this:

sub = Sub()
sub.method1() 
梦旅人picnic 2024-11-15 00:33:50

它会起作用:

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

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

However, this isn't generally recommended. Take a look at S.Lott's answer: Methods with the same name and different arguments are a code smell.

横笛休吹塞上声 2024-11-15 00:33:50

签名重要吗?

不,它不会,但当重写的方法与基本方法不匹配时,PyCharm 会生成警告。
方法“Baz.method1()”的签名与类“Foo”中基方法的签名不匹配

您可以通过添加 typing.Optional 作为键入提示。如果所有参数均为 None,您还可以使用 locals() 调用基本方法。

from typing import Any, Optional
from inspect import signature


class Foo(object):
    def __init__(self):
        pass

    def method1(self):
        print('foo')


class Baz(Foo):
    def __init__(self):
        super(Foo, self).__init__()

    def method1(self, param1: Optional[str] = None, param2: Optional[Any] = None, param3: Optional[Any] = None):
        parameters, copy_of_locals = signature(self.method1).parameters, locals().copy()
        if not next((param for param in parameters if copy_of_locals[param] is not None), None):
            super().method1()
            return
        print('baz')

Output:

baz = Baz()
baz.method1()
>>> 'foo'
baz.method1(param1 = 'dcdb')
>>> 'baz'

does signature matter?

No it doesn't but PyCharm will generate a warning when the overridden method doesn't match the base method.
Signature of method 'Baz.method1()' does not match signature of the base method in class 'Foo'

You can get around this warning by adding typing.Optional as a type hint. You can also use locals() to call the base method if all the parameters are None.

from typing import Any, Optional
from inspect import signature


class Foo(object):
    def __init__(self):
        pass

    def method1(self):
        print('foo')


class Baz(Foo):
    def __init__(self):
        super(Foo, self).__init__()

    def method1(self, param1: Optional[str] = None, param2: Optional[Any] = None, param3: Optional[Any] = None):
        parameters, copy_of_locals = signature(self.method1).parameters, locals().copy()
        if not next((param for param in parameters if copy_of_locals[param] is not None), None):
            super().method1()
            return
        print('baz')

Output:

baz = Baz()
baz.method1()
>>> 'foo'
baz.method1(param1 = 'dcdb')
>>> 'baz'
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文