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?

Kowalstwo
źródło
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).

Alex Martelli
źródło
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.

Antti Haapala
źródło
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.

Nieznany
źródło
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”.

Kowalstwo
źródło
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.

Ryan
źródło
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.

Piotr
źródło