Невозможно установить атрибуты для экземпляра класса «объект»

87

Итак, я играл с Python, отвечая на этот вопрос , и обнаружил, что это неверно:

o = object()
o.attr = 'hello'

из-за файла AttributeError: 'object' object has no attribute 'attr'. Однако для любого класса, унаследованного от объекта, это действительно так:

class Sub(object):
    pass

s = Sub()
s.attr = 'hello'

При печати s.attrотображается "привет", как и ожидалось. Почему это так? Что в спецификации языка Python указывает, что вы не можете назначать атрибуты ванильным объектам?

Smashery
источник
Чистая догадка: objectтип неизменен, и нельзя добавлять новые атрибуты? В этом есть смысл.
Крис Лутц,
2
@ S.Lott: См. Самую первую строчку этого вопроса. Чисто из любопытства.
Smashery,
3
Ваш заголовок вводит в заблуждение, вы пытаетесь установить атрибуты для objectэкземпляров класса, а не для objectкласса.
dhill

Ответы:

129

Для поддержки произвольного назначения атрибутов объекту необходим __dict__: a dict, связанный с объектом, в котором могут храниться произвольные атрибуты. Иначе новые атрибуты некуда ставить .

Экземпляр objectвовсе не носить с собой __dict__- если это так, перед ужасной круговой задачей зависимости (так dict, как и большинство всего остального, наследуется от object;-), это было бы оседлать каждый объект в Python с Dict, что означало бы накладные расходы из многих байт на объект , который в настоящее время не имеет или не нуждается в Dict ( по существу, все объекты , которые не имеют произвольно назначаемые атрибуты не имеют или нужен Dict).

Например, используя отличный pymplerпроект (вы можете получить его через svn отсюда ), мы можем провести некоторые измерения ...:

>>> from pympler import asizeof
>>> asizeof.asizeof({})
144
>>> asizeof.asizeof(23)
16

Вы же не хотите, чтобы каждый intзанимал 144 байта вместо 16, верно? -)

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

>>> class dint(int): pass
... 
>>> asizeof.asizeof(dint(23))
184

... __dict__ будет теперь добавлены (плюс немного больше накладных расходов) - так dintэкземпляр может иметь произвольные атрибуты, но вы платите довольно стоимость пространства для этой гибкости.

Так что, если вы хотите ints с одним дополнительным атрибутом foobar...? Это редкая необходимость, но Python предлагает специальный механизм для этой цели ...

>>> class fint(int):
...   __slots__ = 'foobar',
...   def __init__(self, x): self.foobar=x+100
... 
>>> asizeof.asizeof(fint(23))
80

... не совсем , как крошечное как int, заметьте! (или даже два ints, one the selfи one the self.foobar- второй можно переназначить), но, безусловно, намного лучше, чем a dint.

Когда у класса есть __slots__специальный атрибут (последовательность строк), то classоператор (точнее, метакласс по умолчанию type) не снабжает каждый экземпляр этого класса __dict__(и, следовательно, возможностью иметь произвольные атрибуты), а только конечным , жесткий набор «слотов» (в основном мест, каждая из которых может содержать одну ссылку на какой-либо объект) с заданными именами.

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

Алекс Мартелли
источник
5
Это объясняет, как реализован механизм, но не объясняет, почему он реализован таким образом. Я могу придумать, по крайней мере, два или три способа реализовать добавление dict на лету, что не будет иметь накладных расходов, но добавит некоторой простоты.
Георги Кременлиев 03
Обратите внимание , что непустой __slots__не работают с типами переменной длиной , такими как str, tupleи в Python-тоже int.
arekolek 01
Это отличное объяснение, но все еще не дает ответа, почему (или как) Subэтот __dict__атрибут, а объект - нет, поскольку он Subнаследуется от object, как этот атрибут (и другие подобные __module__) добавляется в наследование? Может быть, это новый вопрос
Родриго Э. Принсипи
2
Объект __dict__создается только в первый раз, когда он нужен, поэтому ситуация с затратами памяти не так проста, как asizeofкажется на выходе. ( asizeofНе знаю , как избежать __dict__материализации.) Вы можете увидеть Dict не получает материализовались , пока не нужны в этом примере , и вы можете увидеть один из путей кода , ответственных за __dict__материализации здесь .
user2357112 поддерживает Монику
17

Как говорили другие респонденты, objectу a нет __dict__. objectявляется базовым классом всех типов, включая intили str. Таким образом, все, что предусмотрено, objectбудет для них бременем. Даже такая простая вещь, как необязательный __dict__ , потребует дополнительного указателя для каждого значения; это приведет к потере дополнительных 4-8 байтов памяти для каждого объекта в системе для очень ограниченной полезности.


Вместо создания экземпляра фиктивного класса в Python 3.3+ вы можете (и должны) использовать types.SimpleNamespaceдля этого.

Антти Хаапала
источник
4

Это просто из-за оптимизации.

Дикты относительно большие.

>>> import sys
>>> sys.getsizeof((lambda:1).__dict__)
140

Большинство (возможно, все) классы, определенные в C, не имеют подсказок для оптимизации.

Если вы посмотрите исходный код, вы увидите, что существует множество проверок, чтобы увидеть, есть ли у объекта dict или нет.

Неизвестно
источник
1

Итак, исследуя свой собственный вопрос, я обнаружил следующее о языке Python: вы можете наследовать от таких вещей, как int, и вы видите то же поведение:

>>> class MyInt(int):
       pass

>>> x = MyInt()
>>> print x
0
>>> x.hello = 4
>>> print x.hello
4
>>> x = x + 1
>>> print x
1
>>> print x.hello
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
AttributeError: 'int' object has no attribute 'hello'

Я предполагаю, что ошибка в конце __add__связана с тем, что функция добавления возвращает int, поэтому мне пришлось бы переопределить такие функции , чтобы сохранить мои настраиваемые атрибуты. Но теперь все это имеет для меня смысл (я думаю), когда я думаю об «объекте» как об «int».

Smashery
источник
0

Это потому, что объект - это «тип», а не класс. В общем, все классы, которые определены в расширениях C (как и все встроенные типы данных, и прочее вроде массивов numpy), не позволяют добавлять произвольные атрибуты.

Райан
источник
Но object () - это объект, так же как Sub () - это объект. Насколько я понимаю, и s, и o являются объектами. Так в чем же принципиальная разница между s и o? Является ли один экземпляр экземпляром типа, а другой экземпляром класса?
Smashery
Бинго. В этом и проблема.
Райан,
1
В Python 3 разницы между типами и классами действительно не существует. Итак, «тип» и «класс» теперь являются синонимами. Но вы по-прежнему не можете добавлять атрибуты к тем классам, у которых нет __dict__, по причинам, указанным Алексом Мартелли.
PM 2Ring
-2

Это (IMO) одно из фундаментальных ограничений Python - вы не можете повторно открывать классы. Я считаю, что настоящая проблема вызвана тем фактом, что классы, реализованные на C, не могут быть изменены во время выполнения ... подклассы, но не базовые классы.

Питер
источник