Korzystanie z automatów?

Jaki jest cel __slots__ w Pythonie-szczególnie w odniesieniu do kiedy chciałbym go używać, a kiedy nie?

 525
Author: Mr_and_Mrs_D, 2009-01-23

11 answers

W Pythonie, jaki jest cel __slots__ i w jakich przypadkach należy tego unikać?

TLDR:

Specjalny atrybut __slots__ pozwala jawnie określić, jakich atrybutów oczekujesz od instancji obiektu, z oczekiwanymi rezultatami:

  1. szybszy dostęp do atrybutów.
  2. Oszczędność miejsca w pamięci.

Oszczędność miejsca jest z

  1. Przechowywanie odniesień do wartości w slotach zamiast __dict__.
  2. zaprzeczanie __dict__ i tworzenie __weakref__ jeśli klasy nadrzędne zaprzeczają im i deklarujesz __slots__.

Quick Caveats

Małe zastrzeżenie, należy zadeklarować konkretny slot tylko jeden raz w drzewie dziedziczenia. 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 źle to zrozumiałeś (prawdopodobnie powinno), problemy mogą się nie pojawić, ale twoje obiekty zajmą więcej przestrzeń niż w przeciwnym razie powinny.

>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(64, 80)

Największe zastrzeżenie dotyczy dziedziczenia wielokrotnego - nie można łączyć wielu "klas nadrzędnych z nieregulowanymi slotami".

Aby uwzględnić to ograniczenie, postępuj zgodnie z najlepszymi praktykami: Uwzględniaj wszystkie abstrakcje rodziców, z wyjątkiem jednej lub wszystkich abstrakcji rodziców, od których ich konkretna klasa i twoja nowa konkretna Klasa zbiorczo odziedziczą - dając abstrakcji puste miejsca (tak jak abstrakcyjne klasy bazowe w bibliotece standardowej).

Zobacz sekcję o dziedziczeniu wielokrotnym poniżej dla przykładu.

Wymagania:

  • Aby atrybuty nazwane w __slots__ były faktycznie przechowywane w slotach zamiast __dict__, klasa musi dziedziczyć z object.

  • Aby zapobiec tworzeniu __dict__, musisz dziedziczyć z object, a wszystkie klasy w dziedziczeniu muszą zadeklarować __slots__ i żadna z nich nie może mieć wpisu '__dict__'.

Istnieje wiele szczegóły jeśli chcesz czytać dalej.

Po co używać __slots__: szybszy dostęp do atrybutów.

Twórca Pythona, Guido van Rossum, stwierdza, że stworzył __slots__ dla szybszego dostępu do atrybutów.

Trywialne jest wykazanie wymiernie istotnego 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
W Pythonie 3.5 na Ubuntu dostęp szczelinowy jest o prawie 30% szybszy.
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342

W Pythonie 2 Na Windows zmierzyłem go o 15% szybciej.

Dlaczego używać __slots__: oszczędność pamięci

Innym celem __slots__ jest zmniejszenie przestrzeni w pamięci, którą zajmuje każda instancja obiektu.

Mój własny wkład w dokumentację jasno określa przyczyny tego :

Przestrzeń zaoszczędzona przez użycie __dict__ może być znacząca.

Atrybuty SQLAlchemy dużo oszczędności pamięci do __slots__.

Aby to zweryfikować, używając Anaconda Dystrybucja Pythona 2.7 na Ubuntu Linux, z guppy.hpy (aka heopy) i sys.getsizeof, Rozmiar instancji klasy bez __slots__ zadeklarowane, i nic więcej, jest 64 bajty. To nie zawiera __dict__. Jeszcze raz dziękuję Pythonowi za leniwą ocenę, __dict__ najwyraźniej nie jest wywoływany do istnienia, dopóki nie zostanie odwołany, ale klasy bez danych są zwykle bezużyteczne. Po wywołaniu atrybut __dict__ ma dodatkowo co najmniej 280 bajtów.

W natomiast instancja klasy z __slots__ zadeklarowaną jako () (brak danych) ma tylko 16 bajtów, a łącznie 56 bajtów z jednym elementem w slotach, 64 Z dwoma.

Dla Pythona 64-bitowego ilustruję zużycie pamięci w bajtach w Pythonie 2.7 i 3.6, Dla __slots__ i __dict__ (brak zdefiniowanych slotów) dla każdego punktu, w którym dict 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

Więc, pomimo mniejszych dictów w Pythonie 3, widzimy jak ładnie __slots__ skalować instancje, aby zapisać nam pamięć, i to jest główny powód, dla którego chciałbyś użyć __slots__.

Dla kompletności moich notatek, zauważ, że istnieje jednorazowy koszt na slot w przestrzeni nazw klasy wynoszący 64 bajty w Pythonie 2 i 72 bajty w Pythonie 3, ponieważ sloty używają deskryptorów danych, takich 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

Demonstracja __slots__:

Aby zaprzeczyć tworzeniu __dict__, musisz podklasować 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 podklasa innej klasy to definiuje __slots__

class Child(Base):
    __slots__ = ('a',)

A 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 umożliwić tworzenie __dict__ podczas podklasowania obiektów szczelinowych, po prostu dodaj '__dict__' do __slots__ (zauważ, że sloty są uporządkowane i nie powinieneś powtarzać slotów, 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 nawet nie musisz deklarować __slots__ w swojej podklasie, i nadal będziesz używać slotów od rodziców, ale nie ograniczaj tworzenia __dict__:

class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'

I:

>>> ns.__dict__
{'b': 'b'}
Jednak, __slots__ może powodować problemy dla wielokrotnego dziedziczenia:
class BaseA(object): 
    __slots__ = ('a',)

class BaseB(object): 
    __slots__ = ('b',)

Ponieważ tworzenie klasy dziecięcej od rodziców z obu niepustymi slotami nie powiodło się:

>>> 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, możeszpo prostu usunąć __slots__ z rodziców, lub jeśli masz kontrolę nad rodzicami, daj im puste miejsca lub refakturuj abstrakcje:
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!

Dodaj '__dict__' do __slots__ aby uzyskać dynamiczny przypisanie:

class Foo(object):
    __slots__ = 'bar', 'baz', '__dict__'

A teraz:

>>> foo = Foo()
>>> foo.boink = 'boink'

Tak więc z '__dict__' w slotach tracimy niektóre korzyści z wielkości z plusem posiadania dynamicznego przypisania i nadal posiadania slotów dla nazw, których oczekujemy.

Gdy dziedziczysz po obiekcie, który nie jest szczelinowy, otrzymujesz ten sam rodzaj semantyki, gdy używasz __slots__ - nazw, które są w __slots__, wskazują na wartości szczelinowe, podczas gdy wszelkie inne wartości są umieszczane w instancji __dict__.

Unikanie __slots__ ponieważ ty chcesz mieć możliwość dodawania atrybutów w locie, nie jest to dobry powód - wystarczy dodać {[88] } do swojego __slots__, jeśli jest to wymagane.

Możesz w podobny sposób dodać __weakref__ do __slots__ jawnie, jeśli potrzebujesz tej funkcji.

Ustawione na Empty tuple podczas podkategoryzowania nazwastuple:

Namedtuple builtin tworzy niezmienne instancje, które są bardzo lekkie (zasadniczo wielkości krotek), ale aby uzyskać korzyści, musisz zrobić to sam, jeśli podklasujesz oni:

from collections import namedtuple
class MyNT(namedtuple('MyNT', 'bar baz')):
    """MyNT is an immutable and lightweight object"""
    __slots__ = ()

Użycie:

>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'

I próba przypisania nieoczekiwanego atrybutu wywołuje AttributeError, ponieważ uniemożliwiliśmy utworzenie __dict__:

>>> nt.quux = 'quux'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'

Możesz zezwolić na tworzenie __dict__ pomijając __slots__ = (), ale nie możesz używać niepustych __slots__ Z podtypami krotki.

Największe Zastrzeżenie: dziedziczenie wielokrotne

Nawet jeśli niepuste miejsca są takie same dla wielu rodziców, nie mogą być używane 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

Używanie pustego __slots__ w rodzicu wydaje się zapewniać największą elastyczność, pozwalając dziecku wybrać, aby zapobiec lub zezwolić (dodając '__dict__', aby uzyskać dynamiczne przypisanie, patrz sekcja powyżej) tworzenie __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 masz aby mieć sloty - więc jeśli dodasz je i usuniesz później, nie powinno to sprawiać żadnych problemów.

: jeśli komponujesz mixinslub używanie abstrakcyjnych klas bazowych, które nie są przeznaczone do tworzenia instancji, puste __slots__ w tych rodzicach wydaje się być najlepszym sposobem pod względem elastyczności dla podklaserów.

Aby zademonstrować, najpierw stwórzmy klasę z kodem, którego chcemy użyć w dziedziczeniu wielokrotnym

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 sloty:]}
class Foo(AbstractBase):
    __slots__ = 'a', 'b'

Ale nie dbamy o to, to jest trywialne w przypadku dziedziczenia pojedynczego, potrzebujemy innej klasy, od której możemy również dziedziczyć, być 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 nonempty slotów, nie moglibyśmy zrobić poniżej. W rzeczywistości, gdybyśmy chcieli, moglibyśmy podać AbstractBase nonempty sloty a i b, i pominąć je z poniższej deklaracji - pozostawienie ich byłoby błędem): {]}

class Concretion(AbstractBase, AbstractBaseC):
    __slots__ = 'a b _c'.split()

I teraz mamy funkcjonalność z obu poprzez dziedziczenie wielokrotne, i nadal możemy zaprzeczyć __dict__ i __weakref__ instancjacja:

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

Inne przypadki unikania slotów:

  • unikaj ich, gdy chcesz wykonać __class__ przypisanie do innej klasy, która ich nie ma (i nie możesz ich dodać), chyba że układy gniazd są identyczne. (Jestem bardzo zainteresowany poznaniem, kto to robi i dlaczego.)
  • unikaj ich, jeśli chcesz podklasować zmienne długości wbudowane, takie jak long, tuple lub str, i chcesz dodać do nich atrybuty.
  • unikaj ich, jeśli nalegasz na dostarczanie wartości domyślnych za pomocą atrybutów klasy, na przykład zmiennych.

Możesz być w stanie dokuczyć innym zastrzeżeniom od reszty __slots__ dokumentacja (najbardziej aktualna jest dokumentacja 3.7 dev) , do której ostatnio wniosłem znaczący wkład.

Krytyka innych odpowiedzi

Aktualne top odpowiedzi cytują nieaktualne informacje i są dość faliste i chybiają znamienia w kilka ważnych sposobów.

Nie " używaj tylko __slots__ podczas tworzenia wielu obiektów "

Cytuję:

"chcesz użyć __slots__, jeśli masz zamiar utworzyć wiele (setki, tysiące) obiektów tej samej klasy."

Abstrakcyjne klasy bazowe, na przykład z modułu collections, nie są tworzone instancyjnie, ale __slots__ są dla nich deklarowane.

Dlaczego?

Jeśli użytkownik chce odmówić __dict__ lub __weakref__ tworzenia, te rzeczy nie mogą być dostępne w rodzicu klasy.

__slots__ przyczynia się do wielokrotnego użytku podczas tworzenia interfejsów lub mieszanek.

Prawdą jest, że wielu Użytkowników Pythona nie pisze o możliwości wielokrotnego użytku, ale gdy tak jest, posiadanie opcji odmowy niepotrzebnego wykorzystania przestrzeni jest cenne.

__slots__ nie łamie marynowania

Podczas wytrawiania obiektu szczelinowego można zauważyć, że narzeka on mylącym TypeError:

>>> pickle.loads(pickle.dumps(f))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled

To jest rzeczywiście niepoprawne. Ta wiadomość pochodzi z najstarszego protokołu, co jest domyślne. Możesz wybrać najnowszy protokół za pomocą argumentu -1. W Pythonie 2.7 byłoby to 2 (które zostało wprowadzone w 2.3), a w 3.6 jest to 4.

>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>

W Pythonie 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>

Więc chciałbym o tym pamiętać, ponieważ jest to rozwiązany problem.

Krytyka (do 2 października 2016) zaakceptowana odpowiedź

Pierwszy akapit to pół krótkie wyjaśnienie, pół predykcyjne. Oto jedyna część to rzeczywiście odpowiada na pytanie

Właściwym zastosowaniem {[39] } jest oszczędzanie miejsca w obiektach. Zamiast mieć dynamiczny dict, który pozwala na dodawanie atrybutów do obiektów w dowolnym momencie, istnieje statyczna struktura, która nie zezwala na dodawanie po utworzeniu. Zapisuje to narzut jednego dict dla każdego obiektu, który używa slotów.]}

Druga połowa to pobożne życzenia, a poza tym:

Chociaż jest to czasami przydatna optymalizacja, to byłoby to całkowicie niepotrzebne, gdyby interpreter Pythona był na tyle dynamiczny, że wymagałby dict tylko wtedy, gdy rzeczywiście istniały dodatki do obiektu.

Python faktycznie robi coś podobnego, tylko tworzenie __dict__ kiedy jest dostępny, ale tworzenie wielu obiektów bez danych jest dość śmieszne.

Drugi akapit upraszcza i pomija rzeczywiste powody, aby uniknąć __slots__. Poniżej jest nie prawdziwym powodem, aby uniknąć sloty (dla rzeczywistych powodów, Zobacz resztę mojej odpowiedzi powyżej.):

Zmieniają zachowanie obiektów, które mają szczeliny w sposób, który może być nadużywany przez maniaków kontroli i statyczne pisanie weenies.

Następnie omawia inne sposoby osiągnięcia tego przewrotnego celu za pomocą Pythona, nie dyskutując o niczym związanym z {39]}.

Trzeci akapit jest bardziej pobożnym życzeniem. Razem jest to głównie off-the-mark treści, które answer Nie był nawet autorem i przyczynił się do krytyki strony.

Dowód użycia pamięci

Tworzenie normalnych obiektów i obiektów szczelinowych:

>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()

Utworzyć milion z nich:

>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]

Zobacz z 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 unifikowanie typów i klas w Pythonie 2.2

Jeśli podklasujesz Typ wbudowany, dodatkowa przestrzeń jest automatycznie dodawana do instancji, aby pomieścić __dict__ i __weakrefs__. (__dict__ nie jest inicjowany, dopóki go nie użyjesz, więc nie powinieneś martwić się o przestrzeń zajmowaną przez pusty słownik dla każdego utworzonego wystąpienia.) Jeśli nie potrzebujesz tego dodatkowego miejsca, możesz dodać frazę "__slots__ = [] " do swojej klasy.

 576
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-26 12:46:20

Cytowanie Jacob Hallen :

Właściwym zastosowaniem {[0] } jest oszczędzanie miejsca w obiektach. Zamiast mieć dynamiczny dict umożliwiający dodawanie atrybutów do obiektów w dowolnym momencie, istnieje statyczna struktura, która nie pozwala na dodawanie po utworzeniu. [To użycie __slots__ eliminuje narzut jednego dicta dla każdego obiektu.] Chociaż czasami jest to przydatna optymalizacja, to byłaby całkowicie niepotrzebne, jeśli interpreter Pythona był na tyle dynamiczny, że wymagaj dict tylko wtedy, gdy rzeczywiście były dodatki do obiektu.

Niestety istnieje efekt uboczny slotów. Zmieniają zachowanie obiekty, które mają szczeliny w sposób, który może być nadużywany przez maniaków kontroli i statyczne pisanie weenies. To jest złe, bo maniacy kontroli powinni nadużywać metaklasy i statyczne wpisywanie weenies powinno nadużywać dekoratorzy, ponieważ w Pythonie powinien być tylko jeden oczywisty sposób zrobienia czegoś.

[[3]}uczynienie Cpythona wystarczająco inteligentnym, aby poradzić sobie z oszczędzaniem miejsca bez __slots__ jest głównym zobowiązanie, dlatego pewnie nie ma go na liście zmian dla P3k (jeszcze).
 258
Author: Jeff Bauer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-09 19:36:04

Chcesz użyć __slots__, jeśli masz zamiar utworzyć wiele (setki, tysiące) obiektów tej samej klasy. __slots__ istnieje tylko jako narzędzie do optymalizacji pamięci.

Jest wysoce odradzane używanie __slots__ do ograniczania tworzenia atrybutów, i ogólnie chcesz tego unikać, ponieważ łamie pickle, wraz z innymi funkcjami introspekcji Pythona.

 118
Author: Ryan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-23 05:50:21

Każdy obiekt Pythona mA __dict__ atttribute, który jest słownikiem zawierającym wszystkie inne atrybuty. np. gdy wpiszesz self.attr python faktycznie robi self.__dict__['attr']. Jak możesz sobie wyobrazić, używanie słownika do przechowywania atrybutów zajmuje trochę więcej miejsca i czasu na dostęp do niego.

Jednakże, gdy użyjesz __slots__, żaden obiekt utworzony dla tej klasy nie będzie miał atrybutu __dict__. Zamiast tego cały dostęp do atrybutów odbywa się bezpośrednio za pomocą wskaźników.

Więc jeśli chcesz mieć strukturę w stylu C, a nie PEŁNĄ fledged class można użyć __slots__ do zagęszczania rozmiarów obiektów i skracania czasu dostępu do atrybutów. Dobrym przykładem jest klasa punktowa zawierająca atrybuty x i Y. Jeśli masz zamiar mieć dużo punktów, możesz spróbować użyć __slots__ w celu zachowania pamięci.

 57
Author: Suraj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-05 08:39:53

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__, potrzeba tylko dodatkowej linii (i uczynienia twojej klasy klasą w Nowym Stylu, jeśli jeszcze nie jest). W ten sposób możesz zmniejszyć ślad pamięci tych klas 5-krotnie, kosztem konieczności pisania niestandardowego kodu pickle, jeśli i kiedy staje się to konieczne.

 18
Author: Evgeni Sergeev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-03 07:43:05

Sloty są bardzo przydatne dla wywołań biblioteki, aby wyeliminować "named method dispatch" podczas wykonywania wywołań funkcji. Jest to wspomniane w dokumentacji SWIG . W przypadku bibliotek o wysokiej wydajności, które chcą zmniejszyć narzut funkcji dla powszechnie nazywanych funkcji, korzystanie z gniazd jest znacznie szybsze.

Teraz to może nie być bezpośrednio związane z OPs pytanie. Jest to związane bardziej z budowaniem rozszerzeń niż z użyciem składni slotów na obiekcie. Ale to pomaga uzupełnij obraz korzystania z automatów i niektóre z uzasadnień za nimi.

 11
Author: Demolishun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-25 03:06:11

Atrybut instancji klasy ma 3 właściwości: instancję, nazwę atrybutu i wartość atrybutu.

In zwykły dostęp do atrybutów, instancja działa jak słownik, a nazwa atrybutu działa jak klucz w tym słowniku szukający wartości.

Instancja (atrybut) -- > wartość

In __sloty_ _ dostęp, nazwa atrybutu działa jak słownik, a instancja działa jak klucz w słownik szuka wartości.

Atrybut (instancja) -- > wartość

In wzór masy ciała, nazwa atrybutu działa jak słownik, a wartość działa jak klucz w tym słowniku wyszukujący instancję.

Atrybut (wartość) -- > instancja

 5
Author: Dmitry Rubanovich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-04 22:09:43

Nie masz - w zasadzie-pożytku z __slots__.

Na czas, kiedy wydaje ci się, że możesz potrzebować __slots__, w rzeczywistości chcesz użyć lekkichlub Flyweight wzorców projektowych. Są to przypadki, gdy nie chcesz już używać obiektów czysto Pythonowych. Zamiast tego chcesz, aby obiekt Pythona otaczał tablicę, strukturę lub tablicę numpy.

class Flyweight(object):

    def get(self, theData, index):
        return theData[index]

    def set(self, theData, index, value):
        theData[index]= value

Wrapper podobny do klasy nie ma atrybutów-dostarcza tylko metody, które działają na bazowych danych. Metody mogą być zredukowane do metod klasowych. Rzeczywiście, może być zredukowana do tylko funkcji działających na bazowej tablicy danych.

 2
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-24 14:11:08

Bardzo prosty przykład atrybutu __slot__.

Problem: Bez __slots__

Jeśli nie mam atrybutu __slot__ w mojej klasie, Mogę dodać 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 spojrzysz na powyższy przykład, zobaczysz, że obj1 i obj2 mają własne atrybuty x i y, a python stworzył również atrybut dict dla każdego obiektu (obj1 i obj2).

Załóżmy, że moja klasa Test ma tysiące takich obiektów? Utworzenie dodatkowego atrybutu dict dla każdego obiektu spowoduje duże obciążenie (pamięć, moc obliczeniowa itp.) w kodzie.

Rozwiązanie: Z __slots__

Teraz w poniższym przykładzie moja klasa Test zawiera __slots__ atrybut. Teraz nie mogę dodawać nowych atrybutów do moich obiektów (poza atrybutem x) i python nie tworzy już atrybutu dict. Eliminuje to obciążenie każdego obiektu, które może stać się znaczące jeśli masz wiele przedmiotó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'
 2
Author: N Randhawa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-09 19:30:52

Innym nieco niejasnym zastosowaniem __slots__ jest dodawanie atrybutów do Proxy obiektu z pakietu ProxyTypes, dawniej będącego częścią projektu PEAK. Its ObjectWrapper pozwala na proxy innego obiektu, ale przechwycić wszystkie interakcje z obiektem proxy. Nie jest zbyt często używany (i nie obsługuje Pythona 3), ale użyliśmy go do zaimplementowania bezpiecznego dla wątku opakowania blokującego wokół implementacji asynchronicznej opartej na tornado, która odbija cały dostęp do Proxy obiektu przez ioloop, używając thread-safe concurrent.Future obiekty do synchronizacji i zwracania wyników.

Domyślnie każdy atrybut dostępu do obiektu proxy daje wynik z obiektu proxy. Jeśli chcesz dodać atrybut do obiektu proxy, możesz użyć __slots__.

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
 2
Author: NeilenMarais,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-07 20:56:29

Pierwotne pytanie dotyczyło ogólnych przypadków użycia, a nie tylko pamięci. Należy więc wspomnieć tutaj, że można również uzyskać lepszą wydajność przy tworzeniu instancji dużych ilości obiektów - co ciekawe, np. podczas parsowania dużych dokumentów do obiektów lub z bazy danych.

Oto porównanie tworzenia drzew obiektów z milionem wpisów, za pomocą slotów i bez slotów. Jako odniesienie również wydajność przy użyciu zwykłych dicts dla drzew (Py2. 7. 10 na 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 z slotów):

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)

Testcode, verbose mode:

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
 0
Author: Red Pill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-12 18:42:31