回答:
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はオブジェクトを拒否しません。そうでない場合、問題は明らかにならない可能性がありますが、オブジェクトはそうでない場合よりも多くのスペースを占有します。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__
スロットのレイアウトが同一でない限り、それらを持たない(そしてそれらを追加できない)別のクラスで割り当てを実行したい場合は、それらを避けてください。(私はこれをしている人とその理由を学ぶことに非常に興味があります。)最近の重要な貢献により、残りの__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__
、オブジェクトのスペースを節約することです。いつでもオブジェクトに属性を追加できる動的な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__ = []
」という語句をあなたのクラス。
__slots__
。マジ!ありがとうございました!
__slots__
ほど前にPythonドキュメントに寄稿しました:github.com/python/cpython/pull/1819/files
の適切な使用法は
__slots__
、オブジェクトのスペースを節約することです。いつでもオブジェクトに属性を追加できる動的なdictの代わりに、作成後に追加できない静的な構造があります。[この使用により__slots__
、すべてのオブジェクトに対して1つのdictのオーバーヘッドが排除されます。]これは便利な最適化である場合もありますが、Pythonインタープリターが十分に動的で、実際にdictに追加されたときにのみdictを必要とする場合は、まったく不要です。オブジェクト。残念ながら、スロットには副作用があります。それらは、制御フリークや静的型定義ウィニーによって悪用される可能性がある方法で、スロットを持つオブジェクトの動作を変更します。これは悪いことです。なぜなら、Pythonでは明確な方法が1つしかないため、コントロールフリークがメタクラスを悪用し、静的型定義ウィーニーがデコレーターを悪用するためです。
CPythonを十分にスマートにして
__slots__
、スペースを節約せずに処理できるようにすることは大きな仕事です。おそらく、これがP3kの変更リストに(まだ)含まれていない理由です。
__slots__
静的型付けと同じ問題には対応していません。たとえば、C ++では、メンバー変数の宣言が制限されているわけではなく、その変数への意図しない型の割り当て(およびコンパイラーによる強制)です。の使用は容認せず__slots__
、会話に興味があるだけです。ありがとう!
__slots__
同じクラスの多数(数百、数千)のオブジェクトをインスタンス化する場合に使用します。__slots__
メモリ最適化ツールとしてのみ存在します。
__slots__
属性の作成を制限するために使用することは非常にお勧めしません。
を使用したオブジェクトの__slots__
ピクル化は、デフォルトの(最も古い)ピクルプロトコルでは機能しません。新しいバージョンを指定する必要があります。
Pythonの他のいくつかのイントロスペクション機能も悪影響を受ける可能性があります。
各pythonオブジェクトには、__dict__
他のすべての属性を含む辞書である属性があります。たとえば、タイプするとself.attr
pythonが実際に実行されself.__dict__['attr']
ます。辞書を使用して属性を格納すると想像できるように、属性にアクセスするために余分なスペースと時間がかかります。
ただし、を使用する場合__slots__
、そのクラス用に作成されたオブジェクトには__dict__
属性がありません。代わりに、すべての属性アクセスはポインターを介して直接行われます。
したがって、本格的なクラスではなくCスタイルの構造が必要な場合__slots__
は、オブジェクトのサイズを圧縮して属性アクセス時間を短縮するために使用できます。良い例は、属性xおよびyを含むPointクラスです。多くのポイントがある場合は、__slots__
メモリを節約するために使用してみてください。
__slots__
定義はない Cスタイルの構造が挙げられます。属性名をインデックスにマッピングするクラスレベルのディクショナリがあります。それ以外の場合は、次のことは不可能ですclass A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)
。この答えは明確にすべきだと思います(必要に応じてそれを行うことができます)。また、それinstance.__hidden_attributes[instance.__class__[attrname]]
よりも速いかどうかはわかりませんinstance.__dict__[attrname]
。
他の回答に加えて、使用例は__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に削減できます。
クラスインスタンスの属性には、インスタンス、属性の名前、属性の値の3つのプロパティがあります。
では、通常の属性アクセス、インスタンスは辞書として機能し、属性の名前は、値を調べることで、辞書のキーとして機能します。
インスタンス(属性)->値
で__slots__アクセス、属性の名前は、辞書として機能し、インスタンスが値を調べる辞書でキーとして機能します。
属性(インスタンス)->値
でフライ級のパターン、属性の名前は、辞書として機能し、値がインスタンスを探している辞書でキーとして機能します。
属性(値)->インスタンス
__slots__
ですか?」
__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__
属性が含まれています。これで、オブジェクトに新しい属性を追加できなくなり(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'
もう少しあいまいな使用法は__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
あなたは-本質的に-を使用していません__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
クラスのようなラッパーには属性がありません。基本的なデータに作用するメソッドを提供するだけです。メソッドはクラスメソッドに縮小できます。確かに、それはデータの基礎となる配列を操作する関数だけに減らすことができます。
__slots__
ますか?
__slots__
は、メモリを節約するための最適化手法です。__slots__
には、Flyweightデザインパターンだけでなく、多くのオブジェクトがある場合の利点が示されています。どちらも同じ問題を解決します。
__slots__
本当に答えが時々あり、Evgeniが指摘するように、それは単純な後付けとして追加できます(たとえば、最初に正確さに焦点を当て、次にパフォーマンスを追加できます)。
元々の質問は、メモリだけでなく、一般的な使用例に関するものでした。したがって、ここでは、大量のオブジェクトをインスタンス化するときにパフォーマンスが向上することも言及しておく必要があります。たとえば、大きなドキュメントをオブジェクトに解析したり、データベースから解析したりする場合などです。
以下は、スロットを使用した場合と使用しない場合の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
class Child(BaseA, BaseB): __slots__ = ('a', 'b')
empy-slot-parentsを使用した例は理解できませんでした。なぜfordictproxy
をレイズする代わりに作成されたAttributeError
のc
ですか?