Some question about python design

0x01

Q-1: why types (str, int, dict, ...) __dict__ attribute is dict_proxy object in python2 (or mappingproxy object in python3.3+) ?

>>> str.__dict__
dict_proxy({'__add__': <slot wrapper '__add__' of 'str' objects>,
            '__contains__': <slot wrapper '__contains__' of 'str' objects>,
            .....
            'zfill': <method 'zfill' of 'str' objects>})
>>> type(str.__dict__)
<type 'dictproxy'>
>>> s = "abc"
>>> s.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__dict__'

問題起源

在 Python 中對於某些 object __dict__ 屬性是隻讀的,好比對於 type object。然而,在 Python2.5-2.6 以前,仍是有一些通常性方法能夠獲取和改變 __dict__ 屬性的(without hacking with
gc.get_referrents(), that is)。這會致使一些使人費解的錯誤。python

dictproxy 是爲了用於保證 class.__dict__ 的 keys 必須是 strings, proxy 的機制防止了對於 class.__dict__ 的寫入操做, 所以只有 setattr() 能夠被用於添加屬性, class.__setattr__ 的實現確保了 keys-must-be-strings 的限制.app

若是咱們不使用一些 proxy 的機制,那麼 __dict__class.__dict__ 就能夠被寫入了。若是能夠寫入,也就能夠被刪除,而 class.__dict__ 中的屬性被刪除可能會致使解釋器崩潰。ui

The __dict__ attribute of some objects is read-only,
e.g. for type objects. However, there is a genericthis

way to still access and modify it (without hacking with
gc.get_referrents(), that is). This can lead to
obscure crashes. Attached is an example that shows
a potential "problem" involving putting strange keys
in the __dict__ of a type.code

This is probably very minor anyway. If we wanted toorm

fix this, we would need a __dict__ descriptor that
looks more cleverly at the object to which it is
applied.對象

BTW the first person who understand why the attachedip

program crashes gets a free coffee.get

------- [Armin Rigo] Bypassing dict readonlyness [Python2.5-2.6]string

Q-2: why built-in class instances don't have __dict__ attribute ?

Instances of types defined in C don't have a __dict__ attribute by default.

Q-3: what is the __dict__['__dict__'] attribute of a Python class?

>>> class A(object):
        x = "1"
        def __init__(self):
            self.x = "2"

>>> a = A()
>>> a.__dict__
{'x': '2'}
>>> type(a.__dict__)
dict
>>> A.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'A' objects>,
            '__doc__': None,
            '__init__': <function __main__.__init__>,
            '__module__': '__main__',
            '__weakref__': <attribute '__weakref__' of 'A' objects>,
            'x': '1'})
>>> type(A.__dict__)
dict_proxy
>>> A.__dict__["__dict__"]
<attribute '__dict__' of 'A' objects>
>>> type(A.__dict__["__dict__"])
getset_descriptor
>>> isinstance(A.__dict__["__dict__"], types.GetSetDescriptorType)
True
>>> A.__dict__["__dict__"].__get__(a, A)
{'x': '2'}
>>> a.__dict__
{'x': '2'}

首先,A.__dict__.__dict__A.__dict__['__dict__'] 是不一樣的,A.__dict__.__dict__ 不存在,A.__dict__['__dict__'] 是指 class 的實例擁有的 __dict__ 屬性,它是一個描述器,調用它會返回實例的 __dict__ 屬性。簡單來講,由於一個實例的 __dict__ 屬性不能(why?)保存在實例的 __dict__ 中,因此須要經過 class 中的一個 descriptor 來調用。(由於 python 是動態語言嘛,A.__dict__['__dict__'] 是一個 GetsetDescriptor,因此實例的屬性是有能力增長的)

  1. 對於 class A 的實例 a ,訪問 a.__dict__ 時的背後是經過 A.__dict__['__dict__'] 實現的(vars(A)['__dict__']
  2. 對於 class A,訪問 A.__dict__ <u>理論上</u> 是經過 type.__dict__['__dict__'] 實現的(vars(type)['__dict__']

完整解釋:

class 和實例訪問屬性都是經過屬性操做符 (class or metaclass's __getattribute__) 和 __dict__ 屬性/協議實現的。

對於通常的實例對象,__dict__ 會返回一個保存包含全部實例屬性的獨立的 dict 實例對象,對 __getattribute__ 的調用首先會訪問這個 dict,並獲取相應的實例屬性 (這個調用會在經過描述器協議訪問 class 屬性以前,也會在調用 __getattr__ 以前)。class 裏定義的 __dict__ 描述器實現了對這個 dict 的訪問。

  • x.name 的調用會按照如下順序: x.__dict__['name'], type(x).name.__get__(x, type(x)), type(x).name
  • x.__dict__ 會按照一樣順序,可是很明顯會跳過 x.__dict__['name'] 的訪問。

由於 x.__dict__ 不能保存在 x.__dict__["__dict__"] 中,對於 x.__dict__ 的訪問就會用描述器協議實現,x.__dict__ 的值就會保存在實例中的一個特殊字段裏。

對於 class 也會面臨相同的狀況,雖然 class.__dict__ 是一個假裝成 dict 的特殊的 proxy 對象,class.__dict__ 也不容許你對它進行
修改或替換行爲。這個特殊的 proxy 對象容許你,獲取那些定義在 class 而不是 class 的基類中的的屬性。

默認狀況下,vars(cls) 對於一個空類型,返回的對象包含三個描述器,__dict__ 用於保存實例中的屬性,__weakref__ 是用於 weakref 模塊的內部邏輯,__doc__ 是用於 class 的 docstring。前兩個描述器可能會由於定義了 __slots__ 而消失,沒有 __dict__ and __weakref__ 屬性,反而會有每個定義在 __slots__ 的屬性。此時,實例的屬性不會保存在 dict 中,訪問屬性將會經過相應的描述器實現。

refs: What is the dict__.__dict attribute of a Python class?

Q-4: what's the order of access instance's attribute ?

# -*- encoding: utf -*-


class RevealAccess(object):
    """A data descriptor that sets and returns values
       normally and prints a message logging their access.
    """
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        print('Retrieving', self.name, self.val)
        return self.val

    def __set__(self, obj, val):
        print('Updating', self.name, self.val)
        self.val = val


class Base(object):
    attr_1 = RevealAccess(10, 'var "x"')

    def __init__(self):
        self.attr_2 = RevealAccess(10, 'var "x"')

    def __getattribute__(self, *args, **kwargs):
        print("__getattribute__", args, kwargs)
        return super(Base, self).__getattribute__(*args, **kwargs)

    def __getattr__(self, *args, **kwargs):
        print("__getattr__", args, kwargs)
        try:
            origin = super(Base, self).__getattr__(*args, **kwargs)
            return origin
        except AttributeError as e:
            return "not found"


def main():
    b = Base()
    print("*********** start get b.attr_1 ***********")
    print(b.attr_1)
    print("*********** start get b.attr_2 ***********")
    print(b.attr_2)
    print("*********** start get b.attr_3 ***********")
    print(b.attr_3)

if __name__ == '__main__':
    main()

Output:
*********** start get b.attr_1 ***********
('__getattribute__', ('attr_1',), {})
('Retrieving', 'var "x"', 10)
10
*********** start get b.attr_2 ***********
('__getattribute__', ('attr_2',), {})
<__main__.RevealAccess object at 0x100b1abd0>
*********** start get b.attr_3 ***********
('__getattribute__', ('attr_3',), {})
('__getattr__', ('attr_3',), {})
not found

Refs:

相關文章
相關標籤/搜索