Когда используется ключевое слово super в python
Перейти к содержимому

Когда используется ключевое слово super в python

  • автор:

Python super guide

Этот класс имеет все те же возможности, что и его родитель, dict , но он расширяет метод __setitem__ , чтобы создавать записи в журнале при каждом обновлении ключа. После внесения записи в журнал метод использует super() для делегирования работы по фактическому обновлению словаря с помощью пары ключ/значение.

Класс, которому делегируется работа указаывать по имени не нужно

Какой именно класс будет вызван зависит от того, как образовано дерево родительских классов. В примере ниже оно выглядит так: LoggingOD, LoggingDict, OrderedDict, dict, object — работа будет делегирована нужному нам методу в OrderedDict

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

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

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

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

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

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

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

Следующая проблема — как убедиться, что целевой метод существует. В примере выше мы знаем, что у object всегда есть __init__() и object — всегда последний в цепочке МРО, поэтому любая последовательность из super().__init__(**kwds) гарантированно завершается вызовом метода object.__init__() . Но так бывает далеко не всегда и object может не иметь необходимого нам метода. Для таких случаев требуется написать корневой класс, который гарантированно будет вызываться перед object и будет иметь нужный нам метод. Задача такого корневого класса — стать последним в цепочке вызовов, заменив собойс object .

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

Наконец, для гарантирантий выполнения всей цепочки, каждый делегирующий метод каждого класса должен реализовывать super() .

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

Подробно про метод super() в Python

Сегодня в этом руководстве мы обсудим метод super() в Python. Перед тем, как погрузиться в тему, мы настоятельно рекомендуем изучить руководство по наследованию Python.

метод super

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

Или просто он используется для вызова конструктора, то есть метода __init__() суперкласса.

В версиях Python 3.x мы можем использовать super без передачи двух вышеуказанных параметров. Посмотрите на приведенный ниже фрагмент кода.

Здесь C — производный класс, B — базовый класс, method — определяемая пользователем функция с аргументом arg .

Как видите, строка super().method(arg) super( C, self).method(arg) фактически эквивалентна super( C, self).method(arg) в Python 3.x. Это запрещено в Python 2.x. Следовательно, использовать super там сложно.

Использование super()

Рассмотрим приведенный ниже пример.

вывод

В приведенном выше примере классы, производные от базового класса Demo не были реализованы эффективно или надежно.

Производный класс Newdemo явно инициализирует значения полей A, B и C базового класса. Такое же дублирование кода обнаруживается при инициализации тех же полей в базовом классе, в том числе Demo .

Этот процесс неэффективен. Это означает, что подклассу должен быть предоставлен доступ к членам суперкласса.

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

Super() для вызова конструктора суперкласса

Теперь применим метод super() к приведенному выше примеру.

пример super

Здесь производный класс Newdemo вызывает super() с аргументами a, b и c. Это вызывает __init__ конструктора __init__ базового класса, т.е. Demo . Это инициализирует значения a, b и c. Следовательно, класс Newdemo больше не инициализирует сами значения.

Использование super в Python 2.x

Синтаксис для вызова конструктора super в Python 2.x приведен ниже.

Следовательно, нам нужно внести некоторые незначительные изменения в приведенный выше пример, если мы хотим использовать его в Python 2.

Во-первых, нам нужно поместить object в базовый класс, как показано ниже.

А во-вторых, пройти Newdemo и self на месте вызова суперкласса. Нравится.

Зачем нужен super()

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

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

Необычный Python в обычных библиотеках

Специалист в Data Science из Amazon буквально прочитал код самых распространённых библиотек Python. В этом материале он делится секретами работы с Python, о которых узнал из этих библиотек. За подробностями приглашаем под кат к старту нашего флагманского курса по Data Science:

Вызов super() в базовых классах

Функция super() в Python позволяет наследовать базовые классы (они же суперклассы или родительские классы) без необходимости явно ссылаться на базовый класс. Обычно метод super() используется в методе __init__ . Множественное наследование практически невозможно без super() , хотя оно может быть удобно при одиночном наследовании.

Одно из интересных применений super() — его вызов в классе базовом классе. Этот приём я заметил в requests.adapters, в BaseAdapter :

Базовый класс ни от чего не наследуется, так зачем же вызывать в нём super() ?

Немного покопавшись, я узнал вот что: в базовом классе ключевое слово super() позволяет реализовать совместное множественное наследование. Без него вызовы __init__ родительских классов — после класса без super — пропускаются. Ниже — пример с базовым классом BaseEstimator и миксином ServingMixin , где DecisionTree унаследует оба класса.

Итак, у нас BaseEstimator , который в своём __init__ не вызывает super() . Для вывода атрибутов у него есть базовый метод __repr__ :

Затем мы наследуем BaseEstimator через подкласс DecisionTree . Всё работает нормально — при печати экземпляра DecisionTree отображаются атрибуты BaseEstimator и DecisionTree :

Теперь пронаследуемся от ServingMixin и создадим экземпляр DecisionTree :

Заметно, что ServingMixin не наследуется должным образом: атрибут ServingMixin ( mode ) не отображается, когда выводится экземпляр дерева решений, и, если мы пытаемся получить доступ к атрибуту mode , то окажется, что он не существует.

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

Исправить это можно вызовом super() в BaseEstimator , и DecisionTree заработает, как ожидалось:

Именно поэтому мы можем захотеть вызвать super() в базовом классе.

Когда использовать миксины?

Mixin — это класс, предоставляющий реализации методов для повторного использования дочерними классами. Он представляет ограниченную форму множественного наследования и родительский класс, который просто даёт функциональные возможности подклассам, не содержит состояния и не предназначен для создания экземпляров. Scikit-learn широко использует миксины. Это ClassifierMixin , TransformerMixin , OutlierMixin и т. д.

Когда использовать миксины? Они подходят, когда хочется:

  • предоставить множество дополнительных функций для класса;
  • использовать определённую функцию во множестве разных классов.

Если нужно добавить поддержку заголовка accept , перепишем код вот так:

Нужна поддержка пользовательского агента, аутентификации и т. д.? Нет проблем, просто добавьте миксины:

Благодаря модуляризации этих функций в виде миксинов (а не добавления в класс) базовый класс не раздувается функциями, которые могут использовать только несколько подклассов. Кроме того, эти миксины теперь могут повторно использоваться другими дочерними классами, которые могут не наследоваться от BaseRequest .

Использование относительного импорта

Относительный импорт гарантирует, что мы ищем текущий пакет (и импортируем из него) перед поиском в остальной части PYTHONPATH . Он работает, если перед импортом поставить . — вот пример из base.py от Sklearn:

Что произойдёт, если base.py не использует относительный импорт? Если у нас есть пакет с именем utils в каталоге нашего скрипта, во время импорта Python будет искать наш пакет utils , а не utils от sklearn, тем самым нарушая работу sklearn. Точка в выражении импорта гарантирует, что base.py в sklearn сначала ищет utils от Sklearn.

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

Когда добавлять код в __init__.py

__init__.py помечает каталоги как каталоги пакетов Python. Обычная практика — оставлять __init__.py пустыми. Тем не менее во многих библиотеках, которые я читал, были непустые, а иногда длинные файлы __init__.py . Это заставило меня задуматься, что и почему можно добавить в __init__.py .

Во-первых, мы можем добавить в __init__.py импорт, когда хотим реорганизовать код, который вырос в несколько модулей, без критических изменений для существующих пользователей. Допустим, у нас есть один модуль ( models.py ), который содержит реализацию для DecisionTree и Bandit . Со временем этот единственный модуль превращается в пакет моделей с модулями для tree и bandit . Чтобы обеспечить согласованность API для существующих пользователей, в __init__.py в пакете моделей мы можем добавить следующее:

Это гарантирует, что существующие пользователи смогут продолжить импорт через from models import DecisionTree , а не from models.tree import DecisionTree . Для них API не меняется, а существующий код не ломается.

Это подводит к ещё одной причине добавить код в __init__.py , а именно — предоставить упрощённый API, чтобы пользователям не приходилось вникать в детали реализации:

Вместо того чтобы заставлять пользователей решать, что импортировать из model_implementation и data_implementation , мы можем всё упростить, добавив в __init__.py такой код:

В нём говорится, что SimpleModel и SimpleDataLoader — единственные части приложения, с которыми должны работать пользователи, и это упрощает использование пакета приложения (например, from app import SimpleModel, SimpleDataLoader ). И, если пользователи знают, что делают, и хотят импортировать напрямую из model_implementation , то это тоже возможно.

Так делается в Pandas, где __init__.py импортируются типы данных, считыватели (reader) и API изменения формы, а ещё так делается в Accelerate от Hugging Face.

Помимо упомянутого выше применения, мы также можем захотеть:

    в __init__.py основного пакета, чтобы использовать его в нескольких модулях;
  • выполнить проверки совместимости.
    нужен __init__.py ? в __init__.py ? в __init__.py ?

Когда использовать экземпляр, класс и статические методы

Краткий обзор методов, которые можно реализовать для класса. Такие методы пригодятся, когда:

  • Методам экземпляра нужен экземпляр класса, и доступ к нему они могут получить через self .
  • Методам класса не нужен экземпляр. Таким образом, они не могут получить доступ к экземпляру ( self ), но им доступен к класс cls
  • Статические методы не имеют доступа к self или cls . Они работают как обычные функции, но относятся к пространству имён классов.

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

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

Скрытая функция conftest.py

Обычно conftest.py используется для предоставления фикстур для всего каталога. После определения фикстуры в conftest.py , они могут использоваться любым тестом в пакете без необходимости импортировать их. Этот файл также используется для загрузки внешних плагинов и определения хуков, как setup и teardown.

Но, просматривая sklearn, я наткнулся на пустой conftest.py с этим интересным комментарием:

Оказывается, sklearn использовал менее известную функцию conftest.py : его существование в корневом пути гарантирует, что pytest распознает модули без необходимости указывать PYTHONPATH . В фоновом режиме pytest изменяет sys.path , включая все найденные в корневом пути подмодули.

Поясняющая документация библиотек

Помимо чтения кода, мы можем учиться, читая документы, объясняющие библиотеку. Давайте сосредоточимся на принципах проектирования библиотек.

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

Другой пример — fastai, где используется многоуровневый подход. Фреймворк предоставляет высокоуровневый API, готовые к использованию функции обучения моделей для различных приложений. Высокоуровневый API построен на основе иерархии низкоуровневых API, последние предоставляют компонуемые стандартные блоки. Этот многоуровневый подход позволяет через настройку API-интерфейсов среднего уровня быстро создать прототип перед его кастомизацией.

Изображение

  • предоставление прагматичной производительности; .

Вот некоторые необычные способы работы с Python, о которых я узнал, читая эти билиотеки:

А мы поможем прокачать ваши навыки или с самого начала освоить профессию, востребованную в любое время:

Объясните как работает метод super,а так-же конструкция super().__init__() в python? [закрыт]

Хотите улучшить этот вопрос? Переформулируйте вопрос так, чтобы он был сосредоточен только на одной проблеме.

Закрыт 4 года назад .

Объясните как работает метод super , а так-же конструкция super().__init__() в python? super часто используется в классах. Желательно объяснить как можно проще и на простых примерах.

Leon's user avatar

super() — это функция, которая обращается к классу, от которого наследуется текущий.

Сначала немного кода:

Как Вы можете заметить, мы перегрузили метод родительского класса. Но что, если нам потребуется всего лишь немного дополнить его? Как нам дополнить родительский метод, не копируя его полностью и не изобретая велосипедов? Тут нам и нужен super() :

С помощью super().some_method() мы вызвали родительский метод, а после дополнили свой. Именно для этого чаще всего используется эта функция.

__init__() — это метод инициализации класса, следовательно super().__init__() вызывает метод инициализации из родительского класса. Например, чтобы дополнить его.

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

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