Не могу установить атрибуты класса объекта - PullRequest
71 голосов
/ 07 октября 2009

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

Ответы [ 6 ]

116 голосов
/ 07 октября 2009

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

Экземпляр object не не несет __dict__ - если это так, до ужасной проблемы круговой зависимости (поскольку dict, как и большинство всего остального, наследуется от object ;-), это оседлало бы каждый объект в Python с 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 s только с одним дополнительным атрибутом 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 - второй можно переназначить), но, безусловно, намного лучше, чем dint.

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

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

16 голосов
/ 09 апреля 2014

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


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

4 голосов
/ 07 октября 2009

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

Dicts относительно велики.

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

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

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

1 голос
/ 07 октября 2009

Итак, исследуя свой собственный вопрос, я обнаружил это о языке 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».

0 голосов
/ 07 октября 2009

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

0 голосов
/ 07 октября 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...