Почему __mro__ не отображается в dir(MyClass)?

class MyClass(object):
    pass

print MyClass.__mro__
print dir(MyClass)

Выход:

(<class '__main__.MyClass'>, <type 'object'>)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

Почему __mro__ не указан с dir()?

2 ответа

Решение

Из документации Python:

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

__mro__ является атрибутом только для чтения, который используется для определения разрешения метода в случае, если ваш класс наследует от нескольких базовых классов. Если вы хотите настроить это поведение, вы должны использовать метакласс (особый вид объекта, целью которого является создание экземпляров класса), который переопределяет mro() метод. __mro__ остается неизменным в любом случае.

Мне недавно стало интересно то же самое. Я искал ответ более в соответствии с "Как насчет причин реализации Python mro/__mro__ не быть перечисленным в dir? И чему я могу доверять dir перечислить?", чем просто" Как документация Python оправдывает невключение mro в dir"Мне не нравится, когда мои ожидания от поведения языка программирования не соответствуют его фактическому поведению, потому что это означает, что мое понимание языка неверно - если только это не ошибка urllib2.escape, Так что я немного покопался, пока не разобрался с ответом.

Строка adolfopa, процитированная из документации в комментариях выше, хороша для объяснения поведения dir.

"Если объект является объектом типа или класса, список содержит имена его атрибутов и рекурсивные атрибуты его баз".

Что это значит? dir рекурсивно собирает атрибуты из класса __dict__ и каждый из его суперклассов __dict__,

set(dir(object)) == set(dict(object.__dict__).keys() #True



class A(object):
    ...

class B(object):
    ...

class C(B):
    ...

class D(C,A):
    ...

set(dir(D)) == set(D.__dict__.keys()) + set(C.__dict__.keys()) \
   + set(B.__dict__.keys()) + set(A.__dict__.keys()) \
   + set(object.__dict__.keys()) #True

Причина dir(object) не перечисляет __mro__/mro в том, что они не являются атрибутами объекта. Они являются атрибутами type, Каждый класс, который не определяет свой собственный __metaclass__ это пример type, Большинство подклассов метаклассов type, Экземпляры таких метаклассов также являются экземплярами типа. MyClass.__mro__ такой же как type.__getattribute__(MyClass,'__mro__'),

То, как Python реализует классы, обязательно создает небольшую аномалию в отношении того, как работает dir.

Как правило, dir(MyClass) == dir(MyClass(*requiredparameters)) #True,

Тем не мение, dir(type) == dir(type(*requiredparameters)) #False, но единственный способ, которым это могло бы быть иначе, был, если type.__dict__ а также dir мы одинаковы. Это, само собой разумеется, не цель dir,

Но ждать! dir создается рекурсивной суммой, почему мы не можем просто изменить последний кусок так, чтобы dir(object) больше не просто object.__dict__.keys() а скорее становится object.__dict__.keys() + type.__dict__.keys(), Таким образом, было бы mro/__mro__ и все другие атрибуты, которые имеет объект класса? Ах, но это будут атрибуты объекта класса, а не класса. Ну какая разница?

Рассматривать

list.__mro__ #(<type 'list'>, <type 'object'>)

В то время как

[].__mro__
# Traceback (most recent call last):
#  File "<stdin>", line 1, in <module>
# AttributeError: 'list' object has no attribute '__mro__'

Теперь мы готовы ответить на то, на что мы можем рассчитывать dir перечислить и на что мы можем рассчитывать dir не перечислять. Простой ответ - тот, который мы уже рассмотрели. В нем перечислены все ключи в классе __dict__ плюс все ключи в каждом из его суперклассов __dict__х, рекурсивно. Для экземпляра он также включает все параметры в экземпляре __dict__, Чтобы заполнить отрицательное пространство, он не перечисляет ничего определенного в __getattr__ или в чем-либо в __getattribute__ если это не также в __dict__, Также не перечисляются никакие атрибуты типа / метатипа.


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

Атрибуты встроенных объектов не могут быть установлены, поэтому, в некотором смысле, type.__mro__ "только для чтения", но только так же, как list.append или type.mro есть, в этом отношении.

MyClass.__mro__ = "Hello world!" не вызывает ошибку. Это просто не влияет на порядок разрешения методов, определенный в type, Таким образом, это может не дать ожидаемого эффекта, если вы пытаетесь изменить это поведение. (Что он делает, это причина MyClass(*requiredparameters).__mro__ быть "Hello World!" что и следовало ожидать, так как определение атрибутов классов работает в python.) Вы также можете переопределить __mro__ когда вы подкласса типа для создания метакласса. Если вы не переопределяете его, он наследуется, как и все, что вы не переопределяете. (Если вы создаете метакласс, который не является подклассом типа и который не является функцией, которая возвращает экземпляр типа, вероятно, вы уже точно знаете, что делаете, и не беспокоитесь об этом, но __mro__ не будет наследоваться, так как вы не подкласс type)

Согласно документации (описание поведения супер):

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

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

Другие вопросы по тегам