簡體   English   中英

解釋 __dict__ 屬性

[英]Explain __dict__ attribute

我真的很困惑__dict__屬性。 我搜索了很多,但仍然不確定 output。

如果在 object、class 或 function 中使用,有人可以從零開始解釋該屬性的使用嗎?

基本上它包含描述相關對象的所有屬性。 它可用於更改或讀取屬性。 引用__dict__的文檔

用於存儲對象(可寫)屬性的字典或其他映射對象。

請記住,在 Python 中一切都是對象。 當我說一切時,我的意思是所有的東西,比如函數、類、對象等(是的,你沒看錯,類。類也是對象)。 例如:

def func():
    pass

func.temp = 1

print(func.__dict__)

class TempClass:
    a = 1
    def temp_function(self):
        pass

print(TempClass.__dict__)

會輸出

{'temp': 1}
{'__module__': '__main__', 
 'a': 1, 
 'temp_function': <function TempClass.temp_function at 0x10a3a2950>, 
 '__dict__': <attribute '__dict__' of 'TempClass' objects>, 
 '__weakref__': <attribute '__weakref__' of 'TempClass' objects>, 
 '__doc__': None}

__dict__可以得到一個object中的實例變量(數據屬性)作為一個字典。

所以,如果下面有Person class:

class Person:
    x1 = "Hello"
    x2 = "World"
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def test1(self):
        pass
        
    @classmethod
    def test2(cls):
        pass
    
    @staticmethod
    def test3():
        pass

obj = Person("John", 27)    
print(obj.__dict__) # Here

__dict__獲取nameage及其在字典中的值,如下所示:

{'name': 'John', 'age': 27}

並且,如果在實例化后添加新的實例變量gender如下所示:

# ...

obj = Person("John", 27)
obj.gender = "Male" # Here
print(obj.__dict__)

__dict__獲取nameagegender及其在字典中的值,如下所示:

{'name': 'John', 'age': 27, 'gender': 'Male'}

此外,如果使用dir()如下所示:

# ...

obj = Person("John", 27)
obj.gender = "Male" 
print(dir(obj)) # Here

我們可以得到一個 object 中的所有列表,如下所示:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', 
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__le__', '__lt__', '__module__', 
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', 
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 
'age', 'gender', 'name', 'test1', 'test2', 'test3', 'x1', 'x2']

而且,據我研究和問這個問題,沒有函數可以只獲取 static 或特殊變量或正常,class、static 或 Python 中的 object 中的特殊方法。

Python 文檔將__dict__定義為:

字典或其他映射 object 用於存儲對象的(可寫)屬性。

然而,這個定義有點模糊,導致__dict__的很多錯誤用法。

事實上,當您閱讀這個定義時,您能分辨出什么是“可寫”屬性,什么不是嗎?


例子

讓我們舉幾個例子來說明它是多么的混亂和不准確。

class A:

    foo = 1

    def __init__(self):
        self.bar = 2

    @property
    def baz(self):
        return self._baz

    @bar.setter
    def baz(self, value):
        self._baz = value

>>> a = A()
>>> a.foo
1
>>> a.bar
2

鑒於上面的 class A和已知的__dict__的定義,你能猜出a.__dict__的值是多少嗎?

  • fooa的可寫屬性嗎?
  • bara的可寫屬性嗎?
  • baza的可寫屬性嗎?
  • _baza的可寫屬性嗎?

這是答案:

>>> a.__dict__
{'bar': 2}

令人驚訝的是, foo沒有出現。 實際上,雖然可以通過a.foo訪問,但它是 class A的屬性,而不是實例a的屬性。

現在如果我們將它明確定義為 a 的屬性會a什么?

>>> a.foo = 1
>>> a.__dict__
{'bar': 2, 'foo': 1}

從我們的角度來看,沒有什么真正改變, a.foo仍然等於1 ,但現在它出現在__dict__中。 請注意,我們可以通過刪除a.foo來繼續使用它,例如:

>>> del a.foo
>>> a.__dict__
{'bar': 2}
>>> a.foo
1

這里發生的事情是我們刪除了實例屬性,並且調用a.foo再次回落到A.foo

現在讓我們看看baz 我們可以假設我們無法在a.__dict__中找到它,因為我們還沒有給它賦值。

>>> a.baz = 3

好了,現在我們定義a.baz 那么, baz是一個可寫屬性嗎? _baz呢?

>>> a.__dict__
{'bar': 2, '_baz': 3}

__dict__的角度來看, _baz是一個可寫屬性,但baz不是。 同樣,解釋是baz是 class A的屬性,而不是實例a

>>> A.__dict__['baz']
<property at 0x7fb1e30c9590>

a.baz只是一個在幕后調用A.baz.fget(a)的抽象層。

讓我們和我們親愛的朋友更加鬼鬼祟祟,挑戰它對“可寫”的定義。

class B:

    def __init__(self):
        self.foobar = 'baz'

    def __setattr__(self, name, value):
        if name == 'foobar' and 'foobar' in self.__dict__:
            # Allow the first definition of foobar
            # but prevent any subsequent redefinition
            raise TypeError("'foobar' is a read-only attribute")
        super().__setattr__(name, value)

>>> b = B()
>>> b.foobar
'baz'
>>> b.foobar = 'bazbar'
TypeError: 'foobar' is a read-only attribute
>>> # From our developer's perspective, 'foobar' is not a writable attribute
>>> # But __dict__ doesn't share this point of view
>>> b.__dict__
{'foobar': 'baz'}

那么__dict__到底是什么?

由於在上述示例中注意到的行為,我們現在可以更好地理解__dict__的實際作用。 但是我們需要從開發者的角度切換到計算機的角度:

__dict__包含存儲在程序的 memory 中的數據,用於這個特定的 object。

就是這樣, __dict__在我們的對象地址公開了實際存儲在 memory 中的內容。

Python 關於數據 model的文檔也將其定義為對象的命名空間

一個 class 實例有一個作為字典實現的命名空間,這是搜索屬性引用的第一個地方。 如果在那里找不到屬性,並且實例的 class 具有該名稱的屬性,則繼續搜索 class 屬性。

但是,我相信將__dict__視為對象的 memory 表可以更好地可視化此命名空間中包含的內容,以及不包含的內容。

但! 有一個陷阱...


你以為我們已經完成了__dict__

__dict__不是處理對象的 memory 足跡方式,而是一種方式。

確實有另一種方式: __slots__ 我不會在這里詳細說明它是如何工作的,如果你想了解更多關於__slots__已經有一個非常完整的答案 但對我們來說重要的是,如果定義了插槽:

class C:
    __slots__ = ('foo', 'bar')

    def __init__(self):
        self.foo = 1
        self.bar = 2

>>> c = C()
>>> c.foo
1
>>> c.bar
2
>>> c.__dict__
AttributeError: 'C' object has no attribute '__dict__'

我們可以對__dict__說“再見”。


那么什么時候應該使用__dict__呢?

正如我們所見, __dict__必須從計算機的角度來看,而不是從開發人員的角度來看。 通常情況下,我們認為 object 的“屬性”並不直接與 memory 中實際存儲的內容相關聯。尤其是使用屬性或__getattr__等實例時,它們增加了一個抽象級別,讓我們感到舒適。

盡管使用__dict__來檢查 object 的屬性在大多數情況下都可行,但我們不能 100% 依賴它。 對於用於編寫通用邏輯的東西來說,這是一種恥辱。

__dict__的用例應該僅限於檢查對象的 memory 內容。 這並不常見。 請記住,在定義插槽時, __dict__可能根本沒有定義(或者缺少一些實際存儲在內存中的屬性)。

它在 Python 的控制台中也非常有用,可以快速檢查類的屬性和方法。 或者一個對象的屬性(我知道我只是說我們不能依賴它,但在控制台中誰會關心它是否有時會失敗或者它是否不准確)。


謝謝,但是...那我該如何瀏覽對象的屬性呢?

我們看到__dict__經常被誤用,我們不能真正依賴它來檢查對象的屬性。 但是,正確的做法是什么? 有什么方法可以從開發人員的抽象角度瀏覽對象屬性嗎?

是的。 有幾種方法可以進行內省,正確的方法將取決於您實際想要得到什么。 實例屬性,class 屬性,屬性,私有屬性,甚至方法,...從技術上講,所有這些都是屬性,根據您的情況,您可能希望包括一些但排除其他的。 上下文也很重要。 也許您使用的庫已經通過其 API 公開了您想要的屬性。

但一般來說,你可以依賴inspect模塊

class D:

    foo = 1
    __slots = ('bar', '_baz')

    @property
    def baz(self):
        return self._baz

    @baz.setter
    def baz(self, value):
        self._baz = value

    def __init__(self):
        self.bar = 2
        self.baz = 3

    def pointless_method(self):
        pass


>>> import inspect
>>> dict((k, v) for k, v in inspect.getmembers(d) if k[0] != '_')
{'bar': 2, 'baz': 3, 'foo': 1}
>>> dict((k, getattr(d, k)) for k, v in inspect.getmembers(D) if inspect.isdatadescriptor(v) or inspect.isfunction(v))
{
 '__init__': <bound method D.__init__ of <__main__.D object at 0x7fb1e26a5b40>>,
 '_baz': 3,
 'bar': 2,
 'baz': 3,
 'pointless_method': <bound method D.pointless_method of <__main__.D object at 0x7fb1e26a5b40>>
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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