python属性访问顺序_Python 对象属性的访问
在 Python 中,一切皆對象。屬性訪問可以理解為是從一個已有的對象中獲得另一個對象的方法。對象屬性的訪問涉及到對象的 __dict__ 屬性、描述符等概念,以及 __getattribute__、__getattr__ 等方法。
對象字典屬性
Python 中的對象有一個 __dict__ 屬性,其是一個字典類型,對象當前可用的屬性和方法都保存在合格字典中。它存儲著對象屬性的名稱與值的鍵值對。示例(在 Python 2.7 環境測試):
>>> class C(object):
... x = 1
...
>>> C.__dict__
dict_proxy({
'__dict__': ,
'x': 1, '__module__': '__console__',
'__weakref__': ,
'__doc__': None
})
>>> c = C()
>>> c.__dict__
{}
>>> c.y = 1
>>> c.__dict__
{'y': 1}
>>> c.x
1
>>> c.x = 2
>>> c.x
2
>>> C.x
1
>>> c.__dict__
{'y': 1, 'x': 2}
由上例應該注意到,類變量 x 存儲在類 C 的 dict 屬性中,而由 C 初始化的對象 c 的屬性 y 則在 c 的 dict 中。對象 c 仍然可以訪問其類型 C 中的類變量 x。但是,如果在對象 c 中重新設置屬性 x 之后,則 C 與 c 中各自有自己的 x 屬性,此時 c.x 不再訪問其類的屬性,而是訪問自己的 x 屬性。
還應注意到,類對象的 __dict__ 屬性為普通的 dict 類型,而類定義的 __dict__ 則為 dict_proxy 類型(在 Python3 中為 mappingproxy 類型)。類對象的該屬性是可以被直接修改的,而類的卻不行。因為類的 __dict__ 是只讀的,所以其命名中被加入了 proxy 字眼,這樣做的目的是為了防止其被意外修改而導致意想不到的錯誤發生。
>>> c.__dict__['x'] = 5
>>> C.__dict__['x'] = 6
Traceback (most recent call last):
File "", line 1, in
C.__dict__['x'] = 6
TypeError: 'dictproxy' object does not support item assignment
>>> c.x
5
>>> C.x
1
>>> c.__dict__ = {}
>>> C.__dict__ = {}
Traceback (most recent call last):
File "", line 1, in
C.__dict__ = {}
AttributeError: attribute '__dict__' of 'type' objects is not writable
>>> c.__dict__
{}
>>> c.x
1
>>> C.x
1
并不是所有的對象都有 __dict__ 這個屬性,例如實現了 __slots__ 屬性的類的對象。擁有 __slots__ 屬性的類在實例化對象時不會自動分配 __dict__, 只有在 __slots__ 中的屬性才能被使用,但它的設置只對對象真正的屬性有限制作用。如果是用 property 修飾的屬性以及屬性是一個描述符對象時是不受限制的。
描述符
描述符是實現了描述符協議的對象,本質上是一種擁有綁定行為的對象屬性。描述符的訪問行為被如下的描述符協議方法覆蓋:
__get__(self, obj, type=None) --> value
__set__(self, obj, value) --> None
__delete__(self, obj) --> None
描述符協議只是一種在模型中引用屬性時指定將要發生事件的方法。實現了以上描述符協議三個方法中任意一個的對象即是描述符。同時定義了 __get__ 和 __set__ 方法的對象就叫作 數據描述符(Data Descriptor),也被成為資源描述符。而只定義了 __get__ 方法的對象被叫做 非數據描述符(Non-data Descriptor)。實際上類方法(classmethod)即為一個非數據描述符。數據描述符與非數據描述會影響其被訪問的順序。如果實例中存在與數據描述符同名的屬性,則會優先訪問數據描述符。如果實例中存在與非數據描述符同名的屬性,則優先訪問實例屬性。一個描述符的定義類似如下形式:
class Descriptor(object):
def __init__(slef):
pass
def __get__(self, instance, owner):
"""用于訪問屬性
返回屬性的值,或者在所請求的屬性不存在的情況下出現 AttributeError 異常
"""
pass
def __set__(self, instance, value):
"""用于設置屬性值
將在屬性分配操作中調用,不會返回任何內容
"""
pass
def __delete__(self, instance):
"""用于刪除屬性
控制刪除操作,不會返回內容
"""
pass
描述符將某種特殊類型的類的實例指派給另一個類的屬性(注意: 這里是類屬性,而不是對象屬性,即描述符被分配給一個類,而不是實例)。描述符相當于是一種創建托管屬性的方法。托管屬性可以用于保護屬性不受修改,對傳遞的值做檢查,或自動更新某個依賴屬性的值。下面是一個簡單的示例:
class Descriptor(object):
def __init__(self, m):
self.m = m
def __get__(self, instance, owner):
return instance.n * self.m
def __set__(self, instance, value):
if value < 0:
raise ValueError("Negative value not allowed:%s" % value)
instance.n = value
class Foo(object):
bar = Descriptor(0)
har = Descriptor(1)
tar = Descriptor(2)
yar = Descriptor(3)
def __init__(self, n):
self.n = n
"""
>>> f = Foo(10)
>>> f.bar
0
>>> f.bar = 100
>>> f.bar
0
>>> f.har
100
>>> f.har = 10
>>> f.har
10
>>> f.yar
30
>>> f.yar = 12345
>>> f.yar
37035
"""
Python 中的類方法裝飾器 classmethod、staticmethod 實際上是一個非數據描述符,下面是他們的純 Python 實現示例:
class StaticMethod(object):
def __init__(self, f):
self.f = f
def __get__(self, instance, owner):
return self.f
class ClassMethod(object):
def __init__(self, f):
self.f = f
def __get__(self, instance, owner):
if owner is None:
owner = type(instance)
def _func(*args):
return self.f(owner, *args)
return _func
此外,Python 的 property 則是一個非數據描述符,它將對象屬性的訪問轉化為方法調用。類中的 property 裝飾器有一個缺陷,每次試圖訪問 property 屬性時其裝飾的函數都會被調用,而有時候可能只希望函數被調用一次。于是,可以模仿 property 來實現一個惰性屬性(lazy property),即在必要的時候(屬性被真正訪問到時)才初始化屬性。以下是惰性屬性描述符的實現示例:
class lazy_property(object):
def __init__(self, func):
self.func = func
def __get__(self, obj, cls):
if obj is None:
return self
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value
上例中實現一個非數據描述來達到惰性初始化屬性的目的。對象惰性屬性在被訪問時會調用 func 初始化得到 value,然后再在對象的 __dict__ 中設置同名的屬性,下一次再訪問屬性時,會直接返回 __dict__ 中保存的值,而不再去訪問描述符。這里涉及到了對象屬性的訪問優先級順序問題。
屬性訪問順序
Python 在對象屬性訪問時會無條件調用 __getttribute__() 方法。在屬性搜索的優先級鏈中,類字典中發現的數據描述符的優先級高于實例變量,實例變量優先級高于非數據描述符。如果提供了 __getattr__(),優先級鏈會為 __getattr__() 分配最低優先級。除非 __getttribute__() 顯示調用或者拋出 AttributeError 異常,否則 __getattr__() 將不會被調用。
描述符的調用是通過 _getattribute__() 方法實現的,重寫該方法可以阻止描述符的自動調用。數據描述符總是覆蓋類實例的 __dict__,而非數據描述符可能會被類實例的 __dict__ 覆蓋。_getattribute__() 方法的實現大概類似如下形式:
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v
需要注意的是,重寫 __getttribute__() 方法時,不能在其實現中使用 self.xxx 的形式訪問自己的屬性,這樣會導致無限遞歸。而需要訪問自己的屬性時,應該調用基類的方法。如 object.__getattribute__(self, name)。
下面詳細描述下對象屬性的訪問順序。假設有 class C, c = C(), 那么 c.x 的執行順序為:
(1)如果 x 是出現在 C 或其基類的 __dict__ 中,且是數據描述符, 那么調用其 __get__ 方法,否則
(2)如果 x 出現在 c 的 __dict__ 中,那么直接返回 c.__dict__['x'],否則
(3)如果 x 出現在 C 或其基類的 __dict__ 中,那么
(3.1)如果 x 是非數據描述符,那么調用其 __get__ 方法,否則
(3.2)返回 __dict__['x']
(4)如果 C 有 __getattr__ 方法,調用 __getattr__ 方法,否則
(5)拋出 AttributeError
處理缺失值
默認情況下,當訪問的屬性在對象中不存在時,會拋出 AttributeError 異常。而在有些場景中我們并不希望這樣,比如在我工作的項目中,當訪問一項配置時,如果該配置項不存在,我們希望其返回 None,而不是發生異常。這用 __getattr__ 方法很容易實現,該方法通常與 __setattr__、__delattr__ 方法配合使用,__setattr__ 方法會改變屬性的復制行為:
class Foo(object):
def __init__(self):
self.x = 1
def __getattr__(self, key):
try:
return self.__dict__[key]
except KeyError:
return None
def __setattr__(self, key, value):
self.__dict__[key] = value
def __delattr__(self, key):
try:
del self.__dict__[key]
except KeyError:
return None
如果對象是一個字典,則當訪問一個不存在的 key 時,會發生 KeyError 異常。字典也有一個方法可以用來處理缺失值,即 __missing__。這個方法雖然與屬性訪問無關,這里也做一下簡單的介紹。當訪問的鍵不存在時,dict.__getitem__() 方法會自動調用該方法。需要注意的是 dict 中并沒這個方法,需要在子類中實現。示例:
class FooDict(dict):
def __missing__(self, key):
self[key] = "hello"
return "hello"
fdict = FooDict()
print fdict
print fdict["bar"]
# 執行結果:
# {}
# hello
可以用該方法來實現一個缺省字典:
class defaultdict(dict):
def __init__(self, default_factory=None, *a, **kw):
dict.__init__(self, *a, **kw)
self.default_factory = default_factory
def __missing__(self, key):
self[key] = value = self.default_factory()
return value
參考資料
總結
以上是生活随笔為你收集整理的python属性访问顺序_Python 对象属性的访问的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python打包zip文件_python
- 下一篇: 求出歌手的得分python_哪位大侠帮我