Yanıtlar:
Python'da bunun amacı
__slots__
nedir ve bundan kaçınması gereken durumlar nelerdir?
Özel özellik __slots__
, nesne örneklerinizin olmasını beklediğiniz örnek niteliklerini, beklenen sonuçlarla açıkça belirtmenize olanak tanır:
Yerden tasarruf
__dict__
.__dict__
ve __weakref__
yaratım __slots__
.Küçük uyarı, bir miras ağacında sadece bir kez bir yuva beyan etmelisiniz. Örneğin:
class Base:
__slots__ = 'foo', 'bar'
class Right(Base):
__slots__ = 'baz',
class Wrong(Base):
__slots__ = 'foo', 'bar', 'baz' # redundant foo and bar
Python bunu yanlış yaptığınızda itiraz etmez (muhtemelen olması gerekir), problemler başka türlü ortaya çıkmayabilir, ancak nesneleriniz aksi takdirde olması gerekenden daha fazla yer kaplar. Python 3.8:
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
Bunun nedeni, Base'nin slot tanımlayıcısının Yanlışlardan ayrı bir slot olması. Bu genellikle ortaya çıkmamalı, ancak şunlar olabilir:
>>> 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'
En büyük uyarı çoklu miras içindir - çoklu "boş olmayan yuvaları olan ebeveyn sınıfları" birleştirilemez.
Bu kısıtlamaya uymak için en iyi uygulamaları takip edin: Sırasıyla beton sınıflarının ve yeni beton sınıfınızın toplu olarak miras alacağı bir veya tüm ebeveynlerin soyutlaması hariç hepsini dışarı çıkarın - soyutlamalara boş yuvalar vermek (tıpkı soyut temel sınıflar gibi) standart kütüphane).
Örnek için aşağıdaki çoklu miras bölümüne bakınız.
Olarak adlandırılan özniteliklerin __slots__
a yerine yuvalarda depolanması için __dict__
bir sınıfın miras alması gerekir object
.
Bir yaratılmasını önlemek için __dict__
, size devralınmalıdır object
ve miras tüm sınıfları beyan etmelidir __slots__
ve bunların hiçbiri bir olabilir '__dict__'
girdiyi.
Okumaya devam etmek istiyorsanız birçok ayrıntı var.
__slots__
: Daha hızlı özellik erişimi.Python'un yaratıcısı Guido van Rossum, aslında daha hızlı özellik erişimi için yarattığını belirtiyor__slots__
.
Ölçülebilir derecede daha hızlı erişim göstermek önemsizdir:
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
ve
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
Oluklu erişim Ubuntu'daki Python 3.5'te neredeyse% 30 daha hızlıdır.
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
Windows'taki Python 2'de yaklaşık% 15 daha hızlı ölçtüm.
__slots__
: Bellek TasarrufuDiğer bir amacı, __slots__
her bir nesne örneğinin kapladığı bellekteki alanı azaltmaktır.
Belgelere kendi katkım, bunun nedenlerini açıkça belirtiyor :
Kullanarak tasarruf edilen alan
__dict__
önemli olabilir.
SQLAlchemy çok fazla bellek tasarrufu sağlar __slots__
.
Bunu doğrulamak için, Ubuntu Linux'ta Python guppy.hpy
2.7'nin Anaconda dağılımını (aka heapy) ve bildirilmemiş sys.getsizeof
bir sınıf örneğinin boyutu __slots__
ve başka bir şey olmadan 64 bayt kullanılır. Yani yok değil bulunmaktadır __dict__
. Tembel değerlendirme için tekrar Python'a teşekkür ederiz __dict__
, referans verilene kadar varoluş olarak çağrılmaz, ancak veri içermeyen sınıflar genellikle işe yaramaz. Varoluş çağrıldığında, __dict__
öznitelik ek olarak minimum 280 bayttır.
Buna karşılık, (veri yok) __slots__
olarak bildirilen bir sınıf örneği ()
yalnızca 16 bayt ve yuvalarda bir öğe, 64 ikisinde olmak üzere toplam 56 bayttır.
64 bit Python için, diktinin 3.6'da büyüdüğü her nokta için (0, 1 ve 2 öznitelikler hariç) __slots__
ve Python 2.7 ve 3.6'daki bayt cinsinden bellek tüketimini gösterdim __dict__
:
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
Bu nedenle, Python 3'teki daha küçük dikmelere rağmen, __slots__
örneklerin bizi hafızadan kurtarmak için ne kadar güzel ölçeklendiğini görüyoruz ve bu kullanmak isteyebileceğiniz önemli bir nedendir __slots__
.
Sadece notlarımın eksiksiz olması için, sınıfın Python 2'de 64 baytlık ve Python 3'te 72 baytlık ad alanında bir kerelik bir maliyet olduğunu unutmayın, çünkü yuvalar "üyeler" adı verilen özellikler gibi veri tanımlayıcıları kullanır.
>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
__slots__
:A'nın oluşturulmasını reddetmek için __dict__
, alt sınıf object
:
class Base(object):
__slots__ = ()
Şimdi:
>>> 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'
Veya başka bir sınıfın __slots__
class Child(Base):
__slots__ = ('a',)
ve şimdi:
c = Child()
c.a = 'a'
fakat:
>>> 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'
İzin vermek için __dict__
sadece eklemek, oluklu nesneleri sınıflara ise oluşturulmasını '__dict__'
için __slots__
(slot sıralanır o notta, ve yapmanız gerekir ebeveyn sınıfları zaten değil tekrar yuvaları):
class SlottedWithDict(Child):
__slots__ = ('__dict__', 'b')
swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'
ve
>>> swd.__dict__
{'c': 'c'}
Veya __slots__
alt sınıfınızda beyan etmeniz bile gerekmez ve yine de ebeveynlerden yuvalar kullanırsınız, ancak aşağıdakilerin oluşturulmasını kısıtlamazsınız __dict__
:
class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'
Ve:
>>> ns.__dict__
{'b': 'b'}
Ancak, __slots__
birden fazla miras için sorunlara neden olabilir:
class BaseA(object):
__slots__ = ('a',)
class BaseB(object):
__slots__ = ('b',)
Çünkü boş olmayan yuvaları olan ebeveynlerden bir alt sınıf oluşturmak başarısız olur:
>>> 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
Bu sorun ile karşılaşırsanız, Sen olabilir sadece kaldırmak __slots__
ebeveynlerden veya ebeveynlerin kontrol edebiliyor, onları soyutlamalara yuvaları veya Refactor boş verin:
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__'
için __slots__
dinamik atama almak için:class Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
ve şimdi:
>>> foo = Foo()
>>> foo.boink = 'boink'
Bu nedenle '__dict__'
, yuvalarda dinamik atamaya sahip olma ve beklediğimiz adlar için yuvalara sahip olma ile bazı boyut avantajlarını kaybediyoruz.
Oluklu olmayan bir nesneden miras aldığınızda, kullandığınızda aynı tür anlambilim elde edersiniz __slots__
- __slots__
oluklu değerlere işaret eden adlar , örneğin değerlerine başka değerler konulur __dict__
.
Kaçınmak __slots__
size anında özelliklerini eklemek mümkün olmak istiyorum, çünkü aslında iyi bir neden değil - sadece eklemek "__dict__"
adresinden Müşteri __slots__
Bunun gerekli olup olmadığını.
Bu özelliğe ihtiyacınız varsa benzer __weakref__
şekilde __slots__
açıkça ekleyebilirsiniz .
Adlandırılmış üçlü yerleşik, çok hafif (esas olarak, tuples boyutu) değişmez örnekler yapar, ancak faydaları elde etmek için, bunları alt sınıflara ayırırsanız kendiniz yapmanız gerekir:
from collections import namedtuple
class MyNT(namedtuple('MyNT', 'bar baz')):
"""MyNT is an immutable and lightweight object"""
__slots__ = ()
kullanımı:
>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'
Ve beklenmedik bir özellik atamaya çalışmak, AttributeError
aşağıdakilerin oluşturulmasını engelledik çünkü __dict__
:
>>> nt.quux = 'quux'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'
Sen edebilirsiniz izin __dict__
kapalı bırakarak oluşturulmasını __slots__ = ()
, ancak boş olmayan kullanamaz __slots__
başlığın alt tipleri ile.
Boş olmayan yuvalar birden fazla ebeveyn için aynı olsa bile, birlikte kullanılamazlar:
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
Boş kullanarak __slots__
, en esneklik sağlamak için ebeveyn içinde görünüyor önlemek veya izin vermeyi tercih çocuğun sağlayan (ekleyerek '__dict__'
dinamik atama almak için yukarıdaki bölüme bakınız) bir oluşturulmasını__dict__
:
class Foo(object): __slots__ = ()
class Bar(object): __slots__ = ()
class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
b = Baz()
b.foo, b.bar = 'foo', 'bar'
Sen yok olması için herhangi bir hassasiyet olmamalıdır, bunları eklemeniz eğer öyleyse, daha sonra bunları kaldırmak - yuvaları olması.
Burada bir uzuvya çıkmak : Eğer mixins oluşturuyorsanız veya örneklenmesi amaçlanmayan soyut temel sınıfları kullanıyorsanız __slots__
, bu ebeveynlerde boş bir alt sınıflar için esneklik açısından gitmek için en iyi yol gibi görünüyor.
İlk olarak, birden fazla miras altında kullanmak istediğimiz kodlu bir sınıf oluşturalım
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)})'
Yukarıdakileri doğrudan beklenen alanları devralarak ve bildirerek kullanabiliriz:
class Foo(AbstractBase):
__slots__ = 'a', 'b'
Ama bunu umursamıyoruz, bu önemsiz tek miras, belki de gürültülü bir nitelikle miras alabileceğimiz başka bir sınıfa ihtiyacımız var:
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
Şimdi her iki tabanda da boş olmayan yuvalar varsa, aşağıdakileri yapamadık. (Aslında, isteseydik, AbstractBase
a ve b boş olmayan yuvalar verebilirdik ve onları aşağıdaki beyanın dışında bırakabilirdik - onları bırakmak yanlış olurdu):
class Concretion(AbstractBase, AbstractBaseC):
__slots__ = 'a b _c'.split()
Ve şimdi her ikisinden birden çok miras yoluyla işlevsellik sahibiyiz __dict__
ve hala inkar edebilir ve __weakref__
örnekleyebiliriz:
>>> 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__
Yuva düzenleri aynı olmadığı sürece, bunlara sahip olmayan (ve ekleyemezsiniz) başka bir sınıfla ödev yapmak istediğinizde bunlardan kaçının . (Bunu kimin neden yaptığını öğrenmekle çok ilgileniyorum.)Son zamanlarda önemli katkılarda bulunduğum __slots__
belgelerin geri kalanından (3.7 geliştirici dokümanlar en güncel olan) daha fazla uyarı çıkarabilirsiniz .
Şu anki en iyi cevaplar eski bilgileri gösteriyor ve oldukça dalgalı ve bazı önemli yollarla işareti kaçırıyor.
__slots__
çok sayıda nesneyi örneklerken kullanmayın "Alıntı yaparım:
"
__slots__
Aynı sınıftan çok sayıda nesneyi (yüzlerce, binlerce) başlatacaksanız kullanmak istersiniz ."
Özet Temel Sınıflar, örneğin, collections
modülden, somutlaştırılmaz, ancak __slots__
onlar için bildirilir.
Neden?
Bir kullanıcı reddetmek __dict__
veya __weakref__
oluşturmak isterse , bu şeyler üst sınıflarda mevcut olmamalıdır.
__slots__
arayüzler veya karışımlar oluştururken tekrar kullanılabilirliğe katkıda bulunur.
Birçok Python kullanıcısının yeniden kullanılabilirlik için yazmadığı doğrudur, ancak siz gereksiz yere alan kullanımını reddetme seçeneğine sahip olmak değerlidir.
__slots__
dekapaj kırılmazOluklu bir nesneyi seçerken, yanıltıcı bir şekilde şikayet ettiğini görebilirsiniz TypeError
:
>>> pickle.loads(pickle.dumps(f))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
Bu aslında yanlış. Bu ileti, varsayılan olan en eski protokolden gelir. Bağımsız -1
değişken ile en son protokolü seçebilirsiniz . Python 2.7'de bu 2
(2.3'te tanıtıldı) olacak ve 3.6'da olacak 4
.
>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>
Python 2.7'de:
>>> pickle.loads(pickle.dumps(f, 2))
<__main__.Foo object at 0x1129C770>
Python 3.6'da
>>> pickle.loads(pickle.dumps(f, 4))
<__main__.Foo object at 0x1129C770>
Çözülmüş bir problem olduğu için bunu aklımda tutacağım.
İlk paragraf yarı kısa açıklama, yarı öngörücüdür. İşte soruya cevap veren tek bölüm
Doğru kullanımı
__slots__
nesnelerdeki yerden tasarruf etmektir. İstediğiniz zaman nesnelere öznitelik eklemeye izin veren dinamik bir dikte kullanmak yerine, oluşturma işleminden sonra eklemelere izin vermeyen statik bir yapı vardır. Bu, yuva kullanan her nesne için bir diktenin yükünü korur
İkinci yarı arzulu düşünme ve iz bırakmayan:
Bu bazen yararlı bir optimizasyon olsa da, Python yorumlayıcısının yeterince dinamik olması tamamen gereksiz olurdu, böylece sadece nesneye gerçekten eklemeler olduğunda dikteyi gerektirecektir.
Python aslında buna benzer bir şey yapar, sadece __dict__
erişildiği zamanı oluşturur, ancak veri içermeyen çok sayıda nesne oluşturmak oldukça saçmadır.
İkinci paragraf, kaçınılması gereken gerçek nedenleri basitleştirir ve kaçırır __slots__
. Aşağıdakiler yuvaları önlemek için gerçek bir neden değildir ( gerçek nedenlerle, yukarıdaki cevabımın geri kalanına bakın.):
Yuvaları olan nesnelerin davranışlarını kontrol düşkünleri ve statik yazım hatalarıyla kötüye kullanılabilecek şekilde değiştirirler.
Daha sonra, Python ile bu sapkın hedefe ulaşmanın diğer yollarını tartışmaya devam eder, bununla ilgili bir şey tartışmaz __slots__
.
Üçüncü paragraf daha arzulu bir düşüncedir. Birlikte, cevaplayıcının yazmamış olduğu ve sitenin eleştirmenleri için mühimmata katkıda bulunduğu, çoğunlukla marka dışı içeriktir.
Bazı normal nesneler ve oluklu nesneler oluşturun:
>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()
Bunlardan bir milyonunu örnekleyin:
>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]
Aşağıdakilerle inceleyin 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
...
Normal nesnelere ve onların nesnelerine erişin __dict__
ve tekrar inceleyin:
>>> 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
...
Bu, Python 2.2'deki Unifying türleri ve sınıflarından Python'un geçmişi ile tutarlıdır.
Yerleşik bir türü alt sınıfa ayırırsanız
__dict__
ve ve örneklerine otomatik olarak fazladan boşluk eklenir__weakrefs__
. (Ancak__dict__
siz kullanana kadar başlatılmaz, bu nedenle oluşturduğunuz her örnek için boş bir sözlüğün kapladığı alan hakkında endişelenmemelisiniz.) Bu fazladan alana ihtiyacınız yoksa, "__slots__ = []
" ifadesini Senin sınıfın.
__slots__
. Ciddi anlamda! Teşekkür ederim!
__slots__
yaklaşık bir yıl önce Python belgelerine katkıda bulundum
Jacob Hallen'den alıntı :
Doğru kullanımı
__slots__
nesnelerde yerden tasarruf etmektir. İstediğiniz zaman nesnelere öznitelik eklemeye izin veren dinamik bir dikte kullanmak yerine, oluşturma işleminden sonra eklemelere izin vermeyen statik bir yapı vardır. [Bu kullanım,__slots__
her nesne için bir diktenin ek yükünü ortadan kaldırır.] Bu bazen yararlı bir optimizasyon olsa da, Python yorumlayıcısının yeterince dinamik olması, nesne.Ne yazık ki, yuvaların bir yan etkisi vardır. Yuvaları olan nesnelerin davranışlarını kontrol düşkünleri ve statik yazım hatalarıyla kötüye kullanılabilecek şekilde değiştirirler. Bu kötü, çünkü kontrol düşkünleri metasınıfları kötüye kullanıyor olmalı ve statik yazım hataları dekoratörleri kötüye kullanıyor olmalı, çünkü Python'da bir şey yapmanın sadece tek bir yolu olmalı.
CPython'u yerden tasarruf sağlayacak kadar akıllı hale getirmek
__slots__
büyük bir girişimdir, bu yüzden muhtemelen P3k için değişiklikler listesinde değildir (henüz).
__slots__
statik yazmayla aynı sorunları ele almaz. Örneğin, C ++ 'da, bir üye değişkenin bildirimi sınırlandırılmamıştır, bu değişkene istenmeyen bir türün (ve derleyici tarafından zorlanan) atanmasıdır. Kullanımını göz ardı etmiyorum __slots__
, sadece konuşmayla ilgileniyorum. Teşekkürler!
__slots__
Aynı sınıftan çok sayıda (yüzlerce, binlerce) nesne başlatacaksanız kullanmak istersiniz . __slots__
yalnızca bir bellek optimizasyon aracı olarak bulunur.
__slots__
Öznitelik oluşturmayı kısıtlamak için kullanılması önerilmez .
İle dekapaj nesneleri __slots__
varsayılan (en eski) dekapaj protokolü ile çalışmaz; sonraki bir sürümü belirtmek gerekir.
Python'un diğer bazı içgözlem özellikleri de olumsuz etkilenebilir.
Her python nesnesinin __dict__
diğer öznitelikleri içeren bir sözlük olan bir özelliği vardır. örneğin self.attr
python yazarken aslında yapıyor self.__dict__['attr']
. Özniteliği saklamak için bir sözlük kullandığınızı tahmin edebileceğiniz gibi, ona erişmek için fazladan yer ve zaman gerekir.
Ancak, kullandığınızda __slots__
, o sınıf için oluşturulan hiçbir nesnenin bir __dict__
özniteliği olmaz. Bunun yerine, tüm öznitelik erişimi doğrudan işaretçiler aracılığıyla yapılır.
Yani tam teşekküllü bir sınıftan ziyade C stili bir yapı istiyorsanız __slots__
, nesnelerin boyutunu sıkıştırmak ve öznitelik erişim süresini azaltmak için kullanabilirsiniz . Buna iyi bir örnek, x & y niteliklerini içeren bir Point sınıfıdır. Çok fazla puanınız olacaksa, __slots__
hafızayı korumak için kullanmayı deneyebilirsiniz .
__slots__
tanımlanır olmayan C tarzı yapısı gibi. Sınıf düzeyinde sözlük eşleme öznitelik isimleri dizinlere, aksi takdirde aşağıdakiler mümkün olmaz: class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)
Gerçekten bu cevabın açıklığa kavuşturulması gerektiğini düşünüyorum (isterseniz bunu yapabilirim). Ayrıca, instance.__hidden_attributes[instance.__class__[attrname]]
bundan daha hızlı olduğundan emin değilim instance.__dict__[attrname]
.
Diğer cevaplara ek olarak, aşağıdakileri kullanmanın bir örneği __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']
Bu nedenle, uygulamak __slots__
için yalnızca fazladan bir satır gerekir (ve sınıfınızı henüz yoksa yeni stil sınıfı haline getirir). Bu şekilde , eğer gerekliyse ve ne zaman olursa, özel turşu kodu yazmak zorunda kalmak pahasına, bu sınıfların bellek izini 5 kat azaltabilirsiniz .
Yuvalar, işlev çağrıları yaparken "adlandırılmış yöntem dağıtımı" nı ortadan kaldırmak için kütüphane çağrıları için çok yararlıdır. Bu, SWIG belgelerinde belirtilmiştir . Yuva kullanan genel işlevler için işlev yükünü azaltmak isteyen yüksek performanslı kütüphaneler için çok daha hızlıdır.
Şimdi bu doğrudan OP sorusuyla ilgili olmayabilir. Bu, bir nesne üzerinde yuva sözdizimini kullanmaktan çok uzantıları oluşturmakla ilgilidir . Ancak, yuvaların kullanımı ve arkasındaki gerekçelerin bir kısmı için resmin tamamlanmasına yardımcı olur.
Sınıf örneğinin bir özniteliğinin 3 özelliği vardır: örnek, özniteliğin adı ve özniteliğin değeri.
Gelen düzenli nitelik erişimi , örnek bir sözlük olarak görür ve bu sözlükte anahtar değeri ararken olarak özelliğin adı davranır.
örnek (özellik) -> değer
Gelen __slots__ erişimi , özelliğin adı sözlük olarak davranır ve sözlükte anahtar değeri ararken olarak örnek davranır.
özellik (örnek) -> değer
Gelen sineksiklet desen , özelliğin adı sözlük olarak davranır ve değer örneği ararken o sözlükte anahtar olarak görev yapar.
özellik (değer) -> örnek
__slots__
?
Çok basit bir __slot__
özellik örneği .
__slots__
Sınıfımda __slot__
nitelik yoksa , nesnelerime yeni özellikler ekleyebilirim.
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}
Yukarıdaki örneğe bakarsanız, obj1 ve obj2'nin kendi x ve y özniteliklerine sahip olduğunu ve python'undict
her nesne ( obj1 ve obj2 ) için bir öznitelik oluşturduğunu görebilirsiniz .
Sınıf testimde böyle binlerce nesne olup olmadığını varsayalım ? dict
Her nesne için ek bir öznitelik oluşturmak , kodumda çok fazla ek yüke (bellek, hesaplama gücü vb.) Neden olur.
__slots__
Şimdi aşağıdaki örnekte benim sınıf Test__slots__
öznitelik içeriyor . Şimdi nesnelerime yeni özellikler ekleyemiyorum (özellik hariç x
) ve python dict
artık bir özellik oluşturmuyor . Bu, her nesnenin ek yükünü ortadan kaldırır, bu da çok sayıda nesneniz varsa önemli olabilir.
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'
Biraz belirsiz bir başka kullanımı __slots__
, daha önce PEAK projesinin bir parçası olan ProxyTypes paketinden bir nesne proxy'sine nitelikler eklemektir. Onun ObjectWrapper
vekil için başka bir nesne izin verir, ancak vekalet edilen nesne ile tüm etkileşimler kesmek. Çok yaygın olarak kullanılmaz (ve Python 3 desteği yoktur), ancak bunu, iplik güvenli kullanarak, ioloop aracılığıyla proxy nesnesine tüm erişimi sıçrayan kasırgaya dayanan bir zaman uyumsuz uygulama etrafında bir iplik güvenli engelleme sarmalayıcısı uygulamak için kullandık concurrent.Future
sonuçları senkronize etmek ve döndürmek için kullanılır.
Varsayılan olarak proxy nesnesine herhangi bir öznitelik erişimi, proxy nesnesinin sonucunu verir. Proxy nesnesine bir öznitelik eklemeniz __slots__
gerekiyorsa kullanılabilir.
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
- Esasen - hiç faydası yok __slots__
.
Gereksinim duyabileceğinizi düşündüğünüz zaman __slots__
, aslında Hafif veya Flyweight tasarım desenlerini kullanmak istersiniz . Bunlar artık tamamen Python nesnelerini kullanmak istemediğiniz durumlardır. Bunun yerine, bir dizi, yapı veya numpy dizisinin etrafında Python nesnesi benzeri bir paketleyici istersiniz.
class Flyweight(object):
def get(self, theData, index):
return theData[index]
def set(self, theData, index, value):
theData[index]= value
Sınıf benzeri sarmalayıcının hiçbir özniteliği yoktur - yalnızca temel veriler üzerinde etkili olan yöntemler sağlar. Yöntemler sınıf yöntemlerine indirgenebilir. Gerçekten de, sadece temel veri dizisi üzerinde çalışan fonksiyonlara indirgenebilir.
__slots__
?
__slots__
her ikisi de bellek tasarrufu için optimizasyon teknikleridir. __slots__
Flyweight tasarım deseninin yanı sıra çok sayıda nesneye sahip olduğunuzda avantajlar gösterir. Her ikisi de aynı sorunu çözüyor.
__slots__
cevap gerçekten ve Evgeni'nin işaret ettiği gibi, basit bir sonradan düşünülmüş olarak eklenebilir (örneğin önce doğruluk üzerine odaklanabilir ve sonra performans ekleyebilirsiniz).
Orijinal soru sadece bellekle ilgili değil genel kullanım durumları ile ilgilidir. Bu nedenle burada, büyük miktarlarda nesneleri somutlaştırırken daha iyi performans elde ettiğinizden bahsedilmelidir - ilginç, örneğin büyük belgeleri nesnelere veya veritabanından ayrıştırırken.
Burada, yuvaları kullanarak ve yuvaları olmayan bir milyon girişli nesne ağaçları oluşturma karşılaştırması. Referans olarak, ağaçlar için düz dikte kullanırken performans (OSX'te Py2.7.10):
********** 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
Test sınıfları (ident, yuvalardan appart):
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)
test kodu, ayrıntılı mod:
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')
empy-slot-ebeveynleri ile örnek almadım. Neden burada for fordictproxy
yükseltmek yerine yaratılıyor ?AttributeError
c