[Python] dir() 與 __dict__,__slots__ 的區別


  首先需要知道的是,dir() 是 Python 提供的一個 API 函數,dir() 函數會自動尋找一個對象的所有屬性,包括搜索 __dict__ 中列出的屬性。

 

  不是所有的對象都有 __dict__ 屬性。例如,如果你在一個類中添加了 __slots__ 屬性,那么這個類的實例將不會擁有 __dict__ 屬性,但是 dir() 仍然可以找到並列出它的實例所有有效屬性。

 

>>> class Foo(object):
...     __slots__ = ('bar', )
...     bar = 'spam'
... 

>>> Foo.__dict__
dict_proxy({'__module__': '__main__', 'bar': 'spam', '__slots__': ('bar',), '__doc__': None})

>>> Foo().__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__dict__'

>>> dir(Foo)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'bar']

>>> dir(Foo())
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'bar']

 

  同理許多內建類型都沒有 __dict__ 屬性,例如 list 沒有 __dict__ 屬性,但你仍然可以通過 dir() 列出 list 所有的屬性。

>>> [].__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__dict__'
>>> dir([])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

 

dir() 函數和類實例

 

  Python 的實例擁有它們自己的 __dict__,而它們對應的類也有自己的 __dict__:

>>> class Foo(object):
...     bar = 'spam'
... 
>>> Foo().__dict__
{}
>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__module__': '__main__', 'bar': 'spam', '__doc__': None})

 

  dir() 函數會遍歷 Foo,Foo() 和 object 的 __dict__ 屬性,從而為 Foo 類,它的實例以及它所有的被繼承類創建一個完整有效的屬性列表。

 

  當你對一個類設置屬性時,它的實例也會受到影響:

>>> f = Foo()
>>> f.ham
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'ham'
>>> Foo.ham = 'eggs'
>>> f.ham
'eggs'

 

  這是因為 'ham' 屬性被添加到了 Foo 類的 __dict__ 屬性中:

>>> Foo.__dict__
dict_proxy({'__module__': '__main__', 'bar': 'spam', 'ham': 'eggs', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None})
>>> f.__dict__
{}

 

  盡管 Foo 的實例 f 自己的 __dict__ 為空,但它還是能擁有作為類屬性的 'ham'。Python 中,一個對象的屬性查找順序遵循首先查找實例對象自己,然后是類,接着是類的父類。

 

  所以當你直接對一個實例設置屬性時,會看到實例的 __dict__ 中添加了該屬性,但它所屬的類的 __dict__ 卻不受影響。

>>> f.stack = 'overflow'
>>> f.__dict__
{'stack': 'overflow'}
>>> Foo.__dict__
dict_proxy({'__module__': '__main__', 'bar': 'spam', 'ham': 'eggs', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None})

 

  dir() 所做的不是查找一個對象的 __dict__ 屬性(這個屬性有時甚至都不存在),它使用的是對象的繼承關系來反饋一個對象的完整的有效屬性。

 

  一個實例的 __dict__ 屬性僅僅是那個實例的局部屬性集合,不包含該實例所有有效屬性。所以如果你想獲取一個對象所有有效屬性,你應該使用 dir() 來替代 __dict__ 或者 __slots__。

 

關於__slots__

 

  合理使用 __slots__ 屬性可以節省一個對象所消耗的空間。一個普通對象使用一個 dict 來保存它自己的屬性,你可以動態地向其中添加或刪除屬性,但是如果使用 __slots__ 屬性,那么該對象用來保存其自身屬性的結構一旦創建則不能再進行任何修改。因此使用 slot 結構的對象節省了一部分開銷。雖然有時這是一個很有用的優化方案,但是它也可能沒那么有用,因為如果 Python 解釋器足夠動態,那么它完全可以在向對象添加屬性時只請求該對象所使用的 dict。

 

  如何讓 CPython 變得足夠強大可以自動節省空間而不使用 __slots__ 屬性是一項重大工程,這也許就是為什么它依然存在於 P3k 中的原因吧。

 

Python 官方關於 __slots__ 的介紹

 

  在默認情況下,Python 的新類和舊類的實例都有一個字典來存儲屬性值。這對於那些沒什么實例屬性的對象來說太浪費空間了,當需要創建大量實例的時候,這個問題變得尤為突出。

 

  因此這種默認做法可以通過在新式類中定義一個 __slots__ 屬性從而得到解決。__slots__ 聲明中包含若干實例變量,並為每個實例預留恰好足夠的空間來保存每個變量,因為沒有為每個實例都創建一個字典,從而節省空間。

 

__slots__

 

這個類變量可以是 string,iterable 或者是被實例使用的一連串 string。如果在新式類中定義了 __slots__,__slots__ 會為聲明了的變量預留空間,同時阻止該類為它的每個實例自動創建 __dict__ 和 __weakref__。

 

使用 __slots__ 須知

 

  • 當繼承一個沒有 __slots__ 屬性的類時,子類會自動擁有 __dict__ 屬性,因此在子類中定義 __slots__ 是毫無意義的,你可以自由訪問子類的 __dict__ 屬性,所有未在 __slots__ 中聲明的屬性會保存在 __dict__ 中。
  • 在缺少 __dict__ 變量的情況下,實例不能接受新的不在 __slots__ 聲明內的變量作為屬性,如果試圖給這樣的類賦予一個未在 __slots__ 聲明的變量作為屬性會拋出 AttributeError。但是如果你確實需要能夠動態添加屬性,那么將字符串 '__dict__' 納入 __slots__ 的聲明中。如果同時在類中定義 __dict__ 和 __slots__ 是不行的,因為 __slots__ 會阻止 __dict__ 屬性的創建。(本條 Python 2.3 及其以后有效)
  • 在缺少 __weakref__ 變量的情況下,定義了 __slots__ 的類不支持對其實例的弱引用。如果需要,請將字符串 '__weakref__' 納入 __slots__ 聲明中。(本條Python 2.3及其以后有效)
  • __slots__ 的實現原理是在 class 級別為其所聲明的每個變量創建 descriptor,由此帶來的結果就是,類屬性不能用於為 __slots__ 聲明中的實例變量設置默認值,否則類屬性會覆寫描述符的賦值功能。
  • __slots__ 聲明只對它所處的類有效,因此,含有 __slots__ 的類的子類會自動創建一個 __dict__,除非在子類中也聲明一個 __slots__ (在這個 __slots__ 聲明應該只包含父類未聲明的變量)。
  • 如果一個類中定義了一個在基類中相同的變量,那么子類實例將不能訪問基類中定義的實例變量,除非直接從基類中讀取描述符。在將來,可能會添加一個檢查來阻止這種情況。
  • 非空的 __slots__ 對某些類無效,某些類是指源自含有長度屬性的內建類型,例如 long, str 和 tuple。
  • 任何非字符串的可迭代的對象都可以賦值給 __slots__ 。具有映射特性的對象也可以賦值為 __slots__。但是,在將來,每個鍵的值可能會賦予特別的含義。
  • __class__ 賦值只有當兩個類都具有相同的 __slots__ 時才有效。(本條 Python 2.6 及其以后有效)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM