Всем привет, меня зовут Цзецзе, и сегодня я познакомлю вас с очень загадочным магическим методом.
Этот метод настолько ничем не примечателен и узко полезен, что я почти никогда не замечал его, однако, когда я обнаружил, что он может быть единственным исключением из приведенного выше «закона», я подумал, что стоит написать еще одну статью, чтобы рассмотреть его подробно.
Основные опасения этой статьи:
(1) Где священно __missing__()?
(2) Что особенного в __missing__()? Вы хорошо разбираетесь в магии «Великого изменения жизни»?
(3) Действительно ли __missing__() является исключением из приведенного выше вывода? Если да, то почему такое исключение?
1. __missing__() с небольшим значением
При извлечении значений из обычных словарей могут быть случаи, когда ключ не существует:
dd = {'name':'PythonCat'}
dd.get('age') # 结果:None
dd.get('age', 18) # 结果:18
dd['age'] # 报错 KeyError
dd.__getitem__('age') # 等同于 dd['age']
Для метода get() он имеет возвращаемое значение, а второй параметр может быть передан в качестве возвращаемого содержимого, если ключ не существует, поэтому это допустимо. Однако два других способа записи сообщат об ошибке.
Чтобы решить проблему последних двух способов записи, вы можете использовать магический метод __missing__().
Теперь предположим, что у нас есть такой запрос: взять значение, соответствующее ключу, из словаря, вернуть значение, если оно есть, вставить ключ, если значения нет, и присвоить ему значение по умолчанию (например, пустой список).
Если вы используете нативный dict, реализовать его не очень просто, но Python предоставляет очень полезный класс-расширение collections.defaultdict:
Как показано на рисунке, при взятии несуществующего ключа KeyError не сообщается, но по умолчанию сохраняется в словаре.
Почему defaultdict делает это?
Причина в том, что после того, как defaultdict наследует встроенный тип dict, он также определяет метод __missing__().Когда __getitem__ принимает несуществующее значение, он вызывает фабричную функцию, переданную во входном параметре (приведенный выше пример — список вызовов (), создание пустого списка).
Как наиболее типичный пример, defaultdict записывается в комментарии к документу:
короче,Основная функция __missing__() должна вызываться __getitem__, когда ключ отсутствует, что позволяет избежать KeyError.
Другой типичный пример использования — collections.Counter, который также является подклассом dict и возвращает значение 0 при получении неучтенного ключа:
2. Неуловимый __missing__()
Из вышеизложенного видно, что __missing__() будет вызываться, когда __getitem__() не может получить значение, но я случайно обнаружил деталь:__getitem__() не обязательно вызывает __missing__(), когда не может получить значение.
Это связано с тем, что оно не является обязательным свойством встроенных типов и не предопределено в базовом классе словаря.
Если вы возьмете значение атрибута непосредственно из типа dict, он сообщит, что атрибут не существует: AttributeError: объект типа «объект» не имеет атрибута «__missing__».
Используйте dir(), чтобы проверить и обнаружить, что атрибут не существует:
Если вы посмотрите на родительский класс dict, object, вы найдете тот же результат.
Что тут происходит? Почему в словаре и объекте нет атрибута __missing__?
Однако, глядя на последнюю официальную документацию, объект явно содержит это свойство:
Источник:docs.Python.org/3/reference…_
То есть теоретически __missing__ предопределен в классе объекта, и его документация это доказывает, но на практике он не определен! Документ не соответствует действительности!
Таким образом, когда подкласс dict (такой как defaultdict и Counter) определяет __missing__, магический метод фактически принадлежит этому подклассу, то естьЭто волшебный метод, рожденный в подклассе!
Исходя из этого, у меня есть незрелая догадка: __getitem__() будет судить о том, является ли текущий объект подклассом dict, и есть ли у него __missing__(), а затем вызывать его (если в родительском классе тоже есть этот метод, то он не будет Сначала он вынесет решение, но вызовет его напрямую).
Я высказал это предположение в группе по обмену, и некоторые студенты быстро нашли подтверждение в исходном коде CPython:
И это интересно, **магические методы, которые существуют только в подклассах встроенных типов**, глядя на весь мир Python, боюсь, трудно найти второй случай.
У меня вдруг возникает ассоциация: этот неуловимый __missing__() похож на фокусника, который хорошо играет в "Великую перемену живых", сначала пусть зрители увидят его сквозь стекло снаружи (то есть официальный документ), но раскроют Когда дверь открывается, он в ней отсутствует (т.е. встроенного типа), меняем проп и он появляется целым (т.е. подклассом дикт).
3. Зачарованный __missing__()
Магия __missing__() заключается в том, что в дополнение к собственной «магии» ей также нужна мощная «магия» для управления.
В прошлой статье я обнаружил, что нативные магические методы не зависят друг от друга, они могут иметь одинаковую базовую логику в интерфейсе языка C, но в интерфейсе языка Python нет взаимосвязи вызова:
Это «старое и мертвое» поведение магических методов нарушает общий принцип повторного использования кода, а также является причиной странного поведения подклассов встроенных типов.
Официальный Python скорее предоставит новые подклассы UserString, UserList, UserDict, чем повторно использует магические методы.Единственное разумное объяснение, похоже, состоит в том, что заставить магические методы вызывать друг друга слишком дорого.
Однако в особом случае __missing__() Python должен пойти на компромисс и заплатить эту цену!
__missing__() — это магический метод "Граждане второго сорта", у него нет независимой записи вызова, его можно вызывать только пассивно с помощью __getitem__(), то есть __missing__() зависит от __getitem__().
отличается от тех»гражданин первого класса", такие как __init__(), __enter__(), __len__(), __eq__() и т. д., которые либо запускаются в какой-то момент жизненного цикла объекта или процесса выполнения, либо встроенной функцией или оператором. являются относительно независимыми событиями без каких-либо зависимостей.
__missing__() зависит от __getitem__() для реализации вызовов методов, а __getitem__() также зависит от __missing__() для достижения полной функциональности.
Для этого __getitem__() открывает лазейку в коде интерпретатора, возвращаясь из интерфейса C в интерфейс Python для вызова этого конкретного метода с именем «__missing__».
И это настоящая «магия», пока что __missing__() кажется единственным магическим методом, который пользуется такой обработкой!
4. Резюме
Словарь Python предоставляет два встроенных метода для получения значений, а именно __getitem__() и get().Когда значение не существует, их стратегии обработки различаются:Первый сообщит об ошибке KeyError, а второй вернет None.
Почему Python предоставляет два разных метода? Или я должен спросить, почему Python заставляет эти два метода вести себя по-разному?
У этого может быть очень сложное (или, возможно, очень простое) объяснение, в которое я не буду углубляться в этой статье.
Но одно можно сказать наверняка: простой и грубый способ генерирования KeyError для родного типа dict недостаточен.
Чтобы сделать тип словаря более производительным (или позволить __getitem__() вести себя как get()), Python позволяет подклассам словаря определять __missing__() для вызовов поиска __getitem__().
В этой статье разбирается принцип реализации __missing__(), тем самым раскрывая, что это не незаметное существование, а наоборот,Это единственный частный случай, который разрушает барьер между магическими методами и поддерживает вызов других магических методов!
Чтобы сохранить независимость магических методов, Python приложил большие усилия, чтобы ввести производные классы, такие как UserString, UserList и UserDict, но для __missing__() он решил пойти на компромисс.
Эта статья раскрывает тайну этого волшебного метода, что вы чувствуете после прочтения? Добро пожаловать, чтобы оставить сообщение для обсуждения.