2017-07-26 39 views
1

考慮這個例子,其中類A的所有實例的__dict__將指向全局字典shared如何在覆蓋其類的__dict__屬性後訪問實例字典?

shared = {'a': 1, 'b': 2} 

class A(object): 
    def __init__(self): 
     self.__dict__ = shared 

現在讓我們來測試的幾件事情:

>>> a = A() 
>>> b = A() 
>>> a.a, a.b, b.a, b.b 
(1, 2, 1, 2) 
>>> b.x = 100 
>>> shared 
{'a': 1, 'x': 100, 'b': 2} 
>>> a.x 
100 
>>> c = A() 
>>> c.a, c.b, c.x 
(1, 2, 100) 
>>> shared['foo'] = 'bar' 
>>> a.foo, b.foo, c.foo 
('bar', 'bar', 'bar') 
>>> a.__dict__, b.__dict__, c.__dict__ 
({'a': 1, 'x': 100, 'b': 2, 'foo': 'bar'}, 
{'a': 1, 'x': 100, 'b': 2, 'foo': 'bar'}, 
{'a': 1, 'x': 100, 'b': 2, 'foo': 'bar'} 
) 

全部按預期工作。


現在,讓我們添加一個名爲__dict__屬性調整A有點類。

shared = {'a': 1, 'b': 2} 

class A(object): 
    __dict__ = None 
    def __init__(self): 
     self.__dict__ = shared 

讓我們再次運行同一組的步驟:

>>> a = A() 
>>> b = A() 
>>> a.a, a.b, b.a, b.b 
AttributeError: 'A' object has no attribute 'a' 
>>> b.x = 100 
>>> shared 
{'a': 1, 'b': 2} 
>>> b.__dict__ # What happened to x? 
{'a': 1, 'b': 2} 
>>> a.x 
AttributeError: 'A' object has no attribute 'x' 
>>> c = A() 
>>> c.a, c.b, c.x 
AttributeError: 'A' object has no attribute 'a' 
>>> shared['foo'] = 'bar' 
>>> a.foo, b.foo, c.foo 
AttributeError: 'A' object has no attribute 'foo' 
>>> a.__dict__, b.__dict__, c.__dict__ 
({'a': 1, 'b': 2, 'foo': 'bar'}, 
{'a': 1, 'b': 2, 'foo': 'bar'}, 
{'a': 1, 'b': 2, 'foo': 'bar'} 
) 
>>> b.x # Where did this come from? 
100 

基於以上信息,第一種情況和預期一樣,但第二個也沒有,所以我想知道在添加類級別__dict__屬性後發生了什麼變化。我們現在可以以任何方式訪問正在使用的實例字典嗎?

+0

你不應該加用開頭和結尾這是留給系統定義的名稱雙下劃線屬性。請參閱[**保留的標識符類**](https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers)。 – martineau

+0

@martineau我意識到這一點。但這不是這個問題的關鍵。 –

+0

所以你想知道一個解決方案,因爲你沒有遵循指導原則(因爲某種原因存在)而被破壞了。 – martineau

回答

3

在第一種情況下,self.__dict__可以訪問由其類型提供的__dict__描述符。這個描述符允許它獲取底層實例字典,並且分別使用PyObject_GenericGetDictPyObject_GenericSetDict將其設置爲一個新字典。

>>> A.__dict__ 
mappingproxy(
{'__module__': '__main__', 
'__init__': <function A.__init__ at 0x1041fb598>, 
'__dict__': <attribute '__dict__' of 'A' objects>, 
'__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None 
}) 
>>> A.__dict__['__dict__'].__get__(a) 
{'a': 1, 'b': 2} 

當然,我們可以設置從這裏還有一個新的字典:

>>> new_dict = {} 
>>> A.__dict__['__dict__'].__set__(a, new_dict) # a.__dict__ = new_dict 
>>> a.spam = 'eggs' 
>>> a.__dict__ 
{'spam': 'eggs'} 
>>> new_dict 
{'spam': 'eggs'} 
>>> b = A() # Points to `shared` 
>>> b.__dict__ 
{'a': 1, 'b': 2} 

在第二種情況下,我們的類本身包含一個名爲__dict__的屬性,但仍__dict__屬性點到mappingproxy

以這種方式類
>>> A.__dict__ 
mappingproxy(
{'__module__': '__main__', 
'__dict__': None, 
'__init__': <function A.__init__ at 0x1041cfae8>, 
'__weakref__': <attribute '__weakref__' of 'A' objects>, 
'__doc__': None} 
) 

__dict__屬性是special attribute

>>> A.__weakref__ is A.__dict__['__weakref__'] 
True  
>>> A.__weakref__ = 1  
>>> A.__weakref__, A.__dict__['__weakref__'] 
(1, 1) 

>>> A.__dict__ = {}  
AttributeError: attribute '__dict__' of 'type' objects is not writable 

我們已經設置的屬性可以這樣訪問:

>>> repr(A.__dict__['__dict__']) 
'None' 

一個Python水平,我們現在已經失去了訪問實例字典,而是在內部類可以在使用tp_dictoffset找到。如在_PyObject_GetDictPtr中所做的那樣。

__getattribute____setattr__也使用_PyObject_GetDictPtr訪問底層實例字典。

要訪問正在使用的實例字典,我們實際上可以在Python中使用ctypes實現_PyObject_GetDictPtr。這是相當雄辯的@ user4815162342 here

import ctypes 

def magic_get_dict(o): 
    # find address of dict whose offset is stored in the type 
    dict_addr = id(o) + type(o).__dictoffset__ 

    # retrieve the dict object itself 
    dict_ptr = ctypes.cast(dict_addr, ctypes.POINTER(ctypes.py_object)) 
    return dict_ptr.contents.value 

繼續第二種情況:

>>> magic_get_dict(a) 
{'__dict__': {'a': 1, 'b': 2, 'foo': 'bar'}} # `a` has only one attribute i.e. __dict__ 
>>> magic_get_dict(b) 
{'__dict__': {'a': 1, 'b': 2, 'foo': 'bar'}, 'x': 100} # `x` found 
>>> magic_get_dict(b).update(shared) 
>>> b.a, b.b, b.foo, b.x 
(1, 2, 'bar', 100) 
+1

或者,您可以通過'ctypes.pythonapi._PyObject_GetDictPtr'直接訪問'_PyObject_GetDictPtr'。不過,您需要手動設置argtypes和restype。 – user2357112