Заміна методу Python, чи має значення підпис?


78

Скажімо, я маю

class Super():
  def method1():
    pass

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

Це правильно? Чи завжди дзвінки до method1 надходять у підклас? Я планую мати 2 підкласи, кожен із яких замінює метод1 з різними параметрами

Відповіді:


63

У 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

Вгорі буде надруковано:

    Привіт Алісо
    Привіт Боб
    Привіт Рік
    Ваш вік - Ні
    Привіт Джон
    Ваш вік - 30 років
. Пограйте з цим кодом


1
Дякуємо за це оновлення через кілька років із набагато чіткішим та ефективнішим обговоренням та робочим прикладом та манежем!
nealmcb

1
ми повинні помістити вік у підпис привіт після *args? Якщо ні, код типу, d.hello("John", "blue", age=30)не буде працювати. Я маю на увазі, загалом позиційні аргументи завжди слід визначати до кваргів
Quickbeam2k1

1
Я думаю, що відповідь на ваше запитання залежить від того, чи хочемо ми дозволити параметр із параметром за замовчуванням ( ageтут) встановлювати також за позицією або лише як kwarg. Зверніть увагу, що ваша пропозиція працюватиме лише в Python 3, де ви можете вказати аргументи лише для ключових слів *. Див., Наприклад, це .
Nerxis

2
Хороша відповідь, але в "LSP, який гарантує, що якщо людина переходить з базового на похідний екземпляр або навпаки", частина "навпаки" є неправильною.
Michał Jabłoński

Так, LSP не примушує екземпляри підкласу бути розширюваними до батьківського класу.
ruohola

44

Python це дозволить, але якщо method1()він призначений для виконання із зовнішнього коду, можливо, ви захочете переглянути його, оскільки він порушує LSP і тому не завжди буде працювати належним чином.


1
Чи є порушення LSP тим фактом, що Sub.method1 бере 3 аргументи, тоді як Super.method1 не бере жодного, роблячи їх фактично різними інтерфейсами?
розблокувати

2
@Unode: Правильно. Це можна вирішити, якщо аргументи методу підкласу мають усі значення за замовчуванням, але тоді ви дізнаєтесь, які значення за замовчуванням будуть доречними.
Ігнасіо Васкес-Абрамс,

3
Розумію. Але тоді просто для уточнення. Якщо батьківський метод1 був визначений, оскільки Super.method1(param1=None, param2=None, param3=None)він все одно порушував би LSP, якщо в підкласах він визначений як Sub.method1(param1, param2, param3)правильний? Оскільки атрибути є обов’язковими в одному випадку, але не в іншому. Отже, наскільки я розумію, не змінюючи інтерфейс підкласу, єдиним способом не порушити LSP було б наявність параметрів без значень за замовчуванням на батьківському. Чи я правильно з цим чи надто інтерпретую LSP?
unode

3
@Unode: Також правильно. Коли контракт стає менш обмежувальним у підкласі, це порушує LSP.
Ігнасіо Васкес-Абрамс

крім випадків , коли method1є __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

Однак це зазвичай не рекомендується. Погляньте на відповідь С.Лотта : Методи з однаковою назвою та різними аргументами є запахом коду .


-2

Так. Виклики "method1" завжди надходитимуть до підкласу. Підпис методу в Python складається лише з імені, а не зі списку аргументів.


2
Неправдива відповідь. Підпис методу завжди має значення, оскільки немає перевантаження функції, як це трапляється C / C ++.
Заур Насібов

Я маю на увазі, що Python не розглядає список аргументів, щоб вирішити, який метод викликати, але, мабуть, ви маєте рацію, дивлячись на нього з цього боку!
Elektito,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.