Pythonで __slots__
の目的は何ですか。特に、いつ使用したいのか、使わないのかについてはどうですか?
Pythonでは、
__slots__
の目的は何ですか?これを避けるべきケースは何ですか?
特別な属性__slots__
を使用すると、オブジェクトインスタンスにどのインスタンス属性があるかを明示的に指定して、期待される結果を得ることができます。
スペース節約は
__dict__
ではなくスロットに値参照を保存します。__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ビルトインは、非常に軽量(本質的にはタプルのサイズ)の不変インスタンスを作成しますが、利点を得るには、サブクラス化する場合は自分で行う必要があります。
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__
割り当てを実行する場合は、それらを避けてください。 (誰がこれを行っているのか、そしてその理由を知ることに非常に興味があります。)__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>
それは解決された問題なので、私はこれを念頭に置いています。
最初の段落は、半分が短い説明、半分が予測です。実際に質問に答えるのはここだけです
__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__ = []
」というフレーズを追加できます「あなたのクラスに。
引用 ヤコブハレン :
__slots__
の正しい使い方は、オブジェクト内のスペースを節約することです。いつでもオブジェクトに属性を追加できる動的辞書を持つ代わりに、作成後に追加を許可しない静的構造があります。 [__slots__
を使用することで、すべてのオブジェクトに対して1つの辞書のオーバーヘッドがなくなります。]これは最適な最適化ではありますが、Pythonインタプリタが十分動的な場合は実際には不要です。オブジェクト。残念ながら、スロットには副作用があります。これらは、コントロールフリークや静的な型付けの弱点によって悪用される可能性がある方法で、スロットを持つオブジェクトの動作を変更します。 Pythonでは、明らかな方法は1つしかないはずなので、コントロールのおかげでメタクラスが悪用され、静的型付けの弱点でデコレータが悪用されるはずなので、これは悪いことです。
CPythonを
__slots__
なしでスペースの節約に対処するのに十分スマートにすることは大きな仕事です、それはおそらくそれが(まだ)P3kのための変更のリストに載っていない理由です。
同じクラスの多数(数百、数千)のオブジェクトをインスタンス化する場合は、__slots__
を使用します。 __slots__
はメモリ最適化ツールとしてのみ存在します。
属性の作成を制限するために__slots__
を使用することはお勧めできません。一般に、pythonの他のイントロスペクション機能とともにpickleを壊すため、これを避けることをお勧めします。
各pythonオブジェクトは他のすべての属性を含む辞書である__dict__
属性を持ちます。例えばself.attr
と入力すると、pythonは実際にself.__dict__['attr']
を実行します。あなたが想像できるように、属性を格納するために辞書を使うことはそれにアクセスするためにいくらかの余分なスペースと時間がかかります。
ただし、__slots__
を使用すると、そのクラス用に作成されたオブジェクトは__dict__
属性を持ちません。代わりに、すべての属性アクセスはポインタを介して直接行われます。
そのため、本格的なクラスではなくCスタイルの構造が必要な場合は、オブジェクトのサイズを小さくし、属性のアクセス時間を短縮するために__slots__
を使用できます。良い例は、属性x&yを含むPointクラスです。あなたがたくさんのポイントを持つことになるなら、あなたはいくらかのメモリを節約するために__slots__
を使うことを試みることができます。
他の答えに加えて、これは__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に減らすことができます _、必要に応じてカスタムピクルスコードを書く必要があります。
スロットは、関数呼び出しを行うときに「名前付きメソッドのディスパッチ」を排除するためのライブラリ呼び出しに非常に役立ちます。これはSWIG のドキュメント に記載されています。スロットを使用して一般的に呼ばれる関数の関数オーバーヘッドを減らしたい高性能ライブラリの場合は、はるかに高速です。
今これはOPの質問に直接関係しないかもしれません。オブジェクトに対してslots構文を使用するよりも、拡張機能を構築することに関連しています。しかし、それはスロットの使用法とそれらの背後にあるいくつかの推論の図を完成させるのに役立ちます。
クラスインスタンスの属性には、インスタンス、属性の名前、および属性の値の3つのプロパティがあります。
通常の属性アクセスでは、インスタンスは辞書として機能し、属性の名前はその辞書のキー値を探します。
インスタンス(属性) - >値
__ slots__ accessでは、属性の名前が辞書として機能し、インスタンスがキーとして機能します。辞書で値を調べます。
属性(インスタンス) - >値
フライウェイトパターンでは、属性の名前が辞書として機能し、値がキーとして機能しますその辞書でインスタンスを調べます。
属性(値) - >インスタンス
__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
__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がわかります。独自のxとyの属性を持ち、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'
あなたは - 本質的に - __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
クラスのようなラッパーは属性を持ちません - それは基礎となるデータに作用するメソッドを提供するだけです。メソッドはクラスメソッドに減らすことができます。実際には、基礎となる一連のデータを処理する機能だけに減らすことができます。
元の質問は、メモリだけでなく一般的なユースケースに関するものでした。大量のオブジェクトをインスタンス化すると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