Неможливо встановити атрибути на екземплярі класу “object”


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__: 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, один the selfта one the self.foobar- другий може бути перепризначений), але, безумовно, набагато кращий ніжdint .

Коли клас має __slots__спеціальний атрибут (послідовність рядків), тоді classоператор (точніше, метаклас за замовчуванням, type) робить НЕ оснащувати кожен екземпляр цього класу з __dict__(і , отже , можливістю мати довільні атрибути), тільки кінцевий , жорсткий набір "слотів" (в основному місця, кожна з яких може містити одне посилання на якийсь об'єкт) із заданими іменами.

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


5
Це пояснює, як механізм реалізується, але не пояснює, чому він реалізується таким чином. Я можу придумати принаймні два-три способи реалізації додавання дикту на льоту, який не матиме нижчих наслідків, але додасть певної простоти.
Георги Кременліев

Зверніть увагу , що непорожній __slots__не працюють з типами змінною довжиною , такими як str, tupleі в Python-теж int.
arekolek


Це велике пояснення, але до сих пір не відповідає , чому (або як) Subмає __dict__атрибут і об'єкт не робить, в тому , що Subуспадковує від object, як цей атрибут (і інші подібні __module__) додані в спадок? Можливо, це може бути новим запитанням
Родріго Е. Принсіпі

2
Об'єкт __dict__створюється лише в перший раз, коли це потрібно, тому ситуація з вартістю пам'яті не є настільки простою, наскільки asizeofрезультат виглядає так. ( asizeofНе знаю , як уникнути __dict__матеріалізації.) Ви можете побачити Dict не отримує матеріалізувалися , поки не потрібні в цьому прикладі , і ви можете побачити один із шляхів коду , відповідальних за __dict__матеріалізації тут .
user2357112 підтримує Моніку

17

Як казали інші відповідачі, у an objectнемає __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, тому мені довелося б замінити такі функції, як __add__і такі, щоб зберегти мої власні атрибути. Але все це зараз для мене має сенс (я думаю), коли я думаю про "об'єкт", як "int".


0

Це тому, що об’єкт - це „тип”, а не клас. Загалом, усі класи, визначені у розширеннях C (як усі вбудовані типи даних та подібні масиви numpy), не дозволяють додавати довільні атрибути.


Але object () - це об’єкт, як і Sub () - об’єкт. Я розумію, що і s, і o - це об’єкти. То яка принципова різниця між s та o? Це те, що один є інстанційним типом, а інший - інстанційованим класом?
Smashery

Бінго. Саме в цьому питання.
Райан,

1
У Python 3 різниця між типами та класами насправді не існує. Тож "тип" і "клас" тепер є досить синонімами. Але ви все одно не можете додавати атрибути до тих класів, які цього не мають __dict__, з причин, зазначених Алексом Мартеллі.
PM 2Кольцо


-2

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.