Невозможно установить атрибуты класса объектов

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

Ответ 1

Чтобы поддерживать произвольное присвоение атрибутов, объекту требуется __dict__: 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 может иметь произвольные атрибуты, но вы платите за это гибкость.

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

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

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

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

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

Ответ 2

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


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

Ответ 3

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

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

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

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

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

Ответ 4

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

Ответ 5

Итак, исследуя мой собственный вопрос, я обнаружил это в языке 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, поэтому мне пришлось бы переопределять такие функции, как __add__ и т.д., чтобы сохранить мои пользовательские атрибуты. Но все это сейчас имеет смысл для меня (я думаю), когда я думаю о "объекте", как "int".

Ответ 6

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