Jawaban:
Dengan Python, apa tujuan
__slots__
dan kasus apa yang harus dihindari seseorang?
Atribut khusus __slots__
memungkinkan Anda untuk secara eksplisit menyatakan atribut instance yang Anda harapkan dari instance objek Anda, dengan hasil yang diharapkan:
Penghematan ruang berasal
__dict__
.__dict__
dan membuat __weakref__
jika kelas induk menolaknya dan Anda menyatakan __slots__
.Peringatan kecil, Anda hanya boleh mendeklarasikan slot tertentu satu kali di pohon warisan. Sebagai contoh:
class Base:
__slots__ = 'foo', 'bar'
class Right(Base):
__slots__ = 'baz',
class Wrong(Base):
__slots__ = 'foo', 'bar', 'baz' # redundant foo and bar
Python tidak keberatan ketika Anda melakukan kesalahan ini (mungkin seharusnya), masalah mungkin tidak dinyatakan, tetapi objek Anda akan memakan lebih banyak ruang daripada yang seharusnya. Python 3.8:
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
Ini karena deskriptor slot Base memiliki slot terpisah dari Wrong's. Ini biasanya tidak muncul, tetapi bisa:
>>> 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'
Peringatan terbesar adalah untuk beberapa warisan - beberapa "kelas induk dengan slot kosong" tidak dapat digabungkan.
Untuk mengakomodasi pembatasan ini, ikuti praktik terbaik: Faktorkan semua kecuali satu atau semua abstraksi orang tua yang masing-masing kelas konkretnya dan kelas beton baru Anda akan diwarisi dari - memberikan slot kosong abstraksi (seperti kelas dasar abstrak di kelas). perpustakaan standar).
Lihat bagian tentang beberapa warisan di bawah untuk contoh.
Agar atribut yang dinamai __slots__
benar-benar disimpan dalam slot, bukan a __dict__
, kelas harus mewarisi darinya object
.
Untuk mencegah pembuatan a __dict__
, Anda harus mewarisi dari object
dan semua kelas dalam pewarisan harus menyatakan __slots__
dan tidak ada dari mereka yang dapat memiliki '__dict__'
entri.
Ada banyak detail jika Anda ingin terus membaca.
__slots__
: Akses atribut lebih cepat.Pencipta Python, Guido van Rossum, menyatakan bahwa ia sebenarnya diciptakan __slots__
untuk akses atribut yang lebih cepat.
Itu sepele untuk menunjukkan akses cepat signifikan yang terukur:
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
dan
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
Akses yang ditempatkan hampir 30% lebih cepat di Python 3.5 di Ubuntu.
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
Dalam Python 2 pada Windows saya mengukurnya sekitar 15% lebih cepat.
__slots__
: Penghematan MemoriTujuan lain __slots__
adalah untuk mengurangi ruang dalam memori yang digunakan oleh setiap instance objek.
Kontribusi saya sendiri untuk dokumentasi dengan jelas menyatakan alasan di balik ini :
Ruang yang dihemat saat menggunakan
__dict__
bisa signifikan.
SQLAlchemy menghubungkan banyak penghematan memori __slots__
.
Untuk memverifikasi ini, gunakan distribusi Anaconda dari Python 2.7 di Ubuntu Linux, dengan guppy.hpy
(alias heapy) dan sys.getsizeof
, ukuran instance kelas tanpa __slots__
dideklarasikan, dan tidak ada yang lain, adalah 64 byte. Itu tidak termasuk __dict__
. Terima kasih Python untuk evaluasi malas lagi, yang __dict__
tampaknya tidak dipanggil sebelum direferensikan, tetapi kelas tanpa data biasanya tidak berguna. Ketika dipanggil, __dict__
atribut adalah minimum 280 byte.
Sebaliknya, instance kelas dengan __slots__
dinyatakan sebagai ()
(tidak ada data) hanya 16 byte, dan 56 total byte dengan satu item dalam slot, 64 dengan dua.
Untuk 64 bit Python, saya menggambarkan konsumsi memori dalam byte di Python 2.7 dan 3.6, untuk __slots__
dan __dict__
(tidak ada slot yang ditentukan) untuk setiap titik di mana dikt tumbuh dalam 3,6 (kecuali untuk atribut 0, 1, dan 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
Jadi, meskipun ada dikte kecil di Python 3, kita melihat seberapa baik __slots__
skala untuk contoh menghemat memori kita, dan itulah alasan utama yang ingin Anda gunakan __slots__
.
Hanya untuk melengkapi catatan saya, perhatikan bahwa ada biaya satu kali per slot di namespace kelas dari 64 byte di Python 2, dan 72 byte di Python 3, karena slot menggunakan deskriptor data seperti properti, yang disebut "anggota".
>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
__slots__
:Untuk menolak pembuatan a __dict__
, Anda harus subkelas object
:
class Base(object):
__slots__ = ()
sekarang:
>>> 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'
Atau subkelas kelas lain yang mendefinisikan __slots__
class Child(Base):
__slots__ = ('a',)
dan sekarang:
c = Child()
c.a = 'a'
tapi:
>>> 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'
Untuk mengizinkan __dict__
kreasi sambil mensubklasifikasikan objek yang ditempatkan, cukup tambahkan '__dict__'
ke __slots__
(perhatikan bahwa slot dipesan, dan Anda tidak boleh mengulangi slot yang sudah ada di kelas induk):
class SlottedWithDict(Child):
__slots__ = ('__dict__', 'b')
swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'
dan
>>> swd.__dict__
{'c': 'c'}
Atau Anda bahkan tidak perlu mendeklarasikan __slots__
di subclass Anda, dan Anda masih akan menggunakan slot dari orang tua, tetapi tidak membatasi penciptaan __dict__
:
class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'
Dan:
>>> ns.__dict__
{'b': 'b'}
Namun, __slots__
dapat menyebabkan masalah bagi banyak warisan:
class BaseA(object):
__slots__ = ('a',)
class BaseB(object):
__slots__ = ('b',)
Karena membuat kelas anak dari orang tua dengan kedua slot tidak kosong gagal:
>>> 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
Jika Anda mengalami masalah ini, Anda bisa menghapus __slots__
dari orang tua, atau jika Anda memiliki kendali atas orang tua, beri mereka slot kosong, atau refactor untuk abstraksi:
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__'
ke __slots__
untuk mendapatkan penugasan dinamis:class Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
dan sekarang:
>>> foo = Foo()
>>> foo.boink = 'boink'
Jadi dengan '__dict__'
slot kami kehilangan beberapa manfaat ukuran dengan sisi positif memiliki tugas dinamis dan masih memiliki slot untuk nama yang kami harapkan.
Saat Anda mewarisi dari objek yang tidak ditempatkan, Anda mendapatkan jenis semantik yang sama saat Anda menggunakan __slots__
- nama yang __slots__
mengarah ke nilai yang ditempatkan, sementara nilai lainnya diletakkan di instance __dict__
.
Menghindari __slots__
karena Anda ingin dapat menambahkan atribut dengan cepat sebenarnya bukan alasan yang baik - tambahkan saja "__dict__"
ke Anda __slots__
jika ini diperlukan.
Anda sama dapat menambahkan __weakref__
untuk __slots__
secara eksplisit jika Anda perlu fitur itu.
Namanya builtup membuat contoh abadi yang sangat ringan (pada dasarnya, ukuran tupel) tetapi untuk mendapatkan manfaatnya, Anda perlu melakukannya sendiri jika Anda mensubklasifikasikan:
from collections import namedtuple
class MyNT(namedtuple('MyNT', 'bar baz')):
"""MyNT is an immutable and lightweight object"""
__slots__ = ()
pemakaian:
>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'
Dan mencoba menetapkan atribut yang tidak terduga memunculkan sebuah AttributeError
karena kami telah mencegah pembuatan __dict__
:
>>> nt.quux = 'quux'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'
Anda dapat mengizinkan __dict__
pembuatan dengan meninggalkan __slots__ = ()
, tetapi Anda tidak dapat menggunakan non-kosong __slots__
dengan subtipe tuple.
Bahkan ketika slot tidak kosong sama untuk beberapa orang tua, slot tersebut tidak dapat digunakan bersama:
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
Menggunakan kosong __slots__
di induk tampaknya memberikan fleksibilitas yang paling, memungkinkan anak untuk memilih untuk mencegah atau mengizinkan (dengan menambahkan '__dict__'
untuk mendapatkan tugas yang dinamis, lihat bagian di atas) penciptaan__dict__
:
class Foo(object): __slots__ = ()
class Bar(object): __slots__ = ()
class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
b = Baz()
b.foo, b.bar = 'foo', 'bar'
Anda tidak harus memiliki slot - jadi jika Anda menambahkannya, dan menghapusnya nanti, itu tidak akan menyebabkan masalah.
Keluar mengambil risiko di sini : Jika Anda membuat mixin atau menggunakan kelas dasar abstrak , yang tidak dimaksudkan untuk dipakai, kosong __slots__
pada orang tua itu tampaknya menjadi cara terbaik untuk pergi dalam hal fleksibilitas untuk subclasser.
Untuk mendemonstrasikan, pertama, mari kita buat kelas dengan kode yang ingin kita gunakan di bawah banyak pewarisan
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)})'
Kita dapat menggunakan hal di atas secara langsung dengan mewarisi dan mendeklarasikan slot yang diharapkan:
class Foo(AbstractBase):
__slots__ = 'a', 'b'
Tapi kami tidak peduli tentang itu, itu warisan tunggal yang sepele, kami membutuhkan kelas lain yang mungkin juga kami warisi, mungkin dengan atribut berisik:
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
Sekarang jika kedua pangkalan memiliki slot kosong, kami tidak bisa melakukan di bawah ini. (Faktanya, jika kita mau, kita bisa memberikan AbstractBase
slot kosong a dan b, dan meninggalkannya dari deklarasi di bawah ini - membiarkannya salah):
class Concretion(AbstractBase, AbstractBaseC):
__slots__ = 'a b _c'.split()
Dan sekarang kami memiliki fungsionalitas dari keduanya melalui multiple inheritance, dan masih dapat menolak __dict__
dan membuat __weakref__
instance:
>>> 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__
tugas dengan kelas lain yang tidak memilikinya (dan Anda tidak dapat menambahkannya) kecuali tata letak slot identik. (Saya sangat tertarik mempelajari siapa yang melakukan ini dan mengapa.)Anda mungkin dapat menarik peringatan lebih lanjut dari sisa __slots__
dokumentasi (dokumentasi 3,7 dev adalah yang terbaru) , yang telah saya berikan kontribusi signifikan baru-baru ini.
Jawaban teratas saat ini mengutip informasi yang sudah ketinggalan zaman dan sangat bergelombang dan kehilangan tanda dalam beberapa hal penting.
__slots__
saat instantiasi banyak objek"Saya mengutip:
"Kamu ingin menggunakannya
__slots__
jika kamu akan membuat banyak (ratusan, ribuan) objek dari kelas yang sama."
Abstrak Kelas Dasar, misalnya, dari collections
modul, tidak dipakai, tetapi __slots__
dideklarasikan untuk mereka.
Mengapa?
Jika pengguna ingin menolak __dict__
atau membuat __weakref__
, hal-hal itu tidak harus tersedia di kelas induk.
__slots__
berkontribusi terhadap usabilitas ulang ketika membuat antarmuka atau mixin.
Memang benar bahwa banyak pengguna Python tidak menulis untuk digunakan kembali, tetapi ketika Anda, memiliki opsi untuk menolak penggunaan ruang yang tidak perlu sangat berharga.
__slots__
tidak merusak acarSaat memilih objek yang ditempatkan, Anda mungkin akan mengeluhkannya dengan menyesatkan TypeError
:
>>> pickle.loads(pickle.dumps(f))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
Ini sebenarnya salah. Pesan ini berasal dari protokol terlama, yang merupakan default. Anda dapat memilih protokol terbaru dengan -1
argumen. Dalam Python 2.7 ini akan menjadi 2
(yang diperkenalkan pada 2.3), dan dalam 3.6 itu 4
.
>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>
dalam Python 2.7:
>>> pickle.loads(pickle.dumps(f, 2))
<__main__.Foo object at 0x1129C770>
dalam Python 3.6
>>> pickle.loads(pickle.dumps(f, 4))
<__main__.Foo object at 0x1129C770>
Jadi saya akan mengingat ini, karena ini adalah masalah yang terpecahkan.
Paragraf pertama adalah setengah penjelasan singkat, setengah prediksi. Inilah satu-satunya bagian yang benar-benar menjawab pertanyaan itu
Penggunaan yang tepat
__slots__
adalah untuk menghemat ruang pada objek. Alih-alih memiliki dict dinamis yang memungkinkan menambahkan atribut ke objek kapan saja, ada struktur statis yang tidak memungkinkan penambahan setelah pembuatan. Ini menghemat overhead satu dict untuk setiap objek yang menggunakan slot
Babak kedua adalah angan-angan, dan melenceng:
Meskipun ini kadang-kadang optimasi yang berguna, itu akan sama sekali tidak perlu jika juru bahasa Python cukup dinamis sehingga hanya akan membutuhkan dict ketika sebenarnya ada penambahan objek.
Python sebenarnya melakukan sesuatu yang mirip dengan ini, hanya membuat __dict__
ketika diakses, tetapi membuat banyak objek tanpa data cukup konyol.
Paragraf kedua terlalu menyederhanakan dan melewatkan alasan sebenarnya untuk dihindari __slots__
. Di bawah ini bukan alasan sebenarnya untuk menghindari slot (untuk alasan yang sebenarnya , lihat sisa jawaban saya di atas.):
Mereka mengubah perilaku objek yang memiliki slot dengan cara yang dapat disalahgunakan oleh orang aneh kontrol dan alat pengetik statis.
Kemudian dilanjutkan dengan membahas cara-cara lain untuk mencapai tujuan jahat itu dengan Python, tidak membahas apa pun yang berhubungan dengan __slots__
.
Paragraf ketiga adalah angan-angan. Bersama-sama sebagian besar konten yang tidak pantas bahwa penjawab bahkan tidak menulis dan berkontribusi untuk amunisi untuk kritik dari situs.
Buat beberapa objek normal dan objek berlubang:
>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()
Buat sejuta dari mereka:
>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]
Periksa dengan 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
...
Akses benda-benda biasa dan benda-benda itu __dict__
dan periksa lagi:
>>> 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
...
Ini konsisten dengan sejarah Python, dari tipe dan kelas Unifying di Python 2.2
Jika Anda mensubklasifikasikan tipe bawaan, ruang tambahan ditambahkan secara otomatis ke instance untuk menampung
__dict__
dan__weakrefs__
. (Meskipun__dict__
tidak diinisialisasi sampai Anda menggunakannya, jadi Anda tidak perlu khawatir tentang ruang yang ditempati oleh kamus kosong untuk setiap contoh yang Anda buat.) Jika Anda tidak membutuhkan ruang tambahan ini, Anda dapat menambahkan frasa "__slots__ = []
" ke kelasmu.
__slots__
. Serius! Terima kasih!
__slots__
sekitar setahun yang lalu: github.com/python/cpython/pull/1819/files
Mengutip Jacob Hallen :
Penggunaan yang tepat
__slots__
adalah untuk menghemat ruang pada objek. Alih-alih memiliki dict dinamis yang memungkinkan menambahkan atribut ke objek kapan saja, ada struktur statis yang tidak memungkinkan penambahan setelah pembuatan. [Penggunaan ini__slots__
menghilangkan overhead satu dict untuk setiap objek.] Meskipun ini kadang-kadang optimasi yang berguna, itu akan sama sekali tidak perlu jika penerjemah Python cukup dinamis sehingga hanya akan membutuhkan dict ketika sebenarnya ada penambahan ke obyek.Sayangnya ada efek samping pada slot. Mereka mengubah perilaku objek yang memiliki slot dengan cara yang dapat disalahgunakan oleh orang aneh kontrol dan alat pengetik statis. Ini buruk, karena orang-orang aneh yang mengontrol harus menyalahgunakan metaclasses dan cara mengetik yang statis harus menyalahgunakan dekorator, karena dengan Python, seharusnya hanya ada satu cara yang jelas untuk melakukan sesuatu.
Membuat CPython cukup pintar untuk menangani penghematan ruang tanpa
__slots__
adalah upaya besar, yang mungkin mengapa itu tidak ada dalam daftar perubahan untuk P3k (belum).
__slots__
tidak mengatasi masalah yang sama dengan mengetik statis. Sebagai contoh, dalam C ++, itu bukan deklarasi variabel anggota sedang dibatasi, itu adalah penugasan jenis yang tidak diinginkan (dan kompiler ditegakkan) untuk variabel itu. Saya tidak memaafkan penggunaan __slots__
, hanya tertarik pada percakapan. Terima kasih!
Anda ingin menggunakan __slots__
jika Anda akan instantiate banyak (ratusan, ribuan) objek dari kelas yang sama. __slots__
hanya ada sebagai alat optimisasi memori.
Sangat tidak disarankan untuk digunakan __slots__
untuk membatasi pembuatan atribut.
Objek pengawetan dengan __slots__
tidak akan bekerja dengan protokol acar default (terlama); perlu untuk menentukan versi yang lebih baru.
Beberapa fitur introspeksi lain dari python juga dapat terpengaruh.
Setiap objek python memiliki __dict__
atribut yang merupakan kamus yang berisi semua atribut lainnya. misal ketika Anda mengetik self.attr
python sebenarnya sedang melakukan self.__dict__['attr']
. Seperti yang dapat Anda bayangkan menggunakan atribut dictionary to store membutuhkan ruang & waktu ekstra untuk mengaksesnya.
Namun, ketika Anda menggunakan __slots__
, objek apa pun yang dibuat untuk kelas itu tidak akan memiliki __dict__
atribut. Sebagai gantinya, semua akses atribut dilakukan secara langsung melalui pointer.
Jadi, jika menginginkan struktur gaya C daripada kelas penuh yang dapat Anda gunakan __slots__
untuk memadatkan ukuran objek & mengurangi waktu akses atribut. Contoh yang baik adalah kelas Point yang berisi atribut x & y. Jika Anda memiliki banyak poin, Anda dapat mencoba menggunakan __slots__
untuk menghemat beberapa memori.
__slots__
didefinisikan tidak seperti struktur gaya-C. Ada nama atribut pemetaan peta tingkat kamus untuk indeks, kalau tidak, berikut ini tidak akan mungkin: class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)
Saya benar-benar berpikir jawaban ini harus diklarifikasi (saya bisa melakukannya jika Anda mau). Juga, saya tidak yakin itu instance.__hidden_attributes[instance.__class__[attrname]]
lebih cepat dari instance.__dict__[attrname]
.
Selain jawaban lain, berikut adalah contoh penggunaan __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']
Jadi, untuk mengimplementasikannya __slots__
, itu hanya membutuhkan garis tambahan (dan membuat kelas Anda menjadi kelas gaya baru jika belum). Dengan cara ini Anda dapat mengurangi jejak memori kelas-kelas tersebut 5 kali lipat , dengan mengorbankan harus menulis kode acar khusus, jika dan ketika itu menjadi perlu.
Slot sangat berguna untuk panggilan pustaka untuk menghilangkan "metode pengiriman bernama" saat melakukan panggilan fungsi. Ini disebutkan dalam dokumentasi SWIG . Untuk perpustakaan berkinerja tinggi yang ingin mengurangi fungsi overhead untuk fungsi yang biasa disebut menggunakan slot jauh lebih cepat.
Sekarang ini mungkin tidak berhubungan langsung dengan pertanyaan OP. Ini lebih terkait dengan membangun ekstensi daripada menggunakan sintaks slot pada objek. Tapi itu membantu melengkapi gambar untuk penggunaan slot dan beberapa alasan di baliknya.
Atribut instance kelas memiliki 3 properti: instance, nama atribut, dan nilai atribut.
Dalam akses atribut biasa , instance bertindak sebagai kamus dan nama atribut bertindak sebagai kunci dalam kamus yang mencari nilai.
instance (atribut) -> nilai
Dalam akses __slots__ , nama atribut bertindak sebagai kamus dan instance bertindak sebagai kunci dalam kamus mencari nilai.
atribut (instance) -> nilai
Dalam pola kelas terbang , nama atribut bertindak sebagai kamus dan nilainya bertindak sebagai kunci dalam kamus yang mencari contoh.
atribut (nilai) -> instance
__slots__
?
Contoh __slot__
atribut yang sangat sederhana .
__slots__
Jika saya tidak memiliki __slot__
atribut di kelas saya, saya bisa menambahkan atribut baru ke objek saya.
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}
Jika Anda melihat contoh di atas, Anda dapat melihat bahwa obj1 dan obj2 memiliki atribut x dan y sendiri dan python juga telah membuat dict
atribut untuk setiap objek ( obj1 dan obj2 ).
Misalkan jika Tes kelas saya memiliki ribuan objek seperti itu? Membuat atribut tambahan dict
untuk setiap objek akan menyebabkan banyak overhead (memori, daya komputasi dll) dalam kode saya.
__slots__
Sekarang dalam contoh berikut Tes kelas saya berisi __slots__
atribut. Sekarang saya tidak bisa menambahkan atribut baru ke objek saya (kecuali atribut x
) dan python tidak membuat dict
atribut lagi. Ini menghilangkan overhead untuk setiap objek, yang dapat menjadi signifikan jika Anda memiliki banyak objek.
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'
Penggunaan lain yang agak kabur __slots__
adalah untuk menambahkan atribut ke objek proxy dari paket ProxyTypes, yang sebelumnya merupakan bagian dari proyek PEAK. Ini ObjectWrapper
memungkinkan Anda untuk mem-proxy objek lain, tetapi mencegat semua interaksi dengan objek yang diproksikan. Ini tidak terlalu umum digunakan (dan tidak ada dukungan Python 3), tetapi kami telah menggunakannya untuk mengimplementasikan pembungkus pemblokir aman-benang di sekitar implementasi async berdasarkan tornado yang memantulkan semua akses ke objek yang diproksikan melalui ioloop, menggunakan thread-safe concurrent.Future
objek untuk menyinkronkan dan mengembalikan hasil.
Secara default, akses atribut apa pun ke objek proxy akan memberi Anda hasil dari objek proksi. Jika Anda perlu menambahkan atribut pada objek proxy, __slots__
dapat digunakan.
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
Anda telah - pada dasarnya - tidak ada gunanya __slots__
.
Untuk saat Anda pikir Anda mungkin perlu __slots__
, Anda sebenarnya ingin menggunakan pola desain Ringan atau Terbang . Ini adalah kasus ketika Anda tidak lagi ingin menggunakan objek Python murni. Sebagai gantinya, Anda ingin pembungkus seperti objek Python di sekitar array, struct, atau array numpy.
class Flyweight(object):
def get(self, theData, index):
return theData[index]
def set(self, theData, index, value):
theData[index]= value
Wrapper seperti kelas tidak memiliki atribut - itu hanya menyediakan metode yang bertindak pada data yang mendasarinya. Metode dapat direduksi menjadi metode kelas. Memang, itu bisa dikurangi menjadi hanya fungsi yang beroperasi pada array data yang mendasarinya.
__slots__
?
__slots__
keduanya adalah teknik pengoptimalan untuk menghemat memori. __slots__
menunjukkan manfaat ketika Anda memiliki banyak banyak objek serta pola desain Flyweight. Keduanya memecahkan masalah yang sama.
__slots__
- kadang benar-benar jawabannya, dan seperti yang ditunjukkan Evgeni, itu dapat ditambahkan sebagai renungan sederhana (misalnya Anda dapat fokus pada kebenaran terlebih dahulu, dan kemudian menambahkan kinerja).
Pertanyaan aslinya adalah tentang kasus penggunaan umum tidak hanya tentang memori. Jadi harus disebutkan di sini bahwa Anda juga mendapatkan kinerja yang lebih baik ketika membuat instance objek dalam jumlah besar - menarik misalnya ketika mem-parsing dokumen besar menjadi objek atau dari database.
Berikut ini adalah perbandingan membuat pohon objek dengan sejuta entri, menggunakan slot dan tanpa slot. Sebagai referensi juga kinerja saat menggunakan dicts biasa untuk pohon-pohon (Py2.7.10 pada 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
Kelas uji (ident, appart dari slot):
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, mode verbose:
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')
contoh dengan orang tua slot-empy. Mengapa di sinidictproxy
dibuat alih-alih menaikkanAttributeError
untukc
?