Меня смущают внутренние / вложенные классы Python. Есть ли что-то, чего нельзя добиться без них? Если да, то что это?
98
Меня смущают внутренние / вложенные классы Python. Есть ли что-то, чего нельзя добиться без них? Если да, то что это?
Цитируется по http://www.geekinterview.com/question_details/64739 :
Преимущества внутреннего класса:
- Логическая группировка классов : если класс полезен только для одного другого класса, то логично встроить его в этот класс и сохранить их вместе. Вложение таких «вспомогательных классов» делает их пакет более упрощенным.
- Повышенная инкапсуляция : рассмотрим два класса верхнего уровня A и B, где B требуется доступ к членам A, которые в противном случае были бы объявлены частными. Скрывая класс B внутри класса, члены AA могут быть объявлены закрытыми, и B может получить к ним доступ. Кроме того, сам B можно скрыть от внешнего мира.
- Более читаемый, поддерживаемый код : вложение небольших классов в классы верхнего уровня помещает код ближе к тому месту, где он используется.
Главное преимущество - организованность. Все, что можно сделать с помощью внутренних классов, можно сделать и без них.
DataLoader
класс, который может генерироватьCacheMiss
исключение. Вложение исключения в основной классDataLoader.CacheMiss
означает, что вы можете просто импортировать,DataLoader
но по-прежнему использовать исключение.Нет. Они абсолютно эквивалентны определению класса обычно на верхнем уровне и последующему копированию ссылки на него во внешний класс.
Я не думаю, что есть какая-то особая причина, по которой вложенные классы «разрешены», кроме того, что нет особого смысла явно «запрещать» их.
Если вы ищете класс, который существует в жизненном цикле внешнего объекта / объекта-владельца и всегда имеет ссылку на экземпляр внешнего класса - внутренние классы, как это делает Java, - то вложенные классы Python - не то. Но можно взломать что-то вроде этого:
(Здесь используются декораторы классов, которые появились в Python 2.6 и 3.0. В противном случае вам пришлось бы сказать «Inner = innerclass (Inner)» после определения класса.)
источник
self
без какой-либо дополнительной работы (просто используйте другой идентификатор, где вы обычно помещаете внутреннийself
; напримерinnerself
), и вы сможете получить доступ к внешнему экземпляру через него.WeakKeyDictionary
в этом примере фактически не позволяет использовать сборку мусора для ключей, поскольку значения строго ссылаются на соответствующие ключи через ихowner
атрибут.Есть кое-что, что вам нужно подумать, чтобы понять это. В большинстве языков определения классов являются директивами для компилятора. То есть класс создается до того, как программа запускается. В Python все операторы являются исполняемыми. Это означает, что это утверждение:
это оператор, который выполняется во время выполнения, как этот:
Это означает, что вы можете не только создавать классы внутри других классов, вы можете создавать классы где угодно. Рассмотрим этот код:
Таким образом, идея «внутреннего класса» на самом деле не является языковой конструкцией; это конструкция программиста. Guido имеет очень хорошее резюме , как это произошло здесь . Но по сути, основная идея состоит в том, что это упрощает грамматику языка.
источник
Вложенность классов внутри классов:
Вложенные классы раздувают определение класса, что затрудняет понимание того, что происходит.
Вложенные классы могут создавать связи, которые затрудняют тестирование.
В Python вы можете поместить более одного класса в файл / модуль, в отличие от Java, поэтому класс по-прежнему остается близким к классу верхнего уровня и даже может иметь имя класса с префиксом "_", чтобы показать, что другие не должны быть используй это.
Место, где вложенные классы могут оказаться полезными, находится внутри функций.
Класс захватывает значения из функции, позволяя вам динамически создавать класс, подобный метапрограммированию шаблона в C ++.
источник
Я понимаю аргументы против вложенных классов, но в некоторых случаях их можно использовать. Представьте, что я создаю класс двусвязного списка, и мне нужно создать класс узла для обслуживания узлов. У меня есть два варианта: создать класс Node внутри класса DoublyLinkedList или создать класс Node вне класса DoublyLinkedList. В этом случае я предпочитаю первый вариант, потому что класс Node имеет смысл только внутри класса DoublyLinkedList. Хотя нет никакого преимущества скрытия / инкапсуляции, есть преимущество группировки, заключающееся в возможности сказать, что класс Node является частью класса DoublyLinkedList.
источник
Node
класс бесполезен для других типов классов связанных списков, которые вы также можете создать, и в этом случае он, вероятно, должен быть просто снаружи.Node
находится в пространстве именDoublyLinkedList
, и в этом есть логический смысл. Это является Pythonic: «Пространство имен один сигналит отличную идею - давайте больше тех!»Есть кое-что, без чего не обойтись : наследование связанных классов .
Вот минималистичный пример со связанными классами
A
иB
:Этот код приводит к вполне разумному и предсказуемому поведению:
Если бы
B
это был класс верхнего уровня, вы не могли бы писатьself.B()
в методе,make_B
а просто записали быB()
и, таким образом, потеряли бы динамическую привязку к адекватным классам.Обратите внимание, что в этой конструкции вы никогда не должны ссылаться на класс
A
в теле классаB
. Это мотивация для введенияparent
атрибута в классB
.Конечно, это динамическое связывание можно воссоздать без внутреннего класса за счет утомительной и подверженной ошибкам инструментовки классов.
источник
В основном я использую это для предотвращения распространения небольших модулей и предотвращения загрязнения пространства имен, когда отдельные модули не нужны. Если я расширяю существующий класс, но этот существующий класс должен ссылаться на другой подкласс, который всегда должен быть связан с ним. Например, у меня может быть
utils.py
модуль, в котором есть много вспомогательных классов, которые не обязательно связаны вместе, но я хочу усилить связь для некоторых из этих вспомогательных классов. Например, когда я реализую https://stackoverflow.com/a/8274307/2718295:
utils.py
:Тогда мы сможем:
Конечно, мы могли бы избегали наследования
json.JSONEnocder
вообще и просто переопределить по умолчанию ()::
Но иногда просто по соглашению вы хотите,
utils
чтобы он состоял из классов для расширяемости.Вот еще один вариант использования: мне нужна фабрика изменяемых в моем OuterClass без необходимости вызывать
copy
:Я предпочитаю этот шаблон
@staticmethod
декоратору, который вы в противном случае использовали бы для фабричной функции.источник
1. Два функционально эквивалентных способа
Два показанных выше способа функционально идентичны. Однако есть некоторые тонкие различия, и бывают ситуации, когда вы хотите выбрать одно из них.
Способ 1: определение вложенного класса
(= «Вложенный класс»)
Способ 2: с внутренним классом на уровне модуля, присоединенным к внешнему классу
(= "Внутренний класс, на который имеется ссылка")
Подчеркивание используется, чтобы следовать PEP8: «внутренние интерфейсы (пакеты, модули, классы, функции, атрибуты или другие имена) должны - иметь префикс с одним ведущим подчеркиванием».
2. Сходства
Ниже приведен фрагмент кода, демонстрирующий функциональное сходство «Вложенного класса» и «Ссылочного внутреннего класса»; Они будут вести себя таким же образом при проверке кода для типа экземпляра внутреннего класса. Излишне говорить, что они
m.inner.anymethod()
будут вести себя аналогичным образом сm1
иm2
3. Различия
Различия между «вложенным классом» и «внутренним классом, на который имеется ссылка» перечислены ниже. Они не большие, но иногда хочется выбрать то или иное на их основе.
3.1 Инкапсуляция кода
С «Вложенными классами» можно инкапсулировать код лучше, чем с «Внутренним классом, на который есть ссылка». Класс в пространстве имен модуля - это глобальная переменная. Цель вложенных классов - уменьшить беспорядок в модуле и поместить внутренний класс внутрь внешнего класса.
Пока никто * не использует
from packagename import *
, небольшое количество переменных уровня модуля может быть приятным, например, при использовании IDE с автозавершением кода / intellisense.* Верно?
3.2 Читаемость кода
Документация Django инструктирует использовать внутренний класс Meta для метаданных модели. Немного понятнее * указать пользователям фреймворка писать с
class Foo(models.Model)
помощью innerclass Meta
;вместо "напишите a
class _Meta
, затем напишите aclass Foo(models.Model)
сMeta = _Meta
";При использовании подхода «Вложенный класс» код может быть прочитан как вложенный список маркеров , но с помощью метода «Внутренний класс, на который имеется ссылка» нужно прокрутить назад, чтобы увидеть определение,
_Meta
чтобы увидеть его «дочерние элементы» (атрибуты).Метод «Внутренний класс, на который имеется ссылка» может быть более читабельным, если уровень вложенности вашего кода растет или строки становятся длинными по какой-либо другой причине.
* Конечно, дело вкуса
3.3 Несколько иные сообщения об ошибках
Это не имеет большого значения, но просто для полноты: при доступе к несуществующему атрибуту для внутреннего класса мы видим несколько разные исключения. Продолжая пример, приведенный в разделе 2:
Это потому, что
type
s внутренних классовисточник
Я использовал внутренние классы Python для создания подклассов с намеренными ошибками в функциях unittest (т.е. внутри
def test_something():
), чтобы приблизиться к 100% тестового покрытия (например, тестирование очень редко запускаемых операторов регистрации путем переопределения некоторых методов).Оглядываясь назад, это похоже на ответ Эда https://stackoverflow.com/a/722036/1101109
Такие внутренние классы должны выходить за пределы области видимости и быть готовыми к сборке мусора после удаления всех ссылок на них. Например, возьмите следующий
inner.py
файл:Под OSX Python 2.7.6 я получаю следующие любопытные результаты:
Подсказка - не пытайтесь делать это с моделями Django, которые, похоже, хранят другие (кэшированные?) Ссылки на мои ошибочные классы.
В общем, я бы не рекомендовал использовать внутренние классы для таких целей, если вы действительно не цените это 100% тестовое покрытие и не можете использовать другие методы. Хотя я думаю, что приятно осознавать, что если вы используете
__subclasses__()
, то он иногда может загрязняться внутренними классами. В любом случае, если вы зашли так далеко, я думаю, что на данный момент мы довольно глубоко погрузились в Python, частные dunderscores и все такое.источник
.__subclasses__()
чтобы понять, как внутренние классы взаимодействуют со сборщиком мусора, когда что-то выходит за рамки в Python. Это визуально кажется доминирующим в посте, поэтому первые 1-3 абзаца заслуживают немного большего расширения.