先上一段代碼,來源是github。
class Borg(object):
__shared_state = {}
def __init__(self):
self.__dict__ = self.__shared_state
self.state = 'Init'
def __str__(self):
return self.state
class YourBorg(Borg):
pass
if __name__ == '__main__':
rm1 = Borg()
rm2 = Borg()
rm1.state = 'Idle'
rm2.state = 'Running'
print('rm1: {0}'.format(rm1))
print('rm2: {0}'.format(rm2))
rm2.state = 'Zombie'
print('rm1: {0}'.format(rm1))
print('rm2: {0}'.format(rm2))
print('rm1 id: {0}'.format(id(rm1)))
print('rm2 id: {0}'.format(id(rm2)))
rm3 = YourBorg()
print('rm1: {0}'.format(rm1))
print('rm2: {0}'.format(rm2))
print('rm3: {0}'.format(rm3))
### OUTPUT ###
# rm1: Running
# rm2: Running
# rm1: Zombie
# rm2: Zombie
# rm1 id: 140732837899224
# rm2 id: 140732837899296
# rm1: Init
# rm2: Init
# rm3: Init
上面這一段代碼,乍看挺神奇的,Borg 的各個實例共享了state。實現(xiàn)起來也很巧妙,利用了__dict__。 我們知道,python中__dict__存儲了該對象的一些屬性。類和實例分別擁有自己的__dict__,且實例會共享類的__dict__。
這里有一個我一直以來都搞混的知識點,在__init__ 中聲明的變量 ,以及在方法體之外聲明的變量分別是在哪里。很簡單的測試就能得到,在__init__中,self.xxx = xxx會把變量存在實例的__dict__中,僅會在該實例中能獲取到,而在方法體外聲明的,會在class的__dict__中。下面的簡單的測試。
class MyclassA():
in_class = {}
def __init__(self):
self.in_func = {}
a = MyclassA()
print MyclassA.__dict__ # {'in_class': {}, '__module__': '__main__', '__doc__': None, '__init__': <function __init__ at 0x10eb040c8>}
print a.__dict__ # {'in_func': {}}
有個有意思的問題,調(diào)用obj.key的搜索順序是咋樣的?
attr的搜索順序
這個很有意思,比我想象的要復(fù)雜很多,因為涉及到了descriptor,不僅僅只是查詢實例或者是類的__dict__這么簡單,直接上個結(jié)論
1.如果attr是一個Python自動產(chǎn)生的屬性,找到!(優(yōu)先級非常高!)
2.查找obj.__class__.__dict__,如果attr存在并且是data descriptor,返回data descriptor的__get__方法的結(jié)果,如果沒有繼續(xù)在obj.__class__的父類以及祖先類中尋找data descriptor
3.在obj.__dict__中查找,這一步分兩種情況,第一種情況是obj是一個普通實例,找到就直接返回,找不到進(jìn)行下一步。第二種情況是obj是一個類,依次在obj和它的父類、祖先類的__dict__中查找,如果找到一個descriptor就返回descriptor的__get__方法的結(jié)果,否則直接返回attr。如果沒有找到,進(jìn)行下一步。
4.在obj.__class__.__dict__中查找,如果找到了一個descriptor(插一句:這里的descriptor一定是non-data descriptor,如果它是data descriptor,第二步就找到它了)descriptor的__get__方法的結(jié)果。如果找到一個普通屬性,直接返回屬性值。如果沒找到,進(jìn)行下一步。
5.很不幸,Python終于受不了。在這一步,它raise AttributeError
用代碼解釋上面的過程(這其中包含了descriptor相關(guān)概念)
作者:劉縉
鏈接:https://www.zhihu.com/question/25391709/answer/30634637
來源:知乎
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
def object_getattr(obj, name):
'Look for attribute /name/ in object /obj/.'
# First look in class and base classes.
v, cls = class_lookup(obj.__class__, name)
if (v is not None) and hasattr(v, '__get__') and hasattr(v, '__set__'):
# Data descriptor. Overrides instance member.
return v.__get__(obj, cls)
w = obj.__dict__.get(name)
if w is not None:
# Found in object
return w
if v is not None:
if hasattr(v, '__get__'):
# Function-like descriptor.
return v.__get__(obj, cls)
else:
# Normal data member in class
return v
raise AttributeError(obj.__class__, name)
def class_lookup(cls, name):
'Look for attribute /name/ in class /cls/ and bases.'
v = cls.__dict__.get(name)
if v is not None:
# found in this class
return v, cls
# search in base classes
for i in cls.__bases__:
v, c = class_lookup(i, name)
if v is not None:
return v, c
# not found
return None
經(jīng)??吹接腥苏fobj.key = val 等價于obj.__dict__[key] = val,其實根據(jù)我們上面的那個搜索順序,在有descriptor的情況下,會不太一樣。
class DataDescriptor(object):
def __init__(self, init_value):
self.value = init_value
def __get__(self, instance, owner):
return "DataDescriptor __get__" + str(self.value)
def __set__(self, instance, value):
self.value = value
print "DataDescriptor __set__"
class ClassA(object):
val_descriptor = DataDescriptor(0)
val_normal = 0
if __name__ == '__main__':
a = ClassA()
print a.val_descriptor # DataDescriptor __get__0
print a.val_normal # 0
a.val_descriptor = "has change"
a.val_normal = "has change"
print a.val_descriptor # DataDescriptor __get__has change
print a.val_normal # has change
a.__dict__["val_descriptor"] = "change again"
a.__dict__["val_normal"] = "change again"
print a.val_descriptor # DataDescriptor __get__has change
print a.val_normal #change again
重點是最后兩行行,要修改的是一個descriptor 的話,obj.key = val 的方式是而已修改成功的,會調(diào)用descriptor 的__set__ 方法,而直接修改__dict__的方式則會有問題,因為在搜索attr的時候會先查找class的descriptor(對,即便a.__dict__["val_descriptor"] = DataDescriptor("change again")這樣子也是沒用的)。
關(guān)于__dict__,還有一個有趣的點。cls.__dict__ 是一個dictproxy,obj.__dict__ 是一個dict,前者是一個只讀對象,后者是一般的dict。
這意味著,對于一個實例,可以使用obj.__dict__[key] = val的方式去賦值,也可以使用obj.key = value的方式賦值(這種方式會調(diào)用__setattr__方法,會按照attr的搜索順序去搜索),而cls只能使用后者,即必須經(jīng)過__setattr__ ,stackoverflow 上有人解釋說是為了編譯器優(yōu)化。
個人認(rèn)為這種設(shè)置還有另外一種原因,即保護(hù)了descriptor的使用。
看上面那長段的代碼,我們發(fā)現(xiàn),如果一個類中設(shè)置了一個descriptor,后續(xù)沒有任何辦法繞過descriptor的__set__方法。
- 使用obj.key = val 的方式,毫無以為會調(diào)用descriptor的__set__方法。
- obj.__dict__[key] = val的方式,會修改實例的__dict__,但是按照搜索的順序會先搜索類的descriptor,所以相當(dāng)于沒有修改
- cls.key = val,會調(diào)用descriptor的__set__方法
- 于是只剩下直接修改cls.__dict__,但很可惜,這是一個只讀對象無法修改。
后記
非常的亂,這些東西都是我在看python的設(shè)計模式的時候逐漸發(fā)現(xiàn)問題慢慢搜索的,主要是為了給自己做個記錄,當(dāng)做一個隨筆,不太適合除了我以外的人看... 有空的時候會整理下。