1. ホーム
  2. python

サブクラスでの__slot__の継承は、実際にはどのように行われるのですか?

2023-09-28 10:20:33

質問

スロットのPythonデータモデルリファレンスのセクションで を使用する際の注意事項のリストがあります。 __slots__ . 1番目と6番目の項目が矛盾しているような気がして徹底的に混乱しています。

1番目の項目

  • がないクラスを継承する場合 __slots__ がない場合は __dict__ 属性は は常に にアクセスできるので __slots__ の定義は無意味です。 は意味を持ちません。

6番目の項目です。

  • の動作は __slots__ 宣言の動作は、定義されたクラス内に限定されます。 に限定されます。その結果 サブクラスには __dict__ も定義されていない限り __slots__ (を定義していなければなりません(これは追加のスロットの名前のみを含む必要があります)。 を定義していなければなりません。)

これらの項目は、より良い言葉で表現するか、コードで示すことができるように思えますが、私はこのことについて頭を悩ませ、まだ混乱しています。私は、次のように理解しています。 __slots__ を使用することになっています。 であり、私はそれらがどのように機能するかについてより良い把握をしようとしています。

質問です。

どなたか、サブクラス化する際のスロットの継承の条件をわかりやすく説明していただけませんか?

(簡単なコード例があれば助かりますが、必要ありません)。

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

他の方もおっしゃっているように、このように __slots__ を定義する唯一の理由は、あらかじめ定義された属性のセットを持つ単純なオブジェクトがあり、それぞれが辞書を持ち歩く必要がない場合に、メモリを節約するためです。 これは、もちろん、多くのインスタンスを持つ予定のクラスに対してのみ意味があります。

節約はすぐに明らかにならないかもしれませんが、考えてみてください。

>>> class NoSlots(object): pass
... 
>>> n = NoSlots()
>>> class WithSlots(object): __slots__ = 'a', 'b', 'c'
... 
>>> w = WithSlots()
>>> n.a = n.b = n.c = 23
>>> w.a = w.b = w.c = 23
>>> sys.getsizeof(n)
32
>>> sys.getsizeof(w)
36

これより、ウィズスロットサイズは より大きい であるように見えます。 しかし、それは間違いです。 sys.getsizeof は辞書のような "オブジェクトコンテンツ" を考慮しないためです。

>>> sys.getsizeof(n.__dict__)
140

dict だけでも 140 バイトかかるので、明らかに "32 bytes" のオブジェクトは n は、それぞれのインスタンスに関係するすべてを考慮していないと主張されています。 次のようなサードパーティの拡張機能を使用すると、より良い仕事ができます。 ピンプラー :

>>> import pympler.asizeof
>>> pympler.asizeof.asizeof(w)
96
>>> pympler.asizeof.asizeof(n)
288

によって節約されるメモリフットプリントをより明確に示しています。 __slots__ このような単純なオブジェクトの場合、200 バイト弱で、オブジェクト全体のフットプリントのほぼ 2/3 にあたります。 最近では、ほとんどのアプリケーションで 1 メガバイトの多寡はそれほど重要ではないので、このことからもわかるように __slots__ は、一度に数千のインスタンスしか扱わないのであれば、わざわざ使う価値はないということです。しかし、数百万のインスタンスに対しては、非常に重要な違いであることは確かです。また、微小なスピードアップも得られます(小さなオブジェクトに対して __slots__ ):

$ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x'
10000000 loops, best of 3: 0.37 usec per loop
$ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x'
1000000 loops, best of 3: 0.604 usec per loop
$ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x=45'
1000000 loops, best of 3: 0.28 usec per loop
$ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x=45'
1000000 loops, best of 3: 0.332 usec per loop

が、これはPythonのバージョンに多少依存します(これは私が2.5で繰り返し測定した数字です; 2.6では、より大きな相対的利点が __slots__ に対して 設定 属性が設定されていますが、全くなく、本当に小さな ディス のため、利点は 得る を得るため)。

さて、継承についてですが、インスタンスがディクショナリーであるためには すべて クラスもディクテレスインスタンスを持たなければなりません。 ディクテレスインスタンスを持つクラスは __slots__ を定義するクラス、およびほとんどの組み込み型です(インスタンスがディクテを持つ組み込み型は、関数などの任意の属性を設定することができるインスタンスです)。スロット名の重複は禁止されていませんが、スロットは継承されるため、無駄であり、いくつかのメモリを浪費します。

>>> class A(object): __slots__='a'
... 
>>> class AB(A): __slots__='b'
... 
>>> ab=AB()
>>> ab.a = ab.b = 23
>>> 

このように a の上に AB インスタンス -- AB 自体はスロットのみを定義しています。 b を定義するだけで、スロット a から A . 継承したスロットの繰り返しは禁止されていません。

>>> class ABRed(A): __slots__='a','b'
... 
>>> abr=ABRed()
>>> abr.a = abr.b = 23

のようになりますが、少しメモリを浪費します。

>>> pympler.asizeof.asizeof(ab)
88
>>> pympler.asizeof.asizeof(abr)
96

ということで、特に理由はありません。