Odpowiedzi:
W Pythonie jaki jest cel
__slots__
i w jakich przypadkach należy tego unikać?
Specjalny atrybut __slots__
pozwala jawnie określić, które atrybuty instancji mają mieć instancje obiektu, z oczekiwanymi rezultatami:
Oszczędność miejsca pochodzi z
__dict__
.__dict__
i __weakref__
tworzenie, jeśli klasy nadrzędne odmawiają ich, a ty deklarujesz __slots__
.Małe zastrzeżenie, powinieneś zadeklarować konkretny slot tylko raz w drzewie spadkowym. Na przykład:
class Base:
__slots__ = 'foo', 'bar'
class Right(Base):
__slots__ = 'baz',
class Wrong(Base):
__slots__ = 'foo', 'bar', 'baz' # redundant foo and bar
Python nie sprzeciwia się, gdy popełnisz błąd (prawdopodobnie powinien), problemy mogą się w inny sposób nie ujawnić, ale twoje obiekty zajmą więcej miejsca niż powinny. Python 3.8:
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
Wynika to z faktu, że deskryptor gniazda Bazy ma gniazdo inne niż Błędne. To zwykle nie powinno się pojawiać, ale może:
>>> w = Wrong()
>>> w.foo = 'foo'
>>> Base.foo.__get__(w)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: foo
>>> Wrong.foo.__get__(w)
'foo'
Największym zastrzeżeniem jest wielokrotne dziedziczenie - nie można łączyć wielu „klas nadrzędnych z niepustymi gniazdami”.
Aby dostosować się do tego ograniczenia, postępuj zgodnie z najlepszymi praktykami: Uwzględnij abstrakcję wszystkich oprócz jednego lub wszystkich rodziców, które ich konkretna klasa i twoja nowa konkretna klasa zbiorowo odziedziczą - dając abstrakcje puste miejsca (podobnie jak abstrakcyjne klasy podstawowe w biblioteka standardowa).
Przykład zawiera sekcja dotycząca wielokrotnego dziedziczenia poniżej.
Aby atrybuty o nazwie in __slots__
były przechowywane w szczelinach zamiast a __dict__
, klasa musi dziedziczyć po object
.
Aby zapobiec utworzeniu a __dict__
, musisz dziedziczyć, object
a wszystkie klasy w spadku muszą zadeklarować __slots__
i żadna z nich nie może mieć '__dict__'
pozycji.
Jest wiele szczegółów, jeśli chcesz dalej czytać.
__slots__
: Szybszy dostęp do atrybutów.Twórca Pythona, Guido van Rossum, twierdzi , że stworzył go w __slots__
celu szybszego dostępu do atrybutów.
Trywialne jest zademonstrowanie wymiernie szybszego dostępu:
import timeit
class Foo(object): __slots__ = 'foo',
class Bar(object): pass
slotted = Foo()
not_slotted = Bar()
def get_set_delete_fn(obj):
def get_set_delete():
obj.foo = 'foo'
obj.foo
del obj.foo
return get_set_delete
i
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
Dostęp szczelinowy jest prawie 30% szybszy w Pythonie 3.5 na Ubuntu.
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
W Pythonie 2 w systemie Windows zmierzyłem go o około 15% szybciej.
__slots__
: Oszczędności pamięciInnym celem __slots__
jest zmniejszenie miejsca w pamięci zajmowanego przez każdą instancję obiektu.
Mój własny wkład w dokumentację jasno wskazuje powody tego :
Przestrzeń zaoszczędzona przy użyciu
__dict__
może być znacząca.
SQLAlchemy przypisuje wiele oszczędności pamięci __slots__
.
Aby to zweryfikować, użyj dystrybucji Anaconda w Pythonie 2.7 w systemie Ubuntu Linux, używając guppy.hpy
(aka heapy) isys.getsizeof
, a rozmiar instancji klasy bez __slots__
zadeklarowanego i nic innego to 64 bajty. To nie obejmuje __dict__
. Dziękuję Pythonowi za leniwą ocenę, __dict__
najwyraźniej nie jest wywoływana, dopóki nie zostanie przywołana, ale klasy bez danych są zwykle bezużyteczne. Po wywołaniu ten __dict__
atrybut ma dodatkowo minimum 280 bajtów.
Natomiast instancja klasy z __slots__
zadeklarowanym jako ()
(bez danych) ma tylko 16 bajtów i 56 bajtów ogółem z jednym elementem w szczelinach, 64 z dwoma.
Dla 64-bitowego Pythona, ilustruję zużycie pamięci w bajtach w Pythonie 2.7 i 3.6 __slots__
i __dict__
(nie zdefiniowano gniazd) dla każdego punktu, w którym dykt rośnie w 3.6 (z wyjątkiem atrybutów 0, 1 i 2):
Python 2.7 Python 3.6
attrs __slots__ __dict__* __slots__ __dict__* | *(no slots defined)
none 16 56 + 272† 16 56 + 112† | †if __dict__ referenced
one 48 56 + 272 48 56 + 112
two 56 56 + 272 56 56 + 112
six 88 56 + 1040 88 56 + 152
11 128 56 + 1040 128 56 + 240
22 216 56 + 3344 216 56 + 408
43 384 56 + 3344 384 56 + 752
Tak więc, pomimo mniejszych nagrań w Pythonie 3, widzimy, jak ładnie __slots__
skaluje się instancje, aby zaoszczędzić nam pamięć, i to jest główny powód, dla którego chciałbyś skorzystać __slots__
.
Dla kompletności moich notatek zauważ, że istnieje jednorazowy koszt na boks w klasie nazw 64 bajty w Pythonie 2 i 72 bajty w Pythonie 3, ponieważ automaty wykorzystują deskryptory danych takie jak właściwości, zwane „członkami”.
>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
__slots__
:Aby odmówić utworzenia a __dict__
, musisz podklasę object
:
class Base(object):
__slots__ = ()
teraz:
>>> b = Base()
>>> b.a = 'a'
Traceback (most recent call last):
File "<pyshell#38>", line 1, in <module>
b.a = 'a'
AttributeError: 'Base' object has no attribute 'a'
Lub podklasę innej klasy, która definiuje __slots__
class Child(Base):
__slots__ = ('a',)
i teraz:
c = Child()
c.a = 'a'
ale:
>>> c.b = 'b'
Traceback (most recent call last):
File "<pyshell#42>", line 1, in <module>
c.b = 'b'
AttributeError: 'Child' object has no attribute 'b'
Aby zezwolić na __dict__
tworzenie przy podzklasowaniu obiektów szczelinowych, po prostu dodaj '__dict__'
do __slots__
(pamiętaj, że szczeliny są uporządkowane i nie powinieneś powtarzać tych, które są już w klasach nadrzędnych):
class SlottedWithDict(Child):
__slots__ = ('__dict__', 'b')
swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'
i
>>> swd.__dict__
{'c': 'c'}
Lub nie musisz nawet deklarować __slots__
w swojej podklasie i nadal będziesz korzystać z miejsc od rodziców, ale nie będziesz ograniczać tworzenia __dict__
:
class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'
I:
>>> ns.__dict__
{'b': 'b'}
Jednakże, __slots__
może powodować problemy dla wielu dziedziczenia:
class BaseA(object):
__slots__ = ('a',)
class BaseB(object):
__slots__ = ('b',)
Ponieważ utworzenie klasy podrzędnej od rodziców z obydwoma niepustymi miejscami:
>>> class Child(BaseA, BaseB): __slots__ = ()
Traceback (most recent call last):
File "<pyshell#68>", line 1, in <module>
class Child(BaseA, BaseB): __slots__ = ()
TypeError: Error when calling the metaclass bases
multiple bases have instance lay-out conflict
Jeśli napotkasz ten problem, mógłby po prostu usunąć __slots__
z rodziców, lub jeśli masz kontrolę rodziców, dać im pustych szczelin lub Refactor do abstrakcji:
from abc import ABC
class AbstractA(ABC):
__slots__ = ()
class BaseA(AbstractA):
__slots__ = ('a',)
class AbstractB(ABC):
__slots__ = ()
class BaseB(AbstractB):
__slots__ = ('b',)
class Child(AbstractA, AbstractB):
__slots__ = ('a', 'b')
c = Child() # no problem!
'__dict__'
aby __slots__
uzyskać dynamiczne przypisanie:class Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
i teraz:
>>> foo = Foo()
>>> foo.boink = 'boink'
Tak więc '__dict__'
w przypadku automatów do gier tracimy niektóre korzyści związane z rozmiarem, ponieważ zaletą jest dynamiczne przypisywanie i wciąż mają automaty do nazw, których oczekujemy.
Kiedy dziedziczysz po obiekcie, który nie jest dzielony, otrzymujesz ten sam rodzaj semantyki podczas używania __slots__
- nazwy, które __slots__
wskazują wartości wycinane, podczas gdy wszelkie inne wartości są umieszczane w instancji __dict__
.
Unikanie, __slots__
ponieważ chcesz mieć możliwość dodawania atrybutów w locie, nie jest właściwie dobrym powodem - po prostu dodaj "__dict__"
do swojego__slots__
jeśli jest to wymagane.
Możesz również dodać __weakref__
do __slots__
jawnie, jeśli potrzebujesz tej funkcji.
Wbudowane namedtuple tworzą niezmienne instancje, które są bardzo lekkie (zasadniczo rozmiar krotek), ale aby uzyskać korzyści, musisz zrobić to sam, jeśli je podklasujesz:
from collections import namedtuple
class MyNT(namedtuple('MyNT', 'bar baz')):
"""MyNT is an immutable and lightweight object"""
__slots__ = ()
stosowanie:
>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'
A próba przypisania nieoczekiwanego atrybutu podnosi, AttributeError
ponieważ zapobiegliśmy tworzeniu __dict__
:
>>> nt.quux = 'quux'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'
Państwo może umożliwić __dict__
stworzenie pozostawiając off __slots__ = ()
, ale nie można używać niepusty __slots__
z podtypów krotki.
Nawet jeśli puste pola są takie same dla wielu rodziców, nie można ich używać razem:
class Foo(object):
__slots__ = 'foo', 'bar'
class Bar(object):
__slots__ = 'foo', 'bar' # alas, would work if empty, i.e. ()
>>> class Baz(Foo, Bar): pass
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
multiple bases have instance lay-out conflict
__slots__
Wydaje się, że użycie pustego elementu nadrzędnego zapewnia największą elastyczność, umożliwiając dziecku wybranie opcji zapobiegania lub zezwalania (poprzez dodanie w '__dict__'
celu uzyskania dynamicznego przypisania, patrz sekcja powyżej) utworzenie__dict__
:
class Foo(object): __slots__ = ()
class Bar(object): __slots__ = ()
class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
b = Baz()
b.foo, b.bar = 'foo', 'bar'
Nie musisz mieć slotów - więc jeśli je dodasz i usuniesz później, nie powinno to powodować żadnych problemów.
Idąc w opałach tutaj : Jeśli komponowania wstawek lub za pomocą abstrakcyjnych klas bazowych , które nie są przeznaczone do wystąpienia, pusty __slots__
w tych rodziców, wydaje się być najlepszym sposobem, aby przejść pod względem elastyczności subclassers.
Aby to zademonstrować, najpierw stwórzmy klasę z kodem, którego chcielibyśmy używać w ramach wielokrotnego dziedziczenia
class AbstractBase:
__slots__ = ()
def __init__(self, a, b):
self.a = a
self.b = b
def __repr__(self):
return f'{type(self).__name__}({repr(self.a)}, {repr(self.b)})'
Możemy użyć powyższego bezpośrednio, dziedzicząc i deklarując oczekiwane miejsca:
class Foo(AbstractBase):
__slots__ = 'a', 'b'
Ale nas to nie obchodzi, to trywialne pojedyncze dziedziczenie, potrzebujemy innej klasy, z której moglibyśmy dziedziczyć, może z hałaśliwym atrybutem:
class AbstractBaseC:
__slots__ = ()
@property
def c(self):
print('getting c!')
return self._c
@c.setter
def c(self, arg):
print('setting c!')
self._c = arg
Teraz, jeśli obie bazy miały niepuste gniazda, nie moglibyśmy wykonać poniższych czynności. (W rzeczywistości, gdybyśmy chcieli, moglibyśmy podać AbstractBase
niepuste przedziały aib i pozostawić je poza poniższą deklaracją - pozostawienie ich byłoby błędne):
class Concretion(AbstractBase, AbstractBaseC):
__slots__ = 'a b _c'.split()
A teraz mamy funkcjonalność zarówno poprzez wielokrotne dziedziczenie, jak i nadal możemy odmawiać __dict__
i __weakref__
tworzyć instancje:
>>> c = Concretion('a', 'b')
>>> c.c = c
setting c!
>>> c.c
getting c!
Concretion('a', 'b')
>>> c.d = 'd'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Concretion' object has no attribute 'd'
__class__
zadanie z inną klasą, która ich nie ma (i nie możesz ich dodać), chyba że układy miejsc są identyczne. (Jestem bardzo zainteresowany tym, kto to robi i dlaczego).Być może będziesz w stanie wysunąć dalsze zastrzeżenia z pozostałej części __slots__
dokumentacji (dokumenty deweloperów w wersji 3.7 są najbardziej aktualne) , do których w znacznym stopniu przyczyniłem się ostatnio.
Aktualne najlepsze odpowiedzi przytaczają nieaktualne informacje i są dość falujące i pomijają ten znak w ważnych sprawach.
__slots__
podczas tworzenia wielu obiektów”Cytuję:
„Chcesz użyć,
__slots__
jeśli chcesz utworzyć wiele (setki, tysiące) obiektów tej samej klasy”.
Abstrakcyjne klasy podstawowe, na przykład z collections
modułu, nie są tworzone, ale __slots__
są dla nich zadeklarowane.
Dlaczego?
Jeśli użytkownik chce zaprzeczyć __dict__
lub __weakref__
utworzyć, te rzeczy nie mogą być dostępne w klasach nadrzędnych.
__slots__
przyczynia się do ponownego użycia podczas tworzenia interfejsów lub miksów.
Prawdą jest, że wielu użytkowników Pythona nie pisze w celu ponownego użycia, ale kiedy to robisz, opcja odmowy niepotrzebnego wykorzystania miejsca jest cenna.
__slots__
nie przerywa wytrawianiaPodczas trawienia wycinanego obiektu może się wydawać, że narzeka wprowadzającym w błąd TypeError
:
>>> pickle.loads(pickle.dumps(f))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
To jest właściwie niepoprawne. Ta wiadomość pochodzi od najstarszego protokołu, który jest domyślny. Możesz wybrać najnowszy protokół z -1
argumentem. W Pythonie 2.7 byłoby to 2
(które zostało wprowadzone w 2.3), a w 3.6 tak 4
.
>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>
w Python 2.7:
>>> pickle.loads(pickle.dumps(f, 2))
<__main__.Foo object at 0x1129C770>
w Pythonie 3.6
>>> pickle.loads(pickle.dumps(f, 4))
<__main__.Foo object at 0x1129C770>
Chciałbym o tym pamiętać, ponieważ jest to rozwiązany problem.
Pierwszy akapit to w połowie krótkie wyjaśnienie, w połowie przewidywalne. Oto jedyna część, która faktycznie odpowiada na pytanie
Właściwe użycie
__slots__
to oszczędność miejsca w obiektach. Zamiast dynamicznego dyktowania, które pozwala dodawać atrybuty do obiektów w dowolnym momencie, istnieje statyczna struktura, która nie pozwala na dodawanie po utworzeniu. Oszczędza to narzut jednego dyktowania dla każdego obiektu, który korzysta ze szczelin
Druga połowa to pobożne życzenia, a poza tym:
Chociaż jest to czasem przydatna optymalizacja, byłoby całkowicie niepotrzebne, gdyby interpreter Pythona był wystarczająco dynamiczny, aby wymagał dyktowania tylko wtedy, gdy rzeczywiście istniały dodatki do obiektu.
Python faktycznie robi coś podobnego, tworząc tylko __dict__
wtedy, gdy jest dostępny, ale tworzenie wielu obiektów bez danych jest dość śmieszne.
Drugi akapit nadmiernie upraszcza i omija rzeczywiste powody, których należy unikać __slots__
. Poniżej nie jest prawdziwy powód do unikania automatów (z rzeczywistych powodów, zobacz resztę mojej odpowiedzi powyżej):
Zmieniają zachowanie obiektów, które mają gniazda, w sposób, który może być nadużywany przez maniaków sterowania i statycznych drobiazgów pisania.
Następnie omawia inne sposoby osiągnięcia tego perwersyjnego celu z Pythonem, nie omawiając nic wspólnego z tym __slots__
.
Trzeci akapit to bardziej pobożne życzenia. Razem jest to w większości nietypowa treść, której autor nawet nie napisał i przyczynia się do amunicji dla krytyków strony.
Utwórz niektóre normalne obiekty i obiekty szczelinowe:
>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()
Utwórz ich milion:
>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]
Sprawdź za pomocą guppy.hpy().heap()
:
>>> guppy.hpy().heap()
Partition of a set of 2028259 objects. Total size = 99763360 bytes.
Index Count % Size % Cumulative % Kind (class / dict of class)
0 1000000 49 64000000 64 64000000 64 __main__.Foo
1 169 0 16281480 16 80281480 80 list
2 1000000 49 16000000 16 96281480 97 __main__.Bar
3 12284 1 987472 1 97268952 97 str
...
Uzyskaj dostęp do zwykłych obiektów i ich __dict__
i sprawdź ponownie:
>>> for f in foos:
... f.__dict__
>>> guppy.hpy().heap()
Partition of a set of 3028258 objects. Total size = 379763480 bytes.
Index Count % Size % Cumulative % Kind (class / dict of class)
0 1000000 33 280000000 74 280000000 74 dict of __main__.Foo
1 1000000 33 64000000 17 344000000 91 __main__.Foo
2 169 0 16281480 4 360281480 95 list
3 1000000 33 16000000 4 376281480 99 __main__.Bar
4 12284 0 987472 0 377268952 99 str
...
Jest to zgodne z historią Pythona, od typów i klas Unifying w Pythonie 2.2
Jeśli podklasujesz typ wbudowany, do instancji automatycznie dodawane jest dodatkowe miejsce, aby pomieścić
__dict__
i__weakrefs__
. (To__dict__
nie jest inicjowane, dopóki go nie użyjesz, więc nie powinieneś martwić się o miejsce zajmowane przez pusty słownik dla każdej tworzonej instancji.) Jeśli nie potrzebujesz tej dodatkowej przestrzeni, możesz dodać wyrażenie „__slots__ = []
” do Twoja klasa.
__slots__
. Poważnie! Dziękuję Ci!
__slots__
około rok temu: github.com/python/cpython/pull/1819/files
Cytując Jacoba Hallena :
Właściwe użycie
__slots__
to oszczędność miejsca w obiektach. Zamiast dynamicznego dyktowania, które pozwala dodawać atrybuty do obiektów w dowolnym momencie, istnieje statyczna struktura, która nie pozwala na dodawanie po utworzeniu. [Takie zastosowanie__slots__
eliminuje narzut jednego dykta dla każdego obiektu.] Chociaż jest to czasem przydatna optymalizacja, byłoby zupełnie niepotrzebne, gdyby interpreter Pythona był wystarczająco dynamiczny, aby wymagał dyktowania tylko wtedy, gdy rzeczywiście istniały dodatki do obiekt.Niestety na automatach występuje efekt uboczny. Zmieniają zachowanie obiektów, które mają gniazda, w sposób, który może być nadużywany przez maniaków sterowania i statycznych drobiazgów pisania. To źle, ponieważ maniacy kontrolni powinni nadużywać metaklas, a statyczne pisanie czcionek powinno nadużywać dekoratorów, ponieważ w Pythonie powinien istnieć tylko jeden oczywisty sposób na zrobienie czegoś.
Sprawienie, by CPython był wystarczająco inteligentny, aby poradzić sobie z oszczędnością miejsca,
__slots__
jest dużym przedsięwzięciem i prawdopodobnie dlatego nie ma go na liście zmian dla P3k (jeszcze).
__slots__
nie rozwiązuje tych samych problemów, co pisanie statyczne. Na przykład w C ++ nie deklaracja zmiennej członka jest ograniczona, lecz przypisanie do niej zmiennej niezamierzonego typu (i wymuszonego kompilatora). Nie akceptuję korzystania __slots__
, tylko zainteresowany rozmową. Dzięki!
Będziesz chciał użyć, __slots__
jeśli zamierzasz utworzyć wiele (setki, tysiące) obiektów tej samej klasy. __slots__
istnieje tylko jako narzędzie do optymalizacji pamięci.
Odradza się stosowanie __slots__
do ograniczania tworzenia atrybutów.
Wytrawianie przedmiotów za pomocą __slots__
nie będzie działać z domyślnym (najstarszym) protokołem ; konieczne jest podanie późniejszej wersji.
Niektóre inne funkcje introspekcji Pythona również mogą zostać naruszone.
Każdy obiekt python ma __dict__
atrybut, który jest słownikiem zawierającym wszystkie inne atrybuty. np. kiedy wpiszesz self.attr
python faktycznie robi self.__dict__['attr']
. Jak możesz sobie wyobrazić używanie słownika do przechowywania atrybutu zajmuje trochę więcej miejsca i czasu na dostęp do niego.
Jednak podczas używania __slots__
dowolny obiekt utworzony dla tej klasy nie będzie miał __dict__
atrybutu. Zamiast tego dostęp do wszystkich atrybutów odbywa się bezpośrednio za pomocą wskaźników.
Jeśli więc chcesz mieć strukturę w stylu C, a nie pełnoprawną klasę, możesz jej użyć __slots__
do kompaktowania wielkości obiektów i skrócenia czasu dostępu do atrybutów. Dobrym przykładem jest klasa Point zawierająca atrybuty x & y. Jeśli masz dużo punktów, możesz spróbować użyć __slots__
, aby zaoszczędzić trochę pamięci.
__slots__
zdefiniowaną strukturą nie przypomina struktury w stylu C. Istnieją nazwy atrybutów odwzorowujące słownik na poziomie klasy na indeksy, w przeciwnym razie nie byłoby możliwe: class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)
Naprawdę uważam, że ta odpowiedź powinna zostać wyjaśniona (mogę to zrobić, jeśli chcesz). Nie jestem też pewien, czy instance.__hidden_attributes[instance.__class__[attrname]]
to jest szybsze niż instance.__dict__[attrname]
.
Oprócz innych odpowiedzi, oto przykład użycia __slots__
:
>>> class Test(object): #Must be new-style class!
... __slots__ = ['x', 'y']
...
>>> pt = Test()
>>> dir(pt)
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__',
'__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__slots__', '__str__', 'x', 'y']
>>> pt.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: x
>>> pt.x = 1
>>> pt.x
1
>>> pt.z = 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute 'z'
>>> pt.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__dict__'
>>> pt.__slots__
['x', 'y']
Tak więc, aby zaimplementować __slots__
, wymaga tylko dodatkowej linii (i uczynienia twojej klasy klasą nowego stylu, jeśli jeszcze nie jest). W ten sposób można pięciokrotnie zmniejszyć ślad pamięci tych klas , kosztem pisania niestandardowego kodu piklowania, jeśli i kiedy będzie to konieczne.
Sloty są bardzo przydatne dla wywołań biblioteki, aby wyeliminować „nazwaną metodę wysyłania” podczas wykonywania wywołań funkcji. Jest to wspomniane w dokumentacji SWIG . W przypadku bibliotek o wysokiej wydajności, które chcą zmniejszyć obciążenie funkcji w przypadku często nazywanych funkcji za pomocą gniazd, jest to znacznie szybsze.
Teraz może to nie być bezpośrednio związane z pytaniem PO. Jest bardziej związany z budowaniem rozszerzeń niż z użyciem składni szczelin na obiekcie. Ale pomaga uzupełnić obraz wykorzystania gniazd i niektóre uzasadnienia za nimi.
Atrybut instancji klasy ma 3 właściwości: instancję, nazwę atrybutu i wartość atrybutu.
W zwykłym dostępie do atrybutów instancja działa jak słownik, a nazwa atrybutu działa jako klucz w wartości wyszukiwania tego słownika.
instancja (atrybut) -> wartość
W dostępie __slots__ nazwa atrybutu działa jak słownik, a instancja jako klucz w wyszukiwaniu wartości w słowniku.
atrybut (instancja) -> wartość
We wzorze flyweight nazwa atrybutu działa jak słownik, a wartość działa jako klucz w tym słowniku, patrząc na instancję.
atrybut (wartość) -> instancja
__slots__
?
Bardzo prosty przykład __slot__
atrybutu.
__slots__
Jeśli nie mam __slot__
atrybutu w swojej klasie, mogę dodawać nowe atrybuty do moich obiektów.
class Test:
pass
obj1=Test()
obj2=Test()
print(obj1.__dict__) #--> {}
obj1.x=12
print(obj1.__dict__) # --> {'x': 12}
obj1.y=20
print(obj1.__dict__) # --> {'x': 12, 'y': 20}
obj2.x=99
print(obj2.__dict__) # --> {'x': 99}
Jeśli spojrzeć na powyższym przykładzie widać, że obj1 i obj2 mają własne x i y atrybuty i pyton stworzył również dict
atrybut dla każdego obiektu ( obj1 i obj2 ).
Załóżmy, że jeśli mój test klasy ma tysiące takich obiektów? Utworzenie dodatkowego atrybutu dict
dla każdego obiektu spowoduje duże obciążenie (pamięć, moc obliczeniowa itp.) W moim kodzie.
__slots__
Teraz w poniższym przykładzie test mojej klasy zawiera __slots__
atrybut. Teraz nie mogę dodawać nowych atrybutów do moich obiektów (oprócz atrybutu x
), a Python nie tworzy dict
już atrybutu. Eliminuje to narzut dla każdego obiektu, który może stać się znaczący, jeśli masz wiele obiektów.
class Test:
__slots__=("x")
obj1=Test()
obj2=Test()
obj1.x=12
print(obj1.x) # --> 12
obj2.x=99
print(obj2.x) # --> 99
obj1.y=28
print(obj1.y) # --> AttributeError: 'Test' object has no attribute 'y'
Innym, nieco niejasnym zastosowaniem __slots__
jest dodawanie atrybutów do obiektowego proxy z pakietu ProxyTypes, wcześniej części projektu PEAK. Jego ObjectWrapper
pozwala na pełnomocnika innego obiektu, ale osią wszystkich interakcji z proxy obiektu. Nie jest to bardzo często używane (i nie obsługuje Python 3), ale użyliśmy go do implementacji bezpiecznego blokowania wątków wokół implementacji asynchronicznej opartej na tornado, która odbija cały dostęp do obiektu proxy przez ioloop, używając bezpiecznego wątku concurrent.Future
obiekty do synchronizacji i zwracania wyników.
Domyślnie dowolny dostęp do atrybutu do obiektu proxy daje wynik z obiektu proxy. Jeśli chcesz dodać atrybut do obiektu proxy, __slots__
możesz go użyć.
from peak.util.proxies import ObjectWrapper
class Original(object):
def __init__(self):
self.name = 'The Original'
class ProxyOriginal(ObjectWrapper):
__slots__ = ['proxy_name']
def __init__(self, subject, proxy_name):
# proxy_info attributed added directly to the
# Original instance, not the ProxyOriginal instance
self.proxy_info = 'You are proxied by {}'.format(proxy_name)
# proxy_name added to ProxyOriginal instance, since it is
# defined in __slots__
self.proxy_name = proxy_name
super(ProxyOriginal, self).__init__(subject)
if __name__ == "__main__":
original = Original()
proxy = ProxyOriginal(original, 'Proxy Overlord')
# Both statements print "The Original"
print "original.name: ", original.name
print "proxy.name: ", proxy.name
# Both statements below print
# "You are proxied by Proxy Overlord", since the ProxyOriginal
# __init__ sets it to the original object
print "original.proxy_info: ", original.proxy_info
print "proxy.proxy_info: ", proxy.proxy_info
# prints "Proxy Overlord"
print "proxy.proxy_name: ", proxy.proxy_name
# Raises AttributeError since proxy_name is only set on
# the proxy object
print "original.proxy_name: ", proxy.proxy_name
Zasadniczo nie masz pożytku __slots__
.
W chwili, gdy wydaje ci się, że możesz potrzebować __slots__
, faktycznie chcesz użyć wzorów lekkich lub Flyweight . Są to przypadki, gdy nie chcesz już używać obiektów czysto Pythona. Zamiast tego potrzebujesz obiektowego opakowania w języku Python wokół tablicy, struktury lub tablicy numpy.
class Flyweight(object):
def get(self, theData, index):
return theData[index]
def set(self, theData, index, value):
theData[index]= value
Opakowanie podobne do klasy nie ma atrybutów - zapewnia jedynie metody, które działają na dane bazowe. Metody można sprowadzić do metod klasowych. Rzeczywiście, można to sprowadzić do samych funkcji działających na bazowej tablicy danych.
__slots__
?
__slots__
obie techniki optymalizacji w celu oszczędzania pamięci. __slots__
pokazuje korzyści, gdy masz wiele obiektów, a także wzór wzornictwa Flyweight. Oba rozwiązują ten sam problem.
__slots__
naprawdę jest odpowiedzią i jak zauważa Evgeni, można ją dodać jako prostą refleksję (np. Możesz najpierw skupić się na poprawności, a następnie zwiększyć wydajność).
Pierwotne pytanie dotyczyło ogólnych przypadków użycia nie tylko pamięci. Należy tutaj wspomnieć, że uzyskuje się także lepszą wydajność podczas tworzenia dużych ilości obiektów - co jest interesujące np. Podczas analizowania dużych dokumentów w obiektach lub z bazy danych.
Oto porównanie tworzenia drzew obiektów z milionem wpisów, przy użyciu szczelin i bez szczelin. Jako odniesienie również wydajność przy użyciu zwykłych dykt dla drzew (Py2.7.10 w OSX):
********** RUN 1 **********
1.96036410332 <class 'css_tree_select.element.Element'>
3.02922606468 <class 'css_tree_select.element.ElementNoSlots'>
2.90828204155 dict
********** RUN 2 **********
1.77050495148 <class 'css_tree_select.element.Element'>
3.10655999184 <class 'css_tree_select.element.ElementNoSlots'>
2.84120798111 dict
********** RUN 3 **********
1.84069895744 <class 'css_tree_select.element.Element'>
3.21540498734 <class 'css_tree_select.element.ElementNoSlots'>
2.59615707397 dict
********** RUN 4 **********
1.75041103363 <class 'css_tree_select.element.Element'>
3.17366290092 <class 'css_tree_select.element.ElementNoSlots'>
2.70941114426 dict
Klasy testowe (ident, appart from slots):
class Element(object):
__slots__ = ['_typ', 'id', 'parent', 'childs']
def __init__(self, typ, id, parent=None):
self._typ = typ
self.id = id
self.childs = []
if parent:
self.parent = parent
parent.childs.append(self)
class ElementNoSlots(object): (same, w/o slots)
kod testowy, pełny tryb:
na, nb, nc = 100, 100, 100
for i in (1, 2, 3, 4):
print '*' * 10, 'RUN', i, '*' * 10
# tree with slot and no slot:
for cls in Element, ElementNoSlots:
t1 = time.time()
root = cls('root', 'root')
for i in xrange(na):
ela = cls(typ='a', id=i, parent=root)
for j in xrange(nb):
elb = cls(typ='b', id=(i, j), parent=ela)
for k in xrange(nc):
elc = cls(typ='c', id=(i, j, k), parent=elb)
to = time.time() - t1
print to, cls
del root
# ref: tree with dicts only:
t1 = time.time()
droot = {'childs': []}
for i in xrange(na):
ela = {'typ': 'a', id: i, 'childs': []}
droot['childs'].append(ela)
for j in xrange(nb):
elb = {'typ': 'b', id: (i, j), 'childs': []}
ela['childs'].append(elb)
for k in xrange(nc):
elc = {'typ': 'c', id: (i, j, k), 'childs': []}
elb['childs'].append(elc)
td = time.time() - t1
print td, 'dict'
del droot
class Child(BaseA, BaseB): __slots__ = ('a', 'b')
przykładu z rodzicami empy-slot. Dlaczego tutajdictproxy
stworzono zamiast zbieraćAttributeError
forc
?