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.attr
wyś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?
python
attributes
language-design
Kowalstwo
źródło
źródło
object
typ jest niezmienny i nie można dodawać nowych atrybutów? Wydaje się, że miałoby to największy sens.object
instancjach klas, a nie wobject
klasie.Odpowiedzi:
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
object
ma 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 odobject
;-) 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
pympler
projektu (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
int
zajmował 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ęcdint
instancja może mieć dowolne atrybuty, ale za taką elastyczność płacisz sporo miejsca.A co by było, gdybyś chciał mieć
int
tylko jeden dodatkowy atrybutfoobar
...? 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 dwaint
s, jedenself
i jedenself.foobar
- drugi można ponownie przypisać), ale z pewnością znacznie lepszy niżdint
.Gdy klasa ma
__slots__
atrybut specjalny (sekwencję ciągów), toclass
instrukcja (a dokładniej domyślna metaklasatype
) 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 są do tego przypadki użycia).
źródło
__slots__
nie działają z typów zmiennej długości, takich jakstr
,tuple
oraz w Pythonie 3 równieżint
.__dict__
, czy jego klasa musi mieć__slot__
atrybut?Sub
ma__dict__
atrybutu i obiekt nie będąc któreSub
dziedziczą zobject
, jak to jest, że atrybut (i inne podobne__module__
) dodaje się w spadku? Może to może być nowe pytanie__dict__
jest tworzony tylko za pierwszym razem, gdy jest potrzebny, więc sytuacja kosztu pamięci nie jest tak prosta, jak naasizeof
to wygląda wynik. (asizeof
Nie 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 .Jak powiedzieli inni respondenci,
object
nie ma__dict__
.object
jest klasą bazową wszystkich typów, w tymint
lubstr
. Zatem wszystko, co jest zapewnione,object
bę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.SimpleNamespace
do tego.źródło
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.
źródło
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”.źródło
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.
źródło
__dict__
, z powodów podanych przez Alexa Martelli.https://docs.python.org/3/library/functions.html#object :
źródło
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.
źródło