Я часто перезаписываю методы родительского класса и никогда не могу решить, должен ли я явно перечислить заданные параметры или просто использовать общую конструкцию *args, **kwargs. Одна версия лучше другой? Есть ли лучшая практика? Какие (дис) преимущества я упускаю?

class Parent(object):

    def save(self, commit=True):
        # ...

class Explicit(Parent):

    def save(self, commit=True):
        super(Explicit, self).save(commit=commit)
        # more logic

class Blanket(Parent):

    def save(self, *args, **kwargs):
        super(Blanket, self).save(*args, **kwargs)
        # more logic

Воспринимаемые преимущества явного варианта

  • Более явный (Zen of Python)
  • легче понять
  • легко доступны параметры функции

Воспринимаемые преимущества бланкетного варианта

  • более сухой
  • родительский класс легко взаимозаменяемы
  • изменение значений по умолчанию в родительском методе распространяется, не затрагивая другой код
54
Maik Hoepfel 31 Янв 2013 в 16:52

6 ответов

Лучший ответ

Принцип замещения Лискова

Обычно вы не хотите, чтобы сигнатура вашего метода изменялась в производных типах. Это может вызвать проблемы, если вы хотите поменять местами использование производных типов. Это часто называют принципом замены Лискова.

Преимущества явных подписей

В то же время я не считаю правильным, чтобы все ваши методы имели подпись *args, **kwargs. Явные подписи:

  • помогите документировать метод с помощью правильных имен аргументов
  • помогите документировать метод, указав, какие аргументы требуются, а какие имеют значения по умолчанию
  • обеспечить неявную проверку (отсутствующие обязательные аргументы выдают очевидные исключения)

Аргументы и сцепление переменной длины

Не путайте аргументы переменной длины с хорошей практикой связывания. Должно быть определенное количество связей между родительским классом и производными классами, иначе они не будут связаны друг с другом. Для связанного кода является нормальным приводить к соединению, которое отражает уровень связности.

Места для использования аргументов переменной длины

Использование аргументов переменной длины не должно быть вашим первым вариантом. Его следует использовать, когда у вас есть веская причина, например:

  • Определение оболочки функции (то есть декоратора).
  • Определение параметрической полиморфной функции.
  • Когда аргументы, которые вы можете принять, действительно полностью переменны (например, обобщенная функция соединения с БД). Функции подключения к БД обычно принимают строку подключения во многих различных формах, как в форме одного аргумента, так и в форме нескольких аргументов. Существуют также разные наборы опций для разных баз данных.
  • ...

Ты что-то делаешь неправильно?

Если вы обнаружите, что вы часто создаете методы, которые принимают много аргументов или производные методы с разными сигнатурами, у вас может возникнуть большая проблема в организации вашего кода.

35
Peter Duniho 13 Апр 2015 в 03:21

Я предпочитаю явные аргументы, потому что автозаполнение позволяет вам видеть сигнатуру метода функции во время вызова функции.

2
GeneralBecos 7 Фев 2013 в 19:16

Мой выбор будет:

class Child(Parent):

    def save(self, commit=True, **kwargs):
        super(Child, self).save(commit, **kwargs)
        # more logic

Он избегает доступа к аргументу commit из *args и **kwargs и обеспечивает безопасность при изменении сигнатуры Parent:save (например, добавление нового аргумента по умолчанию).

Обновление . В этом случае наличие аргументов * может вызвать проблемы, если к родительскому элементу добавлен новый позиционный аргумент. Я бы оставил только **kwargs и управлял бы только новыми аргументами со значениями по умолчанию. Это позволит избежать ошибок для распространения.

16
luc 9 Июл 2013 в 09:53

Если вы уверены, что Child сохранит подпись, безусловно, предпочтителен явный подход, но когда Child изменит подпись, я лично предпочитаю использовать оба подхода:

class Parent(object):
    def do_stuff(self, a, b):
        # some logic

class Child(Parent):
    def do_stuff(self, c, *args, **kwargs):
        super(Child, self).do_stuff(*args, **kwargs)
        # some logic with c

Таким образом, изменения в подписи вполне читабельны в Child, в то время как оригинальная подпись вполне читаема в Parent.

На мой взгляд, это также лучший способ, когда у вас есть множественное наследование, потому что вызов super несколько раз довольно отвратительно, когда у вас нет аргументов и kwargs.

Для чего бы это ни стоило, это также предпочтительный способ в довольно многих библиотеках и фреймворках Python (Django, Tornado, Requests, Markdown и многие другие). Хотя не следует основывать свой выбор на таких вещах, я просто подразумеваю, что этот подход довольно широко распространен.

4
dmg 6 Фев 2013 в 13:48

Не совсем ответ, но скорее примечание: если вы действительно, действительно хотите убедиться, что значения по умолчанию для родительского класса распространены на дочерние классы, вы можете сделать что-то вроде:

class Parent(object):

    default_save_commit=True
    def save(self, commit=default_save_commit):
        # ...

class Derived(Parent):

    def save(self, commit=Parent.default_save_commit):
        super(Derived, self).save(commit=commit)

Однако я должен признать, что это выглядит довольно некрасиво, и я бы использовал его, только если чувствую, что мне это действительно нужно.

3
Clemens Klein-Robbenhaar 10 Фев 2013 в 13:11

В дополнение к другим ответам:

Наличие переменных аргументов может «отделить» родителя от потомка, но создает связь между созданным объектом и родителем, что, я думаю, хуже, потому что теперь вы создали «длинную дистанцию» (сложнее определить, сложнее поддерживать, потому что вы можете создать несколько объектов в вашем приложении)

Если вы ищете разделение, взгляните на композицию над наследованием

0
JACH 26 Окт 2018 в 15:36