Python:サブクラスでの__slots__の継承は実際にどのように機能しますか?
-
08-07-2019 - |
質問
スロットのPythonデータモデル参照セクションには、 __slots__
の使用に関する注意。第1項目と第6項目は互いに矛盾しているように見えるので、完全に混乱しています。
最初のアイテム:
- なしでクラスから継承する場合
__dict__
、<=>属性 そのクラスのは常に アクセス可能なため、<=> サブクラスの定義は 無意味。
6番目の項目:
- <=>のアクション 宣言はクラスに限定されます 定義されている場所。結果として、 サブクラスには<=>があります <=>も定義しない限り (任意の名前のみを含める必要があります 追加のスロット)。
これらの項目は、コードを使用してより適切に表現したり表示したりできるように思えますが、この問題に頭を悩ませようとしていて、まだ混乱しています。 <=>が使用されることを想定する方法を理解しており、それらがどのように機能するかをよりよく把握します。
質問:
サブクラス化するときのスロットの継承の条件を、誰かが平易な言葉で説明してもらえますか?
(簡単なコード例が役立ちますが、必須ではありません。)
解決
他の人が述べたように、__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
は<!> quot; object contents <!> quot;を考慮しないため、これは間違いです。辞書など:
>>> sys.getsizeof(n.__dict__)
140
dictだけで140バイトかかるため、<!> quot; 32バイト<!> quot;オブジェクトn
は、各インスタンスに関係するすべてを考慮していないと考えられています。 pympler :
>>> import pympler.asizeof
>>> pympler.asizeof.asizeof(w)
96
>>> pympler.asizeof.asizeof(n)
288
これは、a
によって保存されるメモリフットプリントをより明確に示します。この場合のような単純なオブジェクトの場合、オブジェクトのフットプリント全体のほぼ3分の2である200バイトより少し小さいです。さて、最近ではほとんどのアプリケーションでメガバイトが多かれ少なかれそれほど重要ではないので、これはまた、あなたが数千のインスタンスを周りに置くつもりなら、AB
は面倒な価値がないことを伝えます時間-しかし、数百万のインスタンスでは、それは確かに非常に重要な違いをもたらします。微視的なスピードアップも得られます(b
のある小さなオブジェクトのキャッシュ使用の改善が一因です):
$ 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では、属性の設定に対して相対的な利点がより大きくなりますが、すべて、実際には小さな dis 利点、それは取得します)。
今、継承に関して:インスタンスが辞書なしであるためには、継承チェーンのすべてクラスも辞書なしのインスタンスを持たなければなりません。 dictなしのインスタンスを持つクラスは、A
を定義するクラスに加えて、ほとんどの組み込み型(インスタンスにdictがある組み込み型は、インスタンス上で関数などの任意の属性を設定できるものです)。スロット名の重複は禁止されていませんが、スロットは継承されるため、無駄であり、メモリを浪費します。
>>> class A(object): __slots__='a'
...
>>> class AB(A): __slots__='b'
...
>>> ab=AB()
>>> ab.a = ab.b = 23
>>>
ご覧のとおり、<=>インスタンスに属性<=>を設定できます-<=>自体はスロット<=>のみを定義しますが、<=>からスロット<=>を継承します。継承されたスロットを繰り返すことは禁止されていません:
>>> class ABRed(A): __slots__='a','b'
...
>>> abr=ABRed()
>>> abr.a = abr.b = 23
しかし、少しメモリを浪費します:
>>> pympler.asizeof.asizeof(ab)
88
>>> pympler.asizeof.asizeof(abr)
96
したがって、実際に行う理由はありません。
他のヒント
class WithSlots(object):
__slots__ = "a_slot"
class NoSlots(object): # This class has __dict__
pass
最初のアイテム
class A(NoSlots): # even though A has __slots__, it inherits __dict__
__slots__ = "a_slot" # from NoSlots, therefore __slots__ has no effect
6番目の項目
class B(WithSlots): # This class has no __dict__
__slots__ = "some_slot"
class C(WithSlots): # This class has __dict__, because it doesn't
pass # specify __slots__ even though the superclass does.
おそらく近い将来__slots__
を使用する必要はないでしょう。ある程度の柔軟性を犠牲にしてメモリを節約することのみを目的としています。何万ものオブジェクトがない限り、それは重要ではありません。
Python:サブクラスでの
__slots__
の継承は実際にどのように機能しますか?第1項目と第6項目は、互いに矛盾しているように見えるため、完全に混乱しています。
これらのアイテムは実際には互いに矛盾しません。 1つ目は__dict__
を実装しないクラスのサブクラス、2つ目はBar
。
__weakref__
を実装しないクラスのサブクラス
Pythonのドキュメントが(当然のことながら)評判が高いのに、特に言語のあまり使用されていない機能に関しては、完璧ではないことをますます認識しています。 ドキュメントを次のように変更します。
<=>なしのクラスから継承する場合、<=>属性 そのクラスの
は常にアクセス可能ですので、<=>定義は サブクラスは無意味です。
<=>は、そのようなクラスにとっても意味があります。クラスの属性の予想される名前を文書化します。また、これらの属性のスロットを作成します-ルックアップが高速になり、使用スペースが少なくなります。 <=>に割り当てられる他の属性を許可するだけです。
この変更が承認されました。現在は最新のドキュメント。
例を次に示します。
class Foo:
"""instances have __dict__"""
class Bar(Foo):
__slots__ = 'foo', 'bar'
<=>には宣言されたスロットだけでなく、Fooのスロットもあります。これには<=>:
が含まれます>>> b = Bar()
>>> b.foo = 'foo'
>>> b.quux = 'quux'
>>> vars(b)
{'quux': 'quux'}
>>> b.foo
'foo'
実行するクラスのサブクラス<=>
<=>宣言のアクションは、それが存在するクラスに制限されます 定義されています。その結果、サブクラスには<=>があります。 <=>も定義します(これには追加の名前のみを含める必要があります スロット)。
まあ、それはまったく正しくありません。 <=>宣言のアクションは、定義されているクラスに完全に限定されるものではありません 。たとえば、多重継承に影響を与える可能性があります。
これを次のように変更します。
<=>を定義する継承ツリー内のクラスの場合、サブクラスは、<=> <=>も定義します(これには追加の名前のみを含める必要があります スロット)。
実際に更新して読みました:
<=>宣言のアクションはクラスに限定されません 定義されている場所。 <=>親で宣言されたものは 子クラス。ただし、子サブクラスは<=>および <=>も定義します(<=>(追加のスロットの名前のみを含める必要があります)。
例を次に示します。
class Foo:
__slots__ = 'foo'
class Bar(Foo):
"""instances get __dict__ and __weakref__"""
そして、スロット付きクラスのサブクラスがスロットを使用するようになることがわかります:
>>> b = Bar()
>>> b.foo = 'foo'
>>> b.bar = 'bar'
>>> vars(b)
{'bar': 'bar'}
>>> b.foo
'foo'
(<=>の詳細については、こちらの回答をご覧ください。
リンクした回答から:
__slots__
の適切な使用法は、オブジェクトのスペースを節約することです。動的な辞書を使用する代わりに...
<!> quot; __dict__
のないクラスから継承する場合、そのクラスの<=>属性は常にアクセス可能です<!> quot;。したがって、独自の<=>を追加してもオブジェクトが< =>、スペースを節約できません。
<=>が継承されていないことについてのビットは少しわかりにくいです。それは魔法の属性であり、他の属性のように振る舞わないことを覚えておいてください。そして、この魔法のスロットの振る舞いは継承されないと言っていることを再読してください。 (それだけです。)
私の理解は次のとおりです。
-
class
X
には__dict__
<------->
クラス__slots__
がなく、そのスーパークラスにはすべて<=>が指定されています -
この場合、クラスの実際のスロットは、<=>とそのスーパークラスの<=>宣言の結合から構成されます。この結合がばらばらでない場合の動作は未定義です(そしてエラーになります)