Nie można ustawić atrybutów w instancji klasy „object”


88

Tak więc bawiłem się Pythonem, odpowiadając na to pytanie i odkryłem, że to nie jest poprawne:

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

z powodu AttributeError: 'object' object has no attribute 'attr'. Jednak z każdą klasą dziedziczoną z obiektu jest to poprawne:

class Sub(object):
    pass

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

Drukowanie s.attrwyświetla „cześć” zgodnie z oczekiwaniami. Dlaczego tak się dzieje? Co w specyfikacji języka Python stanowi, że nie można przypisywać atrybutów do obiektów waniliowych?


Zgadywanie: objecttyp jest niezmienny i nie można dodawać nowych atrybutów? Wydaje się, że miałoby to największy sens.
Chris Lutz,

2
@ S.Lott: Zobacz pierwszą linię tego pytania. Czysta ciekawość.
Smashery

3
Twój tytuł jest mylący, próbujesz ustawić atrybuty w objectinstancjach klas, a nie w objectklasie.
dhill

Odpowiedzi:


130

Aby obsłużyć przypisanie dowolnego atrybutu, obiekt potrzebuje a __dict__: dict skojarzonego z obiektem, w którym można przechowywać dowolne atrybuty. W przeciwnym razie nie ma gdzie umieścić nowych atrybutów.

Instancja objectma nie nosić ze sobą __dict__- gdyby tak się stało, przed strasznym okrągłym problemu uzależnienia (ponieważ dict, jak większość wszystko inne, dziedziczy od object;-) byłoby to siodło każdy obiekt w Pythonie z dict, co oznaczałoby obciążenie z wielu bajtów na obiekt, który obecnie nie ma lub nie potrzebuje dyktowania (zasadniczo wszystkie obiekty, które nie mają przypisywalnych atrybutów, nie mają ani nie potrzebują dyktowania).

Na przykład, korzystając z doskonałego pymplerprojektu (możesz go pobrać przez svn stąd ), możemy wykonać kilka pomiarów ...:

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

Nie chciałbyś, aby każdy intzajmował 144 bajty zamiast tylko 16, prawda? -)

Teraz, kiedy tworzysz klasę (dziedzicząc po czymkolwiek), rzeczy się zmieniają ...:

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

... __dict__ jest teraz dodawany (plus trochę więcej narzutu) - więc dintinstancja może mieć dowolne atrybuty, ale za taką elastyczność płacisz sporo miejsca.

A co by było, gdybyś chciał mieć inttylko jeden dodatkowy atrybut foobar...? Jest to rzadka potrzeba, ale Python oferuje specjalny mechanizm do tego celu ...

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

... nie dość jak mały jako int, przeszkadza ci! (lub nawet dwa ints, jeden selfi jeden self.foobar- drugi można ponownie przypisać), ale z pewnością znacznie lepszy niż dint.

Gdy klasa ma __slots__atrybut specjalny (sekwencję ciągów), to classinstrukcja (a dokładniej domyślna metaklasa type) nie wyposaża każdej instancji tej klasy w __dict__atrybut (a tym samym możliwość posiadania dowolnych atrybutów), tylko skończoną , sztywny zbiór "szczelin" (w zasadzie miejsc, które mogą zawierać jedno odniesienie do jakiegoś obiektu) o podanych nazwach.

W zamian za utratę elastyczności zyskujesz dużo bajtów na instancję (prawdopodobnie ma to znaczenie tylko wtedy, gdy masz zyliony instancji kręcących się po okolicy, ale do tego przypadki użycia).


5
To wyjaśnia, w jaki sposób mechanizm jest wdrażany, ale nie wyjaśnia, dlaczego jest wdrażany w ten sposób. Mogę wymyślić co najmniej dwa lub trzy sposoby na zaimplementowanie dodawania dyktowania w locie, które nie będzie miało wad narzutów, ale doda trochę prostoty.
Георги Кременлиев

Zauważ, że niepusty __slots__nie działają z typów zmiennej długości, takich jak str, tupleoraz w Pythonie 3 również int.
arekolek


To świetny wyjaśnienie, ale nadal nie odpowiada, dlaczego (i jak) Subma __dict__atrybutu i obiekt nie będąc które Subdziedziczą z object, jak to jest, że atrybut (i inne podobne __module__) dodaje się w spadku? Może to może być nowe pytanie
Rodrigo E. Principe

3
Obiekt __dict__jest tworzony tylko za pierwszym razem, gdy jest potrzebny, więc sytuacja kosztu pamięci nie jest tak prosta, jak na asizeofto wygląda wynik. ( asizeofNie wiem, jak uniknąć __dict__materializację.) Można zobaczyć dict nie uzyskiwanie zmaterializowanej, aż będą potrzebne w tym przykładzie , można zobaczyć jedną ze ścieżek kodu odpowiedzialnych za __dict__materializacji tutaj .
user2357112 obsługuje Monikę

17

Jak powiedzieli inni respondenci, objectnie ma __dict__. objectjest klasą bazową wszystkich typów, w tym intlub str. Zatem wszystko, co jest zapewnione, objectbędzie dla nich ciężarem. Nawet coś tak prostego jak opcjonalne __dict__ wymagałoby dodatkowego wskaźnika dla każdej wartości; spowodowałoby to stratę dodatkowych 4-8 bajtów pamięci dla każdego obiektu w systemie, dla bardzo ograniczonej użyteczności.


Zamiast robić instancję fałszywej klasy, w Pythonie 3.3+ możesz (i powinieneś) użyć types.SimpleNamespacedo tego.


4

Wynika to po prostu z optymalizacji.

Dicty są stosunkowo duże.

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

Większość (być może wszystkie) klas zdefiniowanych w C nie ma dyktowania do optymalizacji.

Jeśli spojrzysz na kod źródłowy , zobaczysz, że jest wiele sprawdzeń, czy obiekt ma dykt, czy nie.


1

Tak więc, badając moje własne pytanie, odkryłem to na temat języka Python: możesz dziedziczyć z rzeczy takich jak int i widzisz to samo zachowanie:

>>> 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'

Zakładam, że błąd na końcu jest taki, że funkcja add zwraca int, więc musiałbym przesłonić funkcje takie jak __add__i inne, aby zachować moje niestandardowe atrybuty. Ale to wszystko ma teraz dla mnie sens (tak mi się wydaje), kiedy myślę o „przedmiocie” takim jak „int”.


0

Dzieje się tak, ponieważ obiekt jest „typem”, a nie klasą. Ogólnie rzecz biorąc, wszystkie klasy zdefiniowane w rozszerzeniach C (jak wszystkie wbudowane typy danych i takie jak tablice numpy) nie pozwalają na dodawanie dowolnych atrybutów.


Ale object () jest obiektem, tak jak Sub () jest obiektem. Rozumiem, że zarówno s, jak i o są obiektami. Jaka jest więc podstawowa różnica między s i o? Czy jest to, że jeden jest typem, na który została utworzona instancja, a druga klasą?
Smashery

Bingo. Właśnie o to chodzi.
Ryan,

1
W Pythonie 3 różnica między typami i klasami tak naprawdę nie istnieje. Zatem „typ” i „klasa” są teraz dość synonimami. Ale nadal nie możesz dodawać atrybutów do tych klas, które nie mają __dict__, z powodów podanych przez Alexa Martelli.
PM 2Ring


-2

Jest to (IMO) jedno z podstawowych ograniczeń Pythona - nie można ponownie otwierać klas. Uważam jednak, że rzeczywisty problem jest spowodowany faktem, że klasy zaimplementowane w C nie mogą być modyfikowane w czasie wykonywania ... podklasy mogą, ale nie klasy bazowe.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.