__slots__の使用法?


回答:


1019

Pythonでは、目的は__slots__何ですか、これを避けるべきケースは何ですか?

TLDR:

特別な属性を__slots__使用すると、オブジェクトインスタンスに期待するインスタンス属性を明示的に示し、期待される結果を得ることができます。

  1. より高速な属性アクセス。
  2. メモリのスペース節約

省スペースは

  1. 値参照をの代わりにスロットに格納します__dict__
  2. 親クラスが拒否し、宣言した場合の拒否__dict____weakref__作成__slots__

クイック警告

小さな警告、継承ツリーで特定のスロットを1回だけ宣言する必要があります。例えば:

class Base:
    __slots__ = 'foo', 'bar'

class Right(Base):
    __slots__ = 'baz', 

class Wrong(Base):
    __slots__ = 'foo', 'bar', 'baz'        # redundant foo and bar

これを間違えた場合(おそらくそうなるはずです)、Pythonはオブジェクトを拒否しません。そうでない場合、問題は明らかにならない可能性がありますが、オブジェクトはそうでない場合よりも多くのスペースを占有します。Python 3.8:

>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)

これは、Baseのスロット記述子に、Wrongとは別のスロットがあるためです。これは通常は発生しないはずですが、次のような可能性があります。

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

最大の注意点は、複数の継承に関するものです。複数の「空でないスロットを持つ親クラス」を組み合わせることはできません。

この制限に対応するには、ベストプラクティスに従います。1つまたはすべての親の抽象化を除いて、それぞれの具象クラスと新しい具象クラスが集合的に継承する-抽象化に空のスロットを与えます(標準ライブラリ)。

例については、以下の多重継承のセクションを参照してください。

要件:

  • inという名前の属性__slots__を実際にの代わりにスロットに格納するに__dict__は、クラスがから継承する必要がありobjectます。

  • の作成を防ぐには、__dict__からobject継承する必要があり、継承のすべてのクラスは宣言する必要があり、どのクラス__slots__'__dict__'エントリを持つことはできません。

読み続けたい場合は、詳細がたくさんあります。

使用する理由__slots__:より高速な属性アクセス。

Pythonの作成者であるGuido van Rossumは、より高速な属性アクセスのために実際に作成したと述べてい__slots__ます。

かなり重要な高速アクセスを実証するのは簡単です。

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

そして

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085

UbuntuのPython 3.5では、スロットアクセスがほぼ30%高速化されています。

>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342

Windows上のPython 2では、約15%速く測定しました。

使用する理由__slots__:メモリの節約

もう1つの目的は__slots__、各オブジェクトインスタンスが使用するメモリ内のスペースを減らすことです。

ドキュメントへの私自身の貢献は、この背後にある理由を明確に述べています

使用__dict__することで節約されるスペースは、かなりのものになる可能性があります。

SQLAlchemyは、多くのメモリの節約に貢献しています__slots__

これを確認するには、Ubuntu LinuxでPython 2.7のAnacondaディストリビューションを使用し、guppy.hpy(別名heapy)およびsys.getsizeof__slots__宣言されていないクラスインスタンスのサイズ、およびその他は64バイトです。これには含まれませ__dict__。Pythonの遅延評価を再度ありがとうございます。__dict__これは、参照されるまで存在しないように見えますが、データのないクラスは通常役に立ちません。存在するように呼び出された場合、__dict__属性は最低でも280バイトです。

対照的に、(データなし)と__slots__宣言されたクラスインスタンス()は16バイトのみであり、スロットに1アイテムの合計56バイト、2アイテムの64バイトです。

64ビットPythonのため、私はのために、Pythonの2.7と3.6のバイトのメモリ消費量を示す__slots____dict__、(1、0を除いて、2つの属性)辞書が3.6に成長する各点について(無スロットが定義されていません)。

       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

したがって、Python 3のディクテーションが小さいにもかかわらず、__slots__インスタンスのメモリを節約するための適切なスケーリングがわかります。これが、使用したい主な理由です__slots__

私のメモを完全にするために、スロットは「メンバー」と呼ばれるプロパティなどのデータ記述子を使用するため、Python 2では64バイト、Python 3では72バイトのクラスの名前空間でスロットごとに1回限りのコストがあることに注意してください。

>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72

のデモンストレーション__slots__

の作成を拒否するには、__dict__サブクラス化する必要がありますobject

class Base(object): 
    __slots__ = ()

今:

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

または定義する別のクラスをサブクラス化する __slots__

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

そしていま:

c = Child()
c.a = 'a'

だが:

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

__dict__スロット付きオブジェクトのサブクラス化中に作成を許可する'__dict__'には、に追加するだけです__slots__(スロットは順序付けされており、すでに親クラスにあるスロットは繰り返さないでください):

class SlottedWithDict(Child): 
    __slots__ = ('__dict__', 'b')

swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'

そして

>>> swd.__dict__
{'c': 'c'}

または__slots__、サブクラスで宣言する必要すらなくても、親からのスロットを使用しますが、aの作成を制限しません__dict__

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

そして:

>>> ns.__dict__
{'b': 'b'}

ただし、__slots__多重継承で問題が発生する可能性があります。

class BaseA(object): 
    __slots__ = ('a',)

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

空ではない両方のスロットを持つ親から子クラスを作成すると失敗するため:

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

あなたはこの問題に遭遇した場合、あなたは可能性があり、単に削除__slots__両親から、またはあなたが親のコントロールを持っている場合は、抽象化に彼らに空きスロット、またはリファクタリングを与えます:

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__'__slots__て動的割り当てを取得します。

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

そしていま:

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

したがって'__dict__'、スロット内では、動的割り当てがあり、期待する名前のスロットがまだあるという利点があるため、サイズの利点の一部が失われます。

スロット化されていないオブジェクトから継承する場合、使用すると同じ種類のセマンティクスが得られます__slots__- __slots__スロット化された値を指す名前で、他の値はインスタンスのに配置されます__dict__

__slots__オンザフライで属性を追加できるようにするために回避することは、実際には適切な理由ではありません。これが必要な場合は追加"__dict__"してください__slots__

その機能が必要な場合は、同様__weakref____slots__明示的にに追加できます。

名前付きタプルをサブクラス化するときに空のタプルに設定します。

namedtupleビルトインは、非常に軽量な(本質的にはタプルのサイズ)不変のインスタンスを作成しますが、メリットを得るには、サブクラス化する場合は自分で行う必要があります。

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

使用法:

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

また、予期しない属性を割り当てようとするとAttributeError、が作成されないため、が発生し__dict__ます。

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

を省略することで__dict__作成許可でき__slots__ = ()ますが__slots__、タプルのサブタイプで空でないものを使用することはできません。

最大の警告:多重継承

空でないスロットが複数の親で同じ場合でも、それらを一緒に使用することはできません。

class Foo(object): 
    __slots__ = 'foo', 'bar'
class Bar(object):
    __slots__ = 'foo', 'bar' # alas, would work if empty, i.e. ()

>>> class Baz(Foo, Bar): pass
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
    multiple bases have instance lay-out conflict

__slots__親でemptyを使用すると、最も柔軟性が高くなり、子が'__dict__'動的割り当てを取得するために追加することにより、上記のセクションを参照して)の作成__dict__を防止または許可することを選択できます

class Foo(object): __slots__ = ()
class Bar(object): __slots__ = ()
class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
b = Baz()
b.foo, b.bar = 'foo', 'bar'

スロットを用意する必要はありません。スロットを追加して後で削除しても、問題は発生しません。

ここで外に出ます:インスタンス化することを意図していないミックスインを作成している、または抽象基本クラスを使用している場合__slots__、これらの親を空にすることは、サブクラス作成者の柔軟性の点で最適な方法のようです。

実例として、最初に、多重継承で使用するコードを含むクラスを作成しましょう

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

予想されるスロットを継承して宣言することで、上記を直接使用できます。

class Foo(AbstractBase):
    __slots__ = 'a', 'b'

しかし、それは気にしません、それは些細な単一継承です。おそらくノイズのある属性を持つ、継承元となる別のクラスが必要です。

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

両方の基地に空でないスロットがある場合、以下を実行できません。(実際、必要に応じて、AbstractBase空ではないスロットaとbを指定し、それらを以下の宣言から除外することもできます-それらを残すことは間違っているでしょう):

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

そして今、私たちは多重継承による両方からの機能を持ち、それでも拒否__dict__して__weakref__インスタンス化することができます:

>>> 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__スロットのレイアウトが同一でない限り、それらを持たない(そしてそれらを追加できない)別のクラスで割り当てを実行したい場合は、それらを避けてください。(私はこれをしている人とその理由を学ぶことに非常に興味があります。)
  • long、tuple、strなどの可変長の組み込みをサブクラス化し、それらに属性を追加する場合は、これらを避けてください。
  • インスタンス変数のクラス属性を介してデフォルト値を提供することを主張する場合は、これらを避けてください。

最近の重要な貢献により、残りの__slots__ ドキュメント(3.7開発者ドキュメントが最新)からさらに注意を払うことができるかもしれません。

他の回答の批評

現在のトップの回答は古い情報を引用しており、非常に波打っており、いくつかの重要な点でマークを逃しています。

__slots__多くのオブジェクトをインスタンス化するときにのみ使用」しないでください

私は引用します:

__slots__同じクラスの多数(数百、数千)のオブジェクトをインスタンス化する場合に使用します。」

たとえば、collectionsモジュールの抽象基本クラスはインスタンス化__slots__されていませんが、宣言されています。

どうして?

ユーザーが拒否__dict__または__weakref__作成を希望する場合、それらは親クラスで使用できません。

__slots__ インターフェイスやミックスインを作成する際の再利用性に貢献します。

多くのPythonユーザーが再利用性を考慮して作成していないことは事実ですが、そうした場合、不必要なスペースの使用を拒否するオプションを持つことは価値があります。

__slots__ 酸洗を壊さない

スロットオブジェクトをピクルすると、誤解を招く文句が付けられることがありますTypeError

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

これは実際には正しくありません。このメッセージは、デフォルトの最も古いプロトコルからのものです。-1引数で最新のプロトコルを選択できます。これはPython 2.7では2(2.3で導入された)で、3.6ではです4

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

Python 2.7の場合:

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

Python 3.6

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

それは解決された問題なので、私はこれを覚えておきます。

(2016年10月2日まで)承認された回答の批評

最初の段落は、半分が短い説明、半分が予測です。これが実際に質問に答える唯一の部分です

の適切な使用は__slots__、オブジェクトのスペースを節約することです。いつでもオブジェクトに属性を追加できる動的なdictの代わりに、作成後に追加できない静的な構造があります。これにより、スロットを使用するすべてのオブジェクトについて、1つの辞書のオーバーヘッドが節約されます

後半は希望に満ちた思考であり、その場しのぎです。

これは便利な最適化になることもありますが、Pythonインタープリターが十分に動的で、実際にオブジェクトに追加があったときにのみdictを必要とする場合は、まったく不要です。

Pythonは実際にはこれに似た処理を行い__dict__、アクセスされたときのみを作成しますが、データのない多数のオブジェクトを作成するのはかなりおかしいです。

2番目の段落は、単純化しすぎて、避けるべき実際の理由を見逃してい__slots__ます。以下はスロットを回避する本当の理由ではありません実際の理由については、上記の残りの回答を参照してください)。

それらは、制御フリークや静的型定義ウィニーによって悪用される可能性がある方法で、スロットを持つオブジェクトの動作を変更します。

次に、Pythonを使用して何かを説明するのではなく、Pythonを使用してその厄介な目標を達成する他の方法について説明し__slots__ます。

3番目の段落は、より希望的な考えです。まとめると、回答者が作成さえしなかったほとんどがオフサイトのコンテンツであり、サイトの批評家の弾薬に貢献しています。

メモリ使用量の証拠

通常のオブジェクトとスロットオブジェクトをいくつか作成します。

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

それらの100万をインスタンス化します。

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

で検査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
...

通常のオブジェクトとそのオブジェクトにアクセスし、__dict__再度検査します。

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

これは、Python 2.2の型とクラスの統一から、Pythonの歴史と一致しています

組み込み型をサブクラス化する__dict__と、とに対応するためにインスタンスに余分なスペースが自動的に追加されます__weakrefs__。(__dict__ただし、使用するまで初期化されないため、作成するインスタンスごとに空の辞書が占めるスペースについて心配する必要はありません。)この余分なスペースが必要ない場合は、「__slots__ = []」という語句をあなたのクラス。


14
うわー、答えの地獄-ありがとう!ただし、class Child(BaseA, BaseB): __slots__ = ('a', 'b')empy-slot-parentsを使用した例は理解できませんでした。なぜfor dictproxyをレイズする代わりに作成されたAttributeErrorcですか?
Skandix

@Skandixは、入力ミスに注意を払ってくれてありがとう。cはインスタンス化ではないことがわかりました。投稿履歴に保存したときに、その部分を編集していたのを忘れていたようです。もし私が正しいことをして、コードをよりコピーしやすいようにすれば、もっと早く捕まえられたでしょう...もう一度ありがとう!
アーロンホール

38
この回答は、に関するPythonの公式ドキュメントの一部である必要があります__slots__。マジ!ありがとうございました!
NightElfik

13
@NightElfikはそれを信じるかどうか、私は1年__slots__ほど前にPythonドキュメントに寄稿しました:github.com/python/cpython/pull/1819/files
Aaron Hall

素晴らしく詳細な答え。質問が1つあります。使用率がいずれかの警告に該当しない限り、スロットをデフォルトとして使用する必要がありますか、それとも速度/メモリに苦労することがわかっている場合は、スロットを検討する必要がありますか?別の言い方をすれば、初心者にそれらについて学び、最初から使用するように勧めるべきですか?
freethebees

265

Jacob Hallenの引用:

の適切な使用法は__slots__、オブジェクトのスペースを節約することです。いつでもオブジェクトに属性を追加できる動的なdictの代わりに、作成後に追加できない静的な構造があります。[この使用により__slots__、すべてのオブジェクトに対して1つのdictのオーバーヘッドが排除されます。]これは便利な最適化である場合もありますが、Pythonインタープリターが十分に動的で、実際にdictに追加されたときにのみdictを必要とする場合は、まったく不要です。オブジェクト。

残念ながら、スロットには副作用があります。それらは、制御フリークや静的型定義ウィニーによって悪用される可能性がある方法で、スロットを持つオブジェクトの動作を変更します。これは悪いことです。なぜなら、Pythonでは明確な方法が1つしかないため、コントロールフリークがメタクラスを悪用し、静的型定義ウィーニーがデコレーターを悪用するためです。

CPythonを十分にスマートにして__slots__、スペースを節約せずに処理できるようにすることは大きな仕事です。おそらく、これがP3kの変更リストに(まだ)含まれていない理由です。


86
「静的型付け」/デコレータポイントの詳細については、冗談ではありません。不在の第三者を引用することは役に立ちません。__slots__静的型付けと同じ問題には対応していません。たとえば、C ++では、メンバー変数の宣言が制限されているわけではなく、その変数への意図しない型の割り当て(およびコンパイラーによる強制)です。の使用は容認せず__slots__、会話に興味があるだけです。ありがとう!
hiwaylon

126

__slots__同じクラスの多数(数百、数千)のオブジェクトをインスタンス化する場合に使用します。__slots__メモリ最適化ツールとしてのみ存在します。

__slots__属性の作成を制限するために使用することは非常にお勧めしません。

を使用したオブジェクトの__slots__ピクル化は、デフォルトの(最も古い)ピクルプロトコルでは機能しません。新しいバージョンを指定する必要があります。

Pythonの他のいくつかのイントロスペクション機能も悪影響を受ける可能性があります。


10
私は私の答えの中でスロット付きオブジェクトをピクルスにすることを示し、またあなたの答えの最初の部分を扱います。
アーロンホール

2
私はあなたの要点を理解していますが、スロットはより高速な属性アクセスも提供します(他の人が述べたように)。その場合パフォーマンスを得るために「同じクラスの多数(数百、数千)のオブジェクトをインスタンス化する必要はありません。代わりに必要なのは、同じインスタンスの同じ(スロット化された)属性への多くのアクセスです。(間違っている場合は修正してください。)
ロタレティ2017

61

各pythonオブジェクトには、__dict__他のすべての属性を含む辞書である属性があります。たとえば、タイプするとself.attrpythonが実際に実行されself.__dict__['attr']ます。辞書を使用して属性を格納すると想像できるように、属性にアクセスするために余分なスペースと時間がかかります。

ただし、を使用する場合__slots__、そのクラス用に作成されたオブジェクトには__dict__属性がありません。代わりに、すべての属性アクセスはポインターを介して直接行われます。

したがって、本格的なクラスではなくCスタイルの構造が必要な場合__slots__は、オブジェクトのサイズを圧縮して属性アクセス時間を短縮するために使用できます。良い例は、属性xおよびyを含むPointクラスです。多くのポイントがある場合は、__slots__メモリを節約するために使用してみてください。


10
ない、とクラスのインスタンス__slots__定義はない Cスタイルの構造が挙げられます。属性名をインデックスにマッピングするクラスレベルのディクショナリがあります。それ以外の場合は、次のことは不可能ですclass A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)。この答えは明確にすべきだと思います(必要に応じてそれを行うことができます)。また、それinstance.__hidden_attributes[instance.__class__[attrname]]よりも速いかどうかはわかりませんinstance.__dict__[attrname]
tzot

22

他の回答に加えて、使用例は__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']

したがって、を実装するため__slots__に必要なのは、追加の行のみです(クラスがまだない場合は、クラスを新しいスタイルのクラスにします)。この方法では、必要に応じてカスタムピクルコードを作成する必要がある代わりに、これらのクラスのメモリフットプリントを5分の1に削減できます。


11

スロットは、ライブラリ呼び出しで関数呼び出しを行うときに「名前付きメソッドディスパッチ」を排除するのに非常に役立ちます。これはSWIGのドキュメントで言及されています。スロットを使用して一般的に呼び出される関数の関数オーバーヘッドを削減する必要がある高性能ライブラリの場合は、はるかに高速です。

現在、これはOPの質問に直接関連していない可能性があります。これは、オブジェクトでスロット構文を使用するよりも、拡張を構築することに関連しています。しかし、スロットの使用状況とその背後にある理由のいくつかを完全に理解するのに役立ちます。


7

クラスインスタンスの属性には、インスタンス、属性の名前、属性の値の3つのプロパティがあります。

では、通常の属性アクセス、インスタンスは辞書として機能し、属性の名前は、値を調べることで、辞書のキーとして機能します。

インスタンス(属性)->値

__slots__アクセス、属性の名前は、辞書として機能し、インスタンスが値を調べる辞書でキーとして機能します。

属性(インスタンス)->値

フライ級のパターン、属性の名前は、辞書として機能し、値がインスタンスを探している辞書でキーとして機能します。

属性(値)->インスタンス


これは良い共有であり、フライウェイトを示唆する回答の1つに対するコメントにはあまり適合しませんが、質問自体に対する完全な回答ではありません。特に(質問のコンテキストでのみ):Flyweightを使用する理由と、「回避すべきケースは何__slots__ですか?」
Merlyn Morgan-Graham

@Merlyn Morgan-Graham、それは選択するためのヒントとして機能します:通常のアクセス、__ slots__、またはflyweight。
ドミトリールバノビッチ2014

3

__slot__属性の非常に単純な例。

問題:なし __slots__

__slot__クラスに属性がない場合は、オブジェクトに新しい属性を追加できます。

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}

あなたは上記の例を見れば、あなたはそれを参照することができOBJ1をし、OBJ2、自分の持っているのxyの属性とPythonはまた、作成したdict各オブジェクト(の属性OBJ1およびOBJ2を)。

私のクラスTestにそのようなオブジェクトが何千もあるとしたら?dict各オブジェクトに追加の属性を作成すると、コードで多くのオーバーヘッド(メモリ、計算能力など)が発生します。

解決策:あり __slots__

次の例では、クラスTest__slots__属性が含まれています。これで、オブジェクトに新しい属性を追加できなくなり(attributeを除くx)、Pythonはdict属性を作成しなくなりました。これにより、各オブジェクトのオーバーヘッドがなくなります。これは、多くのオブジェクトがある場合に重要になる可能性があります。

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

もう少しあいまいな使用法は__slots__、ProxyTypesパッケージ(以前はPEAKプロジェクトの一部でした)からオブジェクトプロキシに属性を追加することです。これObjectWrapperにより、別のオブジェクトをプロキシできますが、プロキシされたオブジェクトとのすべての相互作用をインターセプトできます。これはあまり一般的には使用されていませんが(Python 3のサポートはありません)、スレッドセーフを使用して、ioloop経由でプロキシオブジェクトへのすべてのアクセスをバウンスするトルネードに基づく非同期実装の周りにスレッドセーフなブロッキングラッパーを実装するために使用しましたconcurrent.Future同期して結果を返すオブジェクト。

デフォルトでは、プロキシオブジェクトへの属性アクセスは、プロキシされたオブジェクトからの結果を提供します。プロキシオブジェクトに属性を追加する必要がある場合は、__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

1

あなたは-本質的に-を使用していません__slots__

必要と思われる場合は__slots__、実際には軽量またはフライウェイトのデザインパターンを使用します。これらは、純粋にPythonオブジェクトを使用したくない場合です。代わりに、配列、構造体、またはnumpy配列を囲むPythonオブジェクトのようなラッパーが必要です。

class Flyweight(object):

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

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

クラスのようなラッパーには属性がありません。基本的なデータに作用するメソッドを提供するだけです。メソッドはクラスメソッドに縮小できます。確かに、それはデータの基礎となる配列を操作する関数だけに減らすことができます。


17
Flyweightは何に関係してい__slots__ますか?
oefe 2009年

3
@oefe:私は確かにあなたの質問を聞きません。「スロットが必要になると思われるときに、実際に使用したいのは...フライ級デザインパターン」の場合、私の回答を引用できます。これがFlyweightとスロットの関係です。より具体的な質問がありますか?
S.Lott、2009年

21
@oefe:Flyweight __slots__は、メモリを節約するための最適化手法です。__slots__には、Flyweightデザインパターンだけでなく、多くのオブジェクトがある場合の利点が示されています。どちらも同じ問題を解決します。
jfs 2009年

7
メモリの消費と速度に関して、スロットの使用とFlyweightの使用の間に利用可能な比較はありますか?
kontulai 2013

8
Flyweightは確かに一部のコンテキストで役立ちますが、信じられないかもしれませんが、「ジリオンオブジェクトを作成するときにPythonのメモリ使用量を削減するにはどうすればよいですか」という答えは、必ずしも「ジリオンオブジェクトにPythonを使用しないでください」とは限りません。時々__slots__本当に答えが時々あり、Evgeniが指摘するように、それは単純な後付けとして追加できます(たとえば、最初に正確さに焦点を当て、次にパフォーマンスを追加できます)。
Patrick Maupin 2015

0

元々の質問は、メモリだけでなく、一般的な使用例に関するものでした。したがって、ここでは、大量のオブジェクトをインスタンス化するときにパフォーマンスが向上することも言及しておく必要があります。たとえば、大きなドキュメントをオブジェクトに解析したり、データベースから解析したりする場合などです。

以下は、スロットを使用した場合と使用しない場合の100万のエントリを持つオブジェクトツリーの作成の比較です。参考として、ツリーにプレーンディクショナリーを使用する場合のパフォーマンス(OSXの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

テストクラス(ID、スロットからのアパート):

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)

テストコード、詳細モード:

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.