[英]How does inheritance of __slots__ in subclasses actually work?
在關於插槽的Python 數據模型參考部分中,有一個關於使用__slots__
的注意事項列表。 我對第 1 項和第 6 項感到非常困惑,因為它們似乎相互矛盾。
第一項:
__slots__
的類繼承時,該類的__dict__
屬性將始終可訪問,因此子類中的__slots__
定義是沒有意義的。第六項:
__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 個字節”對象n
沒有考慮每個實例中涉及的所有內容。 您可以使用第三方擴展(例如pympler )做得更好:
>>> import pympler.asizeof
>>> pympler.asizeof.asizeof(w)
96
>>> pympler.asizeof.asizeof(n)
288
這更清楚地顯示了__slots__
節省的內存占用:對於像這種情況這樣的簡單對象,它__slots__
小於 200 字節,幾乎是對象總占用空間的 2/3。 現在,由於如今對於大多數應用程序而言,或多或少的兆字節並不那么重要,這也告訴您__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__
設置的屬性,但根本沒有,確實是一個微小的DIS優勢,得到它) .
現在,關於繼承:對於無字典的實例,其繼承鏈上的所有類也必須具有無字典的實例。 具有 dict-less 實例的類是那些定義__slots__
,以及大多數內置類型(實例具有 dict 的內置類型是那些可以在其實例上設置任意屬性的類,例如函數)。 插槽名稱中的重疊是不被禁止的,但它們是無用的並且會浪費一些內存,因為插槽是繼承的:
>>> class A(object): __slots__='a'
...
>>> class AB(A): __slots__='b'
...
>>> ab=AB()
>>> ab.a = ab.b = 23
>>>
如您所見,您可以在AB
實例上設置屬性a
- 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
所以真的沒有理由這樣做。
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
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 項感到非常困惑,因為它們似乎相互矛盾。
這些項目實際上並不相互矛盾。 的不實現類的第一個方面的子類__slots__
,該做的類第二問候子類實現__slots__
。
__slots__
的類的子類我越來越意識到,盡管 Python 文檔(正確地)被認為是偉大的,但它們並不完美,尤其是關於該語言較少使用的功能。 我會改變文檔如下:
從沒有
__slots__
的類繼承時,該類的__dict__
屬性將始終可訪問,因此子類中的。__slots__
定義是沒有意義的
__slots__
對於這樣的類仍然有意義。 它記錄了類屬性的預期名稱。 它還為這些屬性創建插槽 - 它們將獲得更快的查找並使用更少的空間。 它只允許其他屬性,這些屬性將分配給__dict__
。
下面是一個例子:
class Foo:
"""instances have __dict__"""
class Bar(Foo):
__slots__ = 'foo', 'bar'
Bar
不僅有它聲明的槽,它還有 Foo 的槽——包括__dict__
:
>>> b = Bar()
>>> b.foo = 'foo'
>>> b.quux = 'quux'
>>> vars(b)
{'quux': 'quux'}
>>> b.foo
'foo'
__slots__
__slots__
聲明的作用僅限於定義它的類。 因此,子類將有一個__dict__
除非它們還定義了__slots__
(它必須只包含任何其他插槽的名稱)。
嗯,這也不完全正確。 __slots__
聲明的作用不完全限於定義它的類。 例如,它們可能對多重繼承產生影響。
我會將其更改為:
對於定義
__slots__
的繼承樹中的類,子類將具有__dict__
除非它們還定義了__slots__
(它必須只包含任何其他插槽的名稱)。
我實際上已將其更新為:
__slots__
聲明的作用不限於定義它的類。 在父類中聲明的__slots__
在子類中可用。 然而,孩子的子類將得到一個__dict__
和__weakref__
,除非他們還定義__slots__
(它應該只包含任何額外的插槽的名稱)。
下面是一個例子:
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__
更多信息, 請在此處查看我的回答。)
從您鏈接的答案中:
__slots__
的正確使用是為了節省對象的空間。 而不是有一個動態的字典......
“從沒有__slots__
的類繼承時,該類的__dict__
屬性將始終可訪問”,因此添加您自己的__slots__
不能阻止對象具有__dict__
,也不能節省空間。
關於__slots__
不被繼承的一點有點遲鈍。 請記住,它是一個魔法屬性,它的行為不像其他屬性,然后重新閱讀它,說這個魔法插槽行為不是繼承的。 (這就是它的全部內容。)
我的理解如下:
類X
沒有__dict__
<------->
類X
及其超類都指定了__slots__
在這種情況下,類的實際插槽由X
及其超類的__slots__
聲明的並集組成; 如果這個聯合不是不相交的,則行為是未定義的(並且將成為錯誤)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.