簡體   English   中英

子類中 __slots__ 的繼承實際上是如何工作的?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM