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


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 указывает, что вы не можете назначать атрибуты ванильным объектам?


Чистая догадка: 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».


0

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


Но object () - это объект, так же как Sub () - это объект. Насколько я понимаю, и s, и o являются объектами. Так в чем же принципиальная разница между s и o? Является ли один экземпляр экземпляром типа, а другой экземпляром класса?
Smashery

Бинго. В этом и проблема.
Райан,

1
В Python 3 разницы между типами и классами действительно не существует. Итак, «тип» и «класс» теперь являются синонимами. Но вы по-прежнему не можете добавлять атрибуты к тем классам, у которых нет __dict__, по причинам, указанным Алексом Мартелли.
PM 2Ring


-2

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.