Одна из самых загадочных магических функций в Python!

искусственный интеллект Python
Одна из самых загадочных магических функций в Python!

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

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

Основные опасения этой статьи:

(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']

Python里最神秘的一个魔法函数

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

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

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

Если вы используете нативный dict, реализовать его не очень просто, но Python предоставляет очень полезный класс-расширение collections.defaultdict:

Python里最神秘的一个魔法函数

Как показано на рисунке, при взятии несуществующего ключа KeyError не сообщается, но по умолчанию сохраняется в словаре.

Почему defaultdict делает это?

Причина в том, что после того, как defaultdict наследует встроенный тип dict, он также определяет метод __missing__().Когда __getitem__ принимает несуществующее значение, он вызывает фабричную функцию, переданную во входном параметре (приведенный выше пример — список вызовов (), создание пустого списка).

Как наиболее типичный пример, defaultdict записывается в комментарии к документу:

Python里最神秘的一个魔法函数

короче,Основная функция __missing__() должна вызываться __getitem__, когда ключ отсутствует, что позволяет избежать KeyError.

Другой типичный пример использования — collections.Counter, который также является подклассом dict и возвращает значение 0 при получении неучтенного ключа:

Python里最神秘的一个魔法函数

2. Неуловимый __missing__()

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

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

Если вы возьмете значение атрибута непосредственно из типа dict, он сообщит, что атрибут не существует: AttributeError: объект типа «объект» не имеет атрибута «__missing__».

Используйте dir(), чтобы проверить и обнаружить, что атрибут не существует:

Python里最神秘的一个魔法函数

Если вы посмотрите на родительский класс dict, object, вы найдете тот же результат.

Что тут происходит? Почему в словаре и объекте нет атрибута __missing__?

Однако, глядя на последнюю официальную документацию, объект явно содержит это свойство:

Python里最神秘的一个魔法函数

Источник:docs.Python.org/3/reference…_

То есть теоретически __missing__ предопределен в классе объекта, и его документация это доказывает, но на практике он не определен! Документ не соответствует действительности!

Таким образом, когда подкласс dict (такой как defaultdict и Counter) определяет __missing__, магический метод фактически принадлежит этому подклассу, то естьЭто волшебный метод, рожденный в подклассе!

Исходя из этого, у меня есть незрелая догадка: __getitem__() будет судить о том, является ли текущий объект подклассом dict, и есть ли у него __missing__(), а затем вызывать его (если в родительском классе тоже есть этот метод, то он не будет Сначала он вынесет решение, но вызовет его напрямую).

Я высказал это предположение в группе по обмену, и некоторые студенты быстро нашли подтверждение в исходном коде CPython:

Python里最神秘的一个魔法函数

И это интересно, **магические методы, которые существуют только в подклассах встроенных типов**, глядя на весь мир Python, боюсь, трудно найти второй случай.

У меня вдруг возникает ассоциация: этот неуловимый __missing__() похож на фокусника, который хорошо играет в "Великую перемену живых", сначала пусть зрители увидят его сквозь стекло снаружи (то есть официальный документ), но раскроют Когда дверь открывается, он в ней отсутствует (т.е. встроенного типа), меняем проп и он появляется целым (т.е. подклассом дикт).

3. Зачарованный __missing__()

Магия __missing__() заключается в том, что в дополнение к собственной «магии» ей также нужна мощная «магия» для управления.

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

Python里最神秘的一个魔法函数

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

Официальный Python скорее предоставит новые подклассы UserString, UserList, UserDict, чем повторно использует магические методы.Единственное разумное объяснение, похоже, состоит в том, что заставить магические методы вызывать друг друга слишком дорого.

Однако в особом случае __missing__() Python должен пойти на компромисс и заплатить эту цену!

__missing__() — это магический метод "Граждане второго сорта", у него нет независимой записи вызова, его можно вызывать только пассивно с помощью __getitem__(), то есть __missing__() зависит от __getitem__().

отличается от тех»гражданин первого класса", такие как __init__(), __enter__(), __len__(), __eq__() и т. д., которые либо запускаются в какой-то момент жизненного цикла объекта или процесса выполнения, либо встроенной функцией или оператором. являются относительно независимыми событиями без каких-либо зависимостей.

__missing__() зависит от __getitem__() для реализации вызовов методов, а __getitem__() также зависит от __missing__() для достижения полной функциональности.

Для этого __getitem__() открывает лазейку в коде интерпретатора, возвращаясь из интерфейса C в интерфейс Python для вызова этого конкретного метода с именем «__missing__».

Python里最神秘的一个魔法函数

И это настоящая «магия», пока что __missing__() кажется единственным магическим методом, который пользуется такой обработкой!

4. Резюме

Словарь Python предоставляет два встроенных метода для получения значений, а именно __getitem__() и get().Когда значение не существует, их стратегии обработки различаются:Первый сообщит об ошибке KeyError, а второй вернет None.

Почему Python предоставляет два разных метода? Или я должен спросить, почему Python заставляет эти два метода вести себя по-разному?

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

Но одно можно сказать наверняка: простой и грубый способ генерирования KeyError для родного типа dict недостаточен.

Чтобы сделать тип словаря более производительным (или позволить __getitem__() вести себя как get()), Python позволяет подклассам словаря определять __missing__() для вызовов поиска __getitem__().

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

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

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