1. ホーム
  2. python

[解決済み] スロットの使い方__?

2022-03-18 01:02:50

質問

の目的は何ですか? __slots__ Pythonで、特にどのような場合に使用し、どのような場合に使用しないかについて教えてください。

どのように解決するのですか?

<ブロッククオート

Pythonでは、何のために __slots__ また、どのような場合にこれを避けるべきでしょうか?

TLDR

特殊な属性 __slots__ を使用すると、オブジェクトのインスタンスが持つべきインスタンス属性を明示的に指定でき、期待通りの結果を得ることができます。

  1. より速く 属性にアクセスできるようになります。
  2. スペースセービング をメモリに格納します。

省スペースは

  1. 値の参照をスロットに格納する代わりに __dict__ .
  2. 拒否する __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__ = [] "をクラスに追加してください。