web-dev-qa-db-ja.com

__slots__の使い方

Pythonで __slots__ の目的は何ですか。特に、いつ使用したいのか、使わないのかについてはどうですか?

629
Jeb

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は、これを間違えても反対しません(おそらくそうするはずです)、そうでなければ問題は顕在化しないかもしれませんが、オブジェクトはそうでない場合よりも多くのスペースを占有します。

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

最大の注意点は多重継承です-複数の「空でないスロットを持つ親クラス」は結合できません。

この制限に対応するには、ベストプラクティスに従います。具体的なクラスと新しい具体的なクラスが集合的に継承する、1つまたはすべての親の抽象化を除くすべての要素を除外します。標準ライブラリ)。

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

要件:

  • __slots__で指定された属性を__dict__ではなくスロットに実際に保存するには、クラスがobjectを継承する必要があります。

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

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

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

Pythonの作成者であるGuido van Rossumは、 states により、属性アクセスを高速化するために実際に__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__を使用する理由:メモリの節約

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

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

__dict__を使用して節約されたスペースは非常に大きくなる可能性があります。

SQLAlchemyの属性__slots__へのメモリの大幅な節約。

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

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

64ビットPythonの場合、Python 2.7および3.6のメモリ消費量をバイト単位で示します。__slots__および__dict__(スロットが定義されていない)の場合、dictが3.6で増加する各ポイント(0、1、および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

したがって、Python 3の小さな辞書にも関わらず、メモリを節約するためにインスタンスの__slots__スケーリングがどれほどうまく行われているかがわかります。これが__slots__を使用する主な理由です。

念のため、クラスのネームスペースのスロットごとに1回限りのコストがPython 2では64バイト、Python 3では72バイトであることに注意してください。 「メンバー」と呼ばれるプロパティ。

>>> 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__を宣言する必要さえなく、親からのスロットを引き続き使用しますが、__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__を削除するかcouldするか、親を制御できる場合は、空のスロットを与えるか、抽象化にリファクタリングします。

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をサブクラス化するときに空のTupleに設定します。

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'

あなたはcan__slots__ = ()を省くことで__dict__の作成を許可できますが、タプルのサブタイプで空でない__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__を使用すると、柔軟性が最も高くなるようです子が防止または許可することを選択できるようにする(動的割り当てを取得するために'__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'

スロットを持たないはありません。したがって、スロットを追加して後で削除しても、問題は発生しません。

ここで手足に出るmixins を作成している場合、または を使用している場合インスタンス化することを意図していない抽象基本クラス 、それらの親の空の__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__の適切な使用法は、オブジェクトのスペースを節約することです。いつでもオブジェクトに属性を追加できる動的な辞書の代わりに、作成後に追加を許可しない静的な構造があります。これにより、スロットを使用するオブジェクトごとに1つの辞書のオーバーヘッドが節約されます

後半は希望的観測です。

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

Pythonは実際にこれに似た処理を行い、アクセス時に__dict__を作成するだけですが、データのない多くのオブジェクトを作成するのはかなりばかげています。

2番目の段落では、__slots__を避けるために実際の理由を単純化しすぎています。以下はnotスロットを避ける本当の理由です(実際の理由については、上記の私の答えの残りを参照してください)。

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

その後、__slots__に関係することを議論するのではなく、Pythonでその邪悪な目標を達成する他の方法について議論します。

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__ = []」というフレーズを追加できます「あなたのクラスに。

816
Aaron Hall

引用 ヤコブハレン

__slots__の正しい使い方は、オブジェクト内のスペースを節約することです。いつでもオブジェクトに属性を追加できる動的辞書を持つ代わりに、作成後に追加を許可しない静的構造があります。 [__slots__を使用することで、すべてのオブジェクトに対して1つの辞書のオーバーヘッドがなくなります。]これは最適な最適化ではありますが、Pythonインタプリタが十分動的な場合は実際には不要です。オブジェクト。

残念ながら、スロットには副作用があります。これらは、コントロールフリークや静的な型付けの弱点によって悪用される可能性がある方法で、スロットを持つオブジェクトの動作を変更します。 Pythonでは、明らかな方法は1つしかないはずなので、コントロールのおかげでメタクラスが悪用され、静的型付けの弱点でデコレータが悪用されるはずなので、これは悪いことです。

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

261
Jeff Bauer

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

属性の作成を制限するために__slots__を使用することはお勧めできません。一般に、pythonの他のイントロスペクション機能とともにpickleを壊すため、これを避けることをお勧めします。

120
Ryan

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

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

そのため、本格的なクラスではなくCスタイルの構造が必要な場合は、オブジェクトのサイズを小さくし、属性のアクセス時間を短縮するために__slots__を使用できます。良い例は、属性x&yを含むPointクラスです。あなたがたくさんのポイントを持つことになるなら、あなたはいくらかのメモリを節約するために__slots__を使うことを試みることができます。

58
Suraj

他の答えに加えて、これは__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__を実装するには、余分な行を1つ追加するだけです(まだクラスが新しいスタイルのクラスになっていない場合)。こうすれば これらのクラスのメモリ使用量を5分の1に減らすことができます _、必要に応じてカスタムピクルスコードを書く必要があります。

19
Evgeni Sergeev

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

今これはOPの質問に直接関係しないかもしれません。オブジェクトに対してslots構文を使用するよりも、拡張機能を構築することに関連しています。しかし、それはスロットの使用法とそれらの背後にあるいくつかの推論の図を完成させるのに役立ちます。

11
Demolishun

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

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

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

__ slots__ accessでは、属性の名前が辞書として機能し、インスタンスがキーとして機能します。辞書で値を調べます。

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

フライウェイトパターンでは、属性の名前が辞書として機能し、値がキーとして機能しますその辞書でインスタンスを調べます。

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

6

__slots__のもう少しあいまいな使い方は、以前はPEAKプロジェクトの一部だったProxyTypesパッケージからオブジェクトプロキシに属性を追加することです。そのObjectWrapperはあなたが他のオブジェクトを代理することを可能にしますが、代理オブジェクトとのすべての相互作用を傍受します。あまり一般的には使用されていません(そしてPython 3はサポートされていません)が、スレッドセーフを使用してioloopを介してプロキシオブジェクトへのすべてのアクセスをバウンスするtornadoベースの非同期実装の周りのスレッドセーフブロッキングラッパーの実装同期して結果を返す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 __== "__main__":
    original = Original()
    proxy = ProxyOriginal(original, 'Proxy Overlord')

    # Both statements print "The Original"
    print "original.name: ", original.name
    print "proxy.name: ", proxy.name

    # Both statements below print 
    # "You are proxied by Proxy Overlord", since the ProxyOriginal
    # __init__ sets it to the original object 
    print "original.proxy_info: ", original.proxy_info
    print "proxy.proxy_info: ", proxy.proxy_info

    # prints "Proxy Overlord"
    print "proxy.proxy_name: ", proxy.proxy_name
    # Raises AttributeError since proxy_name is only set on 
    # the proxy object
    print "original.proxy_name: ", proxy.proxy_name
2
NeilenMarais

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

上の例を見ると、obj1obj2がわかります。独自のxyの属性を持ち、pythonもdictを作成しました。各オブジェクトの属性(obj1およびobj2)。

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

解決策:__slots__を使う

今度は次の例で私のクラスTest__slots__属性を含みます。これで私は自分のオブジェクトに新しい属性を追加することができなくなり(属性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
N Randhawa

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

__slots__が必要になると思うときは、実際にはLightweightまたはFlyweightデザインパターンを使用します。純粋にPythonのオブジェクトを使用したくない場合は、このような場合があります。代わりに、配列、構造体、または派手な配列の周りにPythonのオブジェクト風のラッパーが必要です。

class Flyweight(object):

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

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

クラスのようなラッパーは属性を持ちません - それは基礎となるデータに作用するメソッドを提供するだけです。メソッドはクラスメソッドに減らすことができます。実際には、基礎となる一連のデータを処理する機能だけに減らすことができます。

2
S.Lott

元の質問は、メモリだけでなく一般的なユースケースに関するものでした。大量のオブジェクトをインスタンス化するとperformanceになります。大きな文書をオブジェクトまたはデータベースから解析するとき。

これは、スロットを使用した場合とスロットを使用しなかった場合の、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

テストクラス(ident、スロットから始まる):

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
0
Red Pill