__Slots__ kullanımı?


Yanıtlar:


1018

Python'da bunun amacı __slots__nedir ve bundan kaçınması gereken durumlar nelerdir?

TLDR:

Özel özellik __slots__, nesne örneklerinizin olmasını beklediğiniz örnek niteliklerini, beklenen sonuçlarla açıkça belirtmenize olanak tanır:

  1. daha hızlı öznitelik erişimi.
  2. bellekte yer tasarrufu .

Yerden tasarruf

  1. Değer referanslarını bunun yerine yuvalarda depolama __dict__.
  2. Ebeveyn sınıfları onları inkar ederse ve beyan ederseniz inkar __dict__ve __weakref__yaratım __slots__.

Hızlı Uyarılar

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.

Gereksinimler:

  • 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 objectve 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.

Neden kullanılır __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.

Neden kullanılır __slots__: Bellek Tasarrufu

Diğ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.hpy2.7'nin Anaconda dağılımını (aka heapy) ve bildirilmemiş sys.getsizeofbir 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

Gösteri __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!

Ekle '__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 .

Bir adlandırılmış grubun alt sınıfını verirken boş gruba ayarlayın:

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, AttributeErroraş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.

En Büyük Uyarı: Çoklu Kalıtım

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

Yuvaları önlemek için diğer durumlar:

  • __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.)
  • Long, tuple veya str gibi değişken uzunluklu yapıları alt sınıflara ayırmak ve bunlara nitelikler eklemek istiyorsanız bunlardan kaçının.
  • Örnek değişkenler için sınıf öznitelikleri aracılığıyla varsayılan değerler sağlamakta ısrar ediyorsanız bunlardan kaçının.

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 .

Diğer cevapların kriterleri

Şu anki en iyi cevaplar eski bilgileri gösteriyor ve oldukça dalgalı ve bazı önemli yollarla işareti kaçırıyor.

"Yalnızca __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, collectionsmodü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ılmaz

Oluklu 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 -1değ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.

Kabul edilen cevabın (2 Ekim 2016'ya kadar) eleştirisi

İ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.

Bellek kullanım kanıtı

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.


14
vay, bir cevap cehennemi - teşekkürler! Ancak, class Child(BaseA, BaseB): __slots__ = ('a', 'b')empy-slot-ebeveynleri ile örnek almadım. Neden burada for for dictproxyyükseltmek yerine yaratılıyor ? AttributeErrorc
Skandix

@Skandix bu yazım hatası dikkatimi çektiğin için teşekkürler, c'nin bir örnekleme olmadığı ortaya çıktı, muhtemelen bu bölümü yazı geçmişine kaydettiğimde düzenlediğimi unuttum. Doğru şeyi yapsaydım ve kodu daha kopyalanabilir yapsaydım, muhtemelen daha erken yakalanmış olurdu ... Tekrar teşekkürler!
Aaron Hall

38
Bu cevap, ilgili Python belgelerinin bir parçası olmalıdır __slots__. Ciddi anlamda! Teşekkür ederim!
GeceElfik

13
@NightElfik ister inan ister inanma, __slots__yaklaşık bir yıl önce Python belgelerine katkıda bulundum
Aaron Hall

Fevkalade ayrıntılı cevap. Bir sorum var: kullanım uyarılardan birine çarpmadığı sürece yuvaları varsayılan olarak mı kullanıyorsunuz , yoksa hız / bellek için mücadele edeceğinizi biliyorsanız yuvalar dikkate alınması gereken bir şey mi? Başka bir deyişle, bir acemi onları hakkında bilgi edinmeye ve onları en baştan kullanmaya teşvik etmeli misiniz?
freethebees

265

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).


86
"Statik yazım" / dekoratör noktasında, sajo sahtekarlarında biraz ayrıntı görmek istiyorum. Mevcut olmayan üçüncü taraflardan alıntı yapmak yararsızdır. __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!
hiwaylon

126

__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.


10
Cevabımda oluklu bir nesneyi açığa vurduğumu gösteriyorum ve cevabınızın ilk kısmını ele alıyorum.
Aaron Hall

2
Ne demek istediğini anlıyorum, ancak slotlar da (diğerlerinin belirttiği gibi) daha hızlı özellik erişimi sunuyor. Bu durumda , performans kazanmak için "aynı sınıftan çok sayıda nesneyi (yüzlerce, binlerce) somutlaştırmanıza" gerek yoktur . Bunun yerine ne gerek vardır çok aynı örneğinin aynı (yarıklı) özniteliği için erişimlerin. (Yanılıyorsam lütfen beni düzeltin.)
Rotareti

61

Her python nesnesinin __dict__diğer öznitelikleri içeren bir sözlük olan bir özelliği vardır. örneğin self.attrpython 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 .


10
Resim, bir sınıfın bir örneği __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].
tzot

22

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 .


11

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.


7

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


Bu iyi bir paydır ve sinek ağırlıkları da içeren cevaplardan birine yapılan bir yorumda iyi uymayacaktır, ancak sorunun kendisinin tam bir cevabı değildir. Özellikle (sadece sorunun bağlamında): neden Flyweight ve "kişinin kaçınması gereken durumlar ..." __slots__?
Merlyn Morgan-Graham

@Merlyn Morgan-Graham, seçebileceğiniz bir ipucu olarak hizmet eder: düzenli erişim, __slots__ veya sinek siklet.
Dmitry Rubanovich

3

Çok basit bir __slot__özellik örneği .

Sorun: Olmadan __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 ? dictHer nesne için ek bir öznitelik oluşturmak , kodumda çok fazla ek yüke (bellek, hesaplama gücü vb.) Neden olur.

Çözüm: ile __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 dictartı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'

2

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 ObjectWrappervekil 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.Futuresonuç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

1

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


17
Flyweight'in ne ile ilgisi var __slots__?
oefe

3
@oefe: Kesinlikle sorunuzu anlamıyorum. Cevabımı alıntılayabilirim, " yuvalara ihtiyacınız olabileceğini düşündüğünüzde , aslında kullanmak istersiniz ... Flyweight tasarım deseni". Flyweight'in yuvalarla yaptığı şey budur . Daha spesifik bir sorunuz mu var?
S.Lott

21
@oefe: Flyweight ve __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.
jfs

7
Bellek tüketimi ve hız açısından yuva kullanımı ile Flyweight kullanımı arasında kullanılabilir bir karşılaştırma var mı?
kontulai

8
Flyweight bazı bağlamlarda kesinlikle yararlı olsa da, ister inanın ister inanmayın, "Bir milyonlarca nesne oluşturduğumda Python'da bellek kullanımını nasıl azaltabilirim" yanıtı her zaman "milyonlarca nesneniz için Python'u kullanmayın" yanıtı değildir. Bazen __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).
Patrick Maupin

0

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.