Python super()

Почему это так противоречиво?

Учитывая то, что я только что сказал, множественное наследование кажется благословением. Когда объект может наследоваться от нескольких родителей, мы можем легко распределять обязанности между различными классами и использовать только те, которые нам нужны, способствуя повторному использованию кода и избегая «божественных» объектов.

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

Однако в множественном наследовании существует более насущная проблема. Как это происходит с естественным наследованием, родители могут предоставить одну и ту же «генетическую черту» в двух разных вариантах, но у получающегося в результате этого человека будет только одна. Оставляя в стороне генетику (которая невероятно сложнее, чем программирование) и возвращаясь к ООП, мы сталкиваемся с проблемой, когда объект наследуется от двух других объектов, которые предоставляют тот же атрибут.

Итак, если ваш класс Child наследует от родителей Parent1 и Parent2, и оба предоставляют метод __init__, какой из них следует использовать вашему объекту?

class Parent1():
    def __init__(self):
        


class Parent2():
    def __init__(self):
        


class Child(Parent1, Parent2):
    # наследуется Parent1 и Parent2, какой __init__ будет использоваться?
    pass


Ситуация может ухудшиться, так как родители могут иметь разные входные параметры общего метода, например

class Parent1:
    def __init__(self, status):
        


class Parent2:
    def __init__(self, name):
        


class Child(Parent1, Parent2):
    # какой __init__ используется?
    pass


Проблема может быть расширена еще дальше, введя общего предка выше Parent1 и Parent2.

class Ancestor:
    def __init__(self):
        


class Parent1(Ancestor):
    def __init__(self, status):
        


class Parent2(Ancestor):
    def __init__(self, name):
        


class Child(Parent1, Parent2):
    pass


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

д.), Важной частью является то, что он является общим для Parent1 и Parent2. Это так называемая проблема алмаза, так как граф наследования имеет форму ромба

Таким образом, в то время как при наследовании с одним родителем правила просты, с множественным наследованием у нас сразу возникает более сложная ситуация, в которой нет тривиального решения. Помогает ли все это реализовать множественное наследование?

Как мы вскоре увидим, есть решения этой проблемы, но этот дополнительный уровень сложности делает множественное наследование чем-то, что не очень легко вписывается в дизайн. Помните, что наследование — это механизм автоматического делегирования. По этим причинам множественное наследование часто изображается как пугающее и запутанное, и, как правило, ему уделяется время только в продвинутых курсах ООП, по крайней мере, в мире Python. Я считаю, что каждый программист на Python должен ознакомиться с ним и узнать, как им воспользоваться.

Иллюстрация кооперативного множественного наследования с помощью super()

Если хоть один наследник нарушает принципы кооперативного наследования (не
вызывает ), то метод родителя вообще не будет вызван, хотя
вроде бы мы имеем явный вызов этого родителя из другого наследника.

Например, давайте закомментарим вызов в классе (строка 8).
Вывод изменится следующим образом:

Причина в том, что из вызывается следующий по MRO класс
(), а вовсе не родитель ().
Родителя ранее вызывал , но мы убрали этот вызов.

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

Ошибка показывает, что в код пытается
вызвать .

Примечание — особенности работы super()

Одним из ограничений является то, что не получится выполнить операции
(,

и т.д.)
над возвращенным объектом, даже если эти операции реализованы в родителе
вызывающего класса с помощью
“магических методов”.

Если выполнить операцию над экземпляром класса, то Python найдет
нужный для выполнения операции “магический метод” в родителе (в примере ниже —
для индексирования с помощью оператора ).

Но если попытаться выполнить операцию над объектом, возвращаемым ,
получим ошибку:

Опубликовано September 20, 2018

Функция super()

Функция super() обеспечивает так называемое “кооперативное” наследование методов.
Если во всех переопределенных методах использовать эту функцию, то она обеспечит
вызов методов всех классов по алгоритму MRO.

это не класс-родитель, это объект, позволяющий вызвать следующий по
алгоритму MRO класс.

Название вводит в заблуждение — как показано ниже,
вполне может найти метод не в родителе, а в “брате”, если тот следует далее
по алгоритму MRO.

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

Вполне может быть, что нам этого и
хотелось бы (скажем, мы добавили класс, который переопределяет что-то в ,
и хотим чтобы это сказалось на всех классах в дереве наследования).
Но в каких-то ситуациях это может оказаться неприятной и трудно обнаружимой
проблемой.

Контроль доступа к атрибутам

Вы можете определить поведение для случая, когда пользователь пытается обратиться к атрибуту, который не существует (совсем или пока ещё). Это может быть полезным для перехвата и перенаправления частых опечаток, предупреждения об использовании устаревших атрибутов (вы можете всё-равно вычислить и вернуть этот атрибут, если хотите), или хитро возвращать , когда это вам нужно. Правда, этот метод вызывается только когда пытаются получить доступ к несуществующему атрибуту, поэтому это не очень хорошее решение для инкапсуляции.

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

Это то же, что и , но для удаления атрибутов, вместо установки значений

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

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

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

Переопределение операторов на произвольных классах

имели в виду

Магические методы сравнения

  • Самый базовый из методов сравнения. Он, в действительности, определяет поведение для всех операторов сравнения (>, ==, !=, итд.), но не всегда так, как вам это нужно (например, если эквивалентность двух экземпляров определяется по одному критерию, а то что один больше другого по какому-нибудь другому). должен вернуть отрицательное число, если , ноль, если , и положительное число в случае . Но, обычно, лучше определить каждое сравнение, которое вам нужно, чем определять их всех в . Но может быть хорошим способом избежать повторений и увеличить ясность, когда все необходимые сравнения оперерируют одним критерием.

  • Определяет поведение оператора равенства, .

  • Определяет поведение оператора неравенства, .

  • Определяет поведение оператора меньше, .

  • Определяет поведение оператора больше, .

  • Определяет поведение оператора меньше или равно, .

  • Определяет поведение оператора больше или равно, .

Унарные операторы и функции

  • Определяет поведение для унарного плюса ()

  • Определяет поведение для отрицания()

  • Определяет поведение для встроенной функции .

  • Определяет поведение для инвертирования оператором . Для объяснения что он делает смотри .

  • Определяет поведение для встроенной функции . это число знаков после запятой, до которого округлить.

  • Определяет поведение для , то есть, округления до ближайшего меньшего целого.

  • Определяет поведение для , то есть, округления до ближайшего большего целого.

  • Определяет поведение для , то есть, обрезания до целого.

Обычные арифметические операторы

  • Сложение.

  • Вычитание.

  • Умножение.

  • Целочисленное деление, оператор .

  • Деление, оператор .

  • Правильное деление. Заметьте, что это работает только когда используется .
  • Остаток от деления, оператор .

  • Определяет поведение для встроенной функции .

  • Возведение в степень, оператор .

  • Двоичный сдвиг влево, оператор .

  • Двоичный сдвиг вправо, оператор .

  • Двоичное И, оператор .

  • Двоичное ИЛИ, оператор .

  • Двоичный xor, оператор .

Отражённые арифметические операторы

  • Отражённое сложение.

  • Отражённое вычитание.

  • Отражённое умножение.

  • Отражённое целочисленное деление, оператор .

  • Отражённое деление, оператор .

  • Отражённое правильное деление. Заметьте, что работает только когда используется .

  • Отражённый остаток от деления, оператор .

  • Определяет поведение для встроенной функции , когда вызывается .

  • Отражённое возведение в степерь, оператор .

  • Отражённый двоичный сдвиг влево, оператор .

  • Отражённый двоичный сдвиг вправо, оператор .

  • Отражённое двоичное И, оператор .

  • Отражённое двоичное ИЛИ, оператор .

  • Отражённый двоичный xor, оператор .

Составное присваивание

  • Сложение с присваиванием.

  • Вычитание с присваиванием.

  • Умножение с присваиванием.

  • Целочисленное деление с присваиванием, оператор .

  • Деление с присваиванием, оператор .

  • Правильное деление с присваиванием. Заметьте, что работает только если используется .
  • Остаток от деления с присваиванием, оператор .

  • Возведение в степерь с присваиванием, оператор .

  • Двоичный сдвиг влево с присваиванием, оператор .

  • Двоичный сдвиг вправо с присваиванием, оператор .

  • Двоичное И с присваиванием, оператор .

  • Двоичное ИЛИ с присваиванием, оператор .

  • Двоичный xor с присваиванием, оператор .

Магические методы преобразования типов

  • Преобразование типа в int.

  • Преобразование типа в long.

  • Преобразование типа в float.

  • Преобразование типа в комплексное число.

  • Преобразование типа в восьмеричное число.

  • Преобразование типа в шестнадцатиричное число.

  • Преобразование типа к int, когда объект используется в срезах (выражения вида ). Если вы определяете свой числовый тип, который может использоваться как индекс списка, вы должны определить .

  • Вызывается при . Должен вернуть своё значение, обрезанное до целочисленного типа (обычно long).

  • Метод для реализации арифметики с операндами разных типов. должен вернуть если преобразование типов невозможно. Если преобразование возможно, он должен вернуть пару (кортеж из 2-х элементов) из и , преобразованные к одному типу.

Критика других ответов:

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

Я объясню здесь.

И создайте зависимость, которую мы хотим вызвать после Ребенка:

Теперь помните, использует super, не делает:

И не вызывает метод UserDependency:

Но , потому что использует , do!:

Критика для другого ответа

Ни в коем случае не следует делать следующее, на что указывает другой ответ, поскольку вы обязательно получите ошибки, когда вы подклассифицируете ChildB:

Объяснение: Этот ответ предложил назвать супер следующим образом:

Это совершенно неправильно. позволяет нам искать следующего родителя в MRO (см. первый раздел этого ответа) для дочерних классов. Если вы сообщите , что мы находимся в методе дочернего экземпляра, он будет искать следующий метод в строке (возможно, этот), что приведет к рекурсии, что, вероятно, приведет к логическому сбою (например, в примере с ответчиком) или к при превышении глубины рекурсии.

What Can super() Do for You?#

So what can do for you in single inheritance?

Like in other object-oriented languages, it allows you to call methods of the superclass in your subclass. The primary use case of this is to extend the functionality of the inherited method.

In the example below, you will create a class that inherits from and extends the functionality of (inherited from the class through ) to calculate the surface area and volume of a instance:

Now that you’ve built the classes, let’s look at the surface area and volume of a cube with a side length of :

>>>

Caution: Note that in our example above, alone won’t make the method calls for you: you have to call the method on the proxy object itself.

Here you have implemented two methods for the class: and . Both of these calculations rely on calculating the area of a single face, so rather than reimplementing the area calculation, you use to extend the area calculation.

Also notice that the class definition does not have an . Because inherits from and doesn’t really do anything differently for than it already does for , you can skip defining it, and the of the superclass () will be called automatically.

returns a delegate object to a parent class, so you call the method you want directly on it: .

super() in Single Inheritance#

If you’re unfamiliar with object-oriented programming concepts, inheritance might be an unfamiliar term. Inheritance is a concept in object-oriented programming in which a class derives (or inherits) attributes and behaviors from another class without needing to implement them again.

For me at least, it’s easier to understand these concepts when looking at code, so let’s write classes describing some shapes:

Here, there are two similar classes: and .

You can use them as below:

>>>

In this example, you have two shapes that are related to each other: a square is a special kind of rectangle. The code, however, doesn’t reflect that relationship and thus has code that is essentially repeated.

By using inheritance, you can reduce the amount of code you write while simultaneously reflecting the real-world relationship between rectangles and squares:

Here, you’ve used to call the of the class, allowing you to use it in the class without repeating code. Below, the core functionality remains after making changes:

>>>

In this example, is the superclass, and is the subclass.

Because the and methods are so similar, you can simply call the superclass’s method () from that of by using . This sets the and attributes even though you just had to supply a single parameter to the constructor.

When you run this, even though your class doesn’t explicitly implement it, the call to will use the method in the superclass and print . The class inherited from the class.

Note: To learn more about inheritance and object-oriented concepts in Python, be sure to check out Inheritance and Composition: A Python OOP Guide and Object-Oriented Programming (OOP) in Python 3.

Использование модуля pickle на своих объектах

протокол

Сериализация собственных объектов.

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

  • Для классов нового стиля вы можете определить, какие параметры будут переданы в во время десериализации. Этот метод так же должен вернуть кортеж аргументов, которые будут отправлены в .

  • Вместо стандартного атрибута , где хранятся атрибуты класса, вы можете вернуть произвольные данные для сериализации. Эти данные будут переданы в во время десериализации.

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

  • Если вы определили свой тип (с помощью Python’s C API), вы должны сообщить Питону как его сериализовать, если вы хотите, чтобы он его сериализовал. вызывается когда сериализуется объект, в котором этот метод был определён. Он должен вернуть или строку, содержащую имя глобальной переменной, содержимое которой сериализуется как обычно, или кортеж. Кортеж может содержать от 2 до 5 элементов: вызываемый объект, который будет вызван, чтобы создать десериализованный объект, кортеж аргументов для этого вызываемого объекта, данные, которые будут переданы в (опционально), итератор списка элементов для сериализации (опционально) и итератор словаря элементов для сериализации (опционально).

  • Иногда полезно знать версию протокола, реализуя . И этого можно добиться, реализовав вместо него . Если реализован, то предпочтение при вызове отдаётся ему (вы всё-равно должны реализовать для обратной совместимости).

A super() Deep Dive#

Before heading into multiple inheritance, let’s take a quick detour into the mechanics of .

While the examples above (and below) call without any parameters, can also take two parameters: the first is the subclass, and the second parameter is an object that is an instance of that subclass.

First, let’s see two examples showing what manipulating the first variable can do, using the classes already shown:

In Python 3, the call is equivalent to the parameterless call. The first parameter refers to the subclass , while the second parameter refers to a object which, in this case, is . You can call with other classes as well:

In this example, you are setting as the subclass argument to , instead of . This causes to start searching for a matching method (in this case, ) at one level above in the instance hierarchy, in this case .

In this specific example, the behavior doesn’t change. But imagine that also implemented an function that you wanted to make sure did not use. Calling in this way allows you to do that.

Caution: While we are doing a lot of fiddling with the parameters to in order to explore how it works under the hood, I’d caution against doing this regularly.

The parameterless call to is recommended and sufficient for most use cases, and needing to change the search hierarchy regularly could be indicative of a larger design issue.

What about the second parameter? Remember, this is an object that is an instance of the class used as the first parameter. For an example, must return .

By including an instantiated object, returns a bound method: a method that is bound to the object, which gives the method the object’s context such as any instance attributes. If this parameter is not included, the method returned is just a function, unassociated with an object’s context.

For more information about bound methods, unbound methods, and functions, read the Python documentation on its descriptor system.

Note: Technically, doesn’t return a method. It returns a proxy object. This is an object that delegates calls to the correct class methods without making an additional object in order to do so.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *