2017-07-26 99 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