Какая магия python выполняет dir () с __getattr__?

Ниже в python 2.7 с MySQLdb 1.2.3.

Мне понадобилась оболочка класса, чтобы добавить некоторые атрибуты к объектам, которые ее не поддерживали (классы с __slots__ и / или некоторым классом, написанные на C), поэтому я вышел с чем-то вроде этого:

 class Wrapper(object): def __init__(self, obj): self._wrapped_obj = obj def __getattr__(self, obj): return getattr(self._wrapped_obj, attr) 

Я ожидал, что встроенный dir() вызванный моим экземпляром Wrapper должен был вернуть только имена, унаследованные объектом plus wrapped_obj , и я обнаружил, что это на самом деле имеет место для большинства случаев, но не для всех. Я пробовал это с помощью обычного старого класса стиля, пользовательского нового класса стиля и некоторых встроенных классов, он всегда работал следующим образом: единственным исключением, которое я нашел, является то, что обернутый объект был экземпляром класса _mysql.connection . В этом случае dir() на моем объекте знает также все имена методов, прикрепленные к обернутому объекту связи.

Я читал в документации python о dir , и это поведение выглядит законным: dir должен возвращать список «интересных имен», а не «реальное» содержимое экземпляра. Но я действительно не могу понять, как он это делает: он действительно понимает реализацию моего __getattr__ и разрешает прикрепленный элемент? Если это правда, почему только с этим классом connection а не с упрощенным dict ?

Вот пример вложенного кода в качестве примера этого любопытного поведения:

 >>> from _mysql import connection >>> c = connection(**connection_parameters) >>> c <_mysql.connection open to '127.0.0.1' at a16920> >>> >>> dir(c) ['affected_rows', 'autocommit', 'change_user', 'character_set_name', 'close', 'commit', 'dump_debug_info', 'errno', 'error', 'escape', 'escape_string', 'field_count', 'get_character_set_info', 'get_host_info', 'get_proto_info', 'get_server_info', 'info', 'insert_id', 'kill', 'next_result', 'ping', 'query', 'rollback', 'select_db', 'set_character_set', 'set_server_option', 'shutdown', 'sqlstate', 'stat', 'store_result', 'string_literal', 'thread_id', 'use_result', 'warning_count'] >>> >>> w = Wrapper(c) >>> dir(w) ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattr__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_wrapped_obj', 'affected_rows', 'autocommit', 'change_user', 'character_set_name', 'close', 'commit', 'dump_debug_info', 'errno', 'error', 'escape', 'escape_string', 'field_count', 'get_character_set_info', 'get_host_info', 'get_proto_info', 'get_server_info', 'info', 'insert_id', 'kill', 'next_result', 'ping', 'query', 'rollback', 'select_db', 'set_character_set', 'set_server_option', 'shutdown', 'sqlstate', 'stat', 'store_result', 'string_literal', 'thread_id', 'use_result', 'warning_count'] >>> >>> d = Wrapper({}) >>> dir(d) ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattr__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_wrapped_obj'] >>> 

В Python 2 есть два устаревших атрибута, object.__members__ и object.__methods__ ; они были нацелены на поддержку dir() в типах расширений (C-определенные объекты):

object.__methods__
Устаревший с версии 2.2: используйте встроенную функцию dir() чтобы получить список атрибутов объекта. Этот атрибут больше недоступен.

object.__members__
Устаревший с версии 2.2: используйте встроенную функцию dir (), чтобы получить список атрибутов объекта. Этот атрибут больше недоступен.

Они были удалены из Python 3, но поскольку ваш объект соединения (по крайней мере, в старой версии, которую вы используете), по-прежнему содержит атрибут __methods__ который найден через ваш __getattr__ hook и используется здесь dir() .

Если вы добавите оператор print в метод __getattr__ вы увидите доступные атрибуты:

 >>> class Wrapper(object): ... def __init__(self, obj): ... self._wrapped_obj = obj ... def __getattr__(self, obj): ... print 'getattr', obj ... return getattr(self._wrapped_obj, attr) ... >>> dir(Wrapper({})) getattr __members__ getattr __methods__ ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattr__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_wrapped_obj'] 

Для объектов нового стиля более __dir__ метод __dir__ поддерживаемый dir() , корректно просматривается на этом типе, поэтому вы не видите, что к нему обращаются.

Файл проекта HISTORY предполагает, что атрибуты были удалены в большом обновлении совместимости Python 3 для версии 1.2.4 beta 1.