[解決済み] スロットの使い方__?
質問
の目的は何ですか?
__slots__
Pythonで、特にどのような場合に使用し、どのような場合に使用しないかについて教えてください。
どのように解決するのですか?
<ブロッククオート
Pythonでは、何のために
__slots__
また、どのような場合にこれを避けるべきでしょうか?
TLDR
特殊な属性
__slots__
を使用すると、オブジェクトのインスタンスが持つべきインスタンス属性を明示的に指定でき、期待通りの結果を得ることができます。
- より速く 属性にアクセスできるようになります。
- スペースセービング をメモリに格納します。
省スペースは
-
値の参照をスロットに格納する代わりに
__dict__
. -
拒否する
__dict__
と__weakref__
を宣言した場合、親クラスがそれを拒否している場合は__slots__
.
簡単な注意事項
小さな注意点ですが、特定のスロットを宣言するのは、継承ツリーの中で一度だけにしてください。例えば
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つまたはすべてを除外して、抽象化に空のスロットを与えます (ちょうど標準ライブラリの抽象ベースクラスのようなものです)。
例として、以下の多重継承の項を参照してください。
要求事項
-
で指定された属性を持つには
__slots__
の代わりに、実際にスロットに格納されます。__dict__
を継承する必要があります。object
(Python 3では自動ですが、Python 2では明示する必要があります)。 -
を作らないようにするには
__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__
: メモリ節約
のもう一つの目的は
__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バイトになります。
64bitのPythonの場合、Python2.7と3.6で、メモリ消費量をバイト単位で説明します。
__slots__
と
__dict__
(スロットは定義されていません) を、3.6 で dict が成長する各ポイントに適用します (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__
.
スロットはプロパティのようなデータ記述子を使用するため、Python 2では64バイト、Python 3では72バイトの、クラスの名前空間のスロットごとに1回のコストがかかることに注意してください(" members")。
>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
のデモ
__slots__
:
の作成を拒否するには
__dict__
のサブクラスを作成する必要があります。
object
. すべてのサブクラスは
object
はPython 3ですが、Python 2では明示的にする必要がありました。
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__
を親から削除するか、親をコントロールできるのであれば、空のスロットを与えるか、抽象的なものにリファクタリングしてください。
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をサブクラス化する際に、空のタプルに設定します。
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__
を親にするのが最も柔軟性があるようです。
を防ぐか許可するかを、子プロセスが選択できるようにします。
(を追加することで)。
'__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 dev docsが最新です。)
私は最近、このプロジェクトに大きな貢献をしています。
他の回答への批評
現在のトップアンサーは、古い情報を引用していたり、かなり手垢がついていたり、重要な点で的外れなところがあります。
のみを使用しないでください。
__slots__
たくさんのオブジェクトをインスタンス化する場合。
引用します。
<ブロッククオート
を使いたいところです。
__slots__
同じクラスのオブジェクトを大量に (数百、数千) インスタンス化する場合です。
抽象ベースクラスは、例えば、以下のようなものです。
collections
モジュールは、インスタンス化されないのに
__slots__
が宣言されています。
なぜですか?
を拒否したい場合
__dict__
または
__weakref__
を作成する場合、それらのものは親クラスで利用できないはずです。
__slots__
は、インターフェースやミキシンを作成する際に再利用性に貢献します。
多くのPythonユーザーは再利用性を考えて書いていないのは事実ですが、再利用する場合、不必要なスペースの使用を拒否するオプションがあることは貴重です。
__slots__
ピックリングを壊さない
スロットされたオブジェクトをpicklingするとき、誤解を招くような
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でその変態的な目標を達成するための他の方法について議論しており、以下のことについては何も触れていません。
__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の歴史と一致しています。 Python 2.2での型とクラスの統一
<ブロッククオート
組み込み型をサブクラス化する場合、インスタンスに余分なスペースを自動的に追加して
__dict__
と
__weakrefs__
. (その
__dict__
は使用するまで初期化されないので、インスタンスを作成するたびに空の辞書が占めるスペースは気にする必要はありません)。この余分なスペースが必要ない場合は、"というフレーズを追加することができます。
__slots__ = []
"をクラスに追加してください。
関連
-
python string splicing.join()とsplitting.split()の説明
-
[解決済み】LogisticRegression: Pythonでsklearnを使用して、未知のラベルタイプ: '連続'を使用しています。
-
[解決済み】 TypeError: += でサポートされていないオペランド型: 'int' および 'list' です。
-
[解決済み] staticmethodとclassmethodの違いについて
-
[解決済み] バイトを文字列に変換する
-
[解決済み] 最小限の驚き」と「変更可能なデフォルトの引数
-
[解決済み] Python 3で「1000000000000000 in range(1000000000000001)」はなぜ速いのですか?
-
[解決済み】ネストされたディレクトリを安全に作成するには?
-
[解決済み】__str__と__repr__の違いは何ですか?
-
[解決済み】2つの辞書を1つの式でマージする(辞書の和をとる)には?)
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
ピローによる動的キャプチャ認識のためのPythonサンプルコード
-
Python関数の高度な応用を解説
-
Python機械学習Githubが8.9Kstarsに達したモデルインタープリタLIME
-
パッケージングツールPyinstallerの使用と落とし穴の回避
-
Pythonショートビデオクローラーチュートリアル
-
Pythonの@decoratorsについてまとめてみました。
-
[解決済み] 'int'オブジェクトに'__getitem__'属性がない。
-
[解決済み】django インポートエラー - core.managementという名前のモジュールがない
-
[解決済み] Pythonにおける抽象クラスとインターフェースの違い
-
[解決済み】Pythonでimmutableオブジェクトを作るには?