本節知識點
1、__get__, __getattr__, __getattribute__的區別
2、__getattr__巧妙應用
3、延遲初始化(lazy property)
1、__get__, __getattr__, __getattribute__的區別
obj.__getattribute__(self, name)
在實例訪問屬性的時候無條件被調用。如果class中定義了__getattr__(),__getattr__()也不會被調用,除非顯示的調用或者沒有訪問到屬性引發AttributeError異常
obj.__getattr__(self, name)
當一般位置找不到屬性時,會調用__getattr__()返回一個值,如果不存在__getattr__()方法則會引發AttributeError異常。
obj.__get__(self, instance, owner)
如果類定義了它,則這個類可以被稱為descriptor(描述符),owner是所有者的類,instance是訪問descriptor的實例,如果不是通過實例訪問,而是通過類訪問的畫,instance則為None。
descriptor的實例自己訪問自己是不會觸發__get__,而會觸發__call__,只有descriptor作為其它類的屬性才有意義。
類里面是默認不會提供__get__()方法的
class C: a = 'abc' def __getattribute__(self, *args, **kwargs): print("__getattribute__() is called") # print(1, object.__getattribute__(self, *args, **kwargs)) return object.__getattribute__(self, *args, **kwargs) def __getattr__(self, name): print("__getattr__() is called") return name + " from getattr" def __get__(self, instance, owner): print("__get__() is called", instance, owner) # instance 是訪問desciptor的實例 return self def foo(self, x): print(x) def __call__(self, *args, **kwargs): print('__call__() is called', args, kwargs) class C2: d = C() if __name__ == '__main__': c = C() c2 = C2() print(c.a) # 1、__getattribute__() is called 2、abc 先調用__getattribute__()方法,然后獲取屬性 print(c.zzzzzzzz) # 1、__getattribute__() is called 2、__getattr__() is called 3、zzzzzzzz from getattr print(c2.d) # d是C類的實例,而C因為存在__get__()方法,而變成描述符,訪問文件描述符的實例的時候,默認應該是不走__getattribute__方法,所以也就更不可能調用到__getattr__()方法 # 1、__get__() is called 2、C2 object 3、C2 4、d指向的實例C object print('//////////////////////////////////') print(c2.d.a) # 同上面一樣會先獲取d,走__get__()方法,然后獲取a屬性的時候又會走__getattribute__ # 1、__get__() is called 2、C2 object 3、C2 4、__getattribute__ 5、abc print('..................................') print(c2.d.b) # 繼續上面的邏輯,是描述符,到獲取b屬性,沒有找到走__getattr__()方法返回 # 1、__get__() is called 2、C2 object 3、C2 4、__getattribute__ 5、__get__ 6、b from getattr print('----------------------------------') print(c()) # 實例本身調用是調用的call方法 print('**********************************') print(c.c) # 非文件描述符的還是老思路 getattribute==>getattr # 1、__getattribute__ 2、__getattr__ 3、c from getattr

參考:https://www.cnblogs.com/saolv/p/6890645.html 並做了簡單修改
__getattr__應用
根據上面的介紹大概可以發現__getattr__函數的作用
在進行屬性查找事,如果在實例跟類上都查找失敗的時候,就會走到__getattr__函數上,如果沒有定義這個函數,就會拋出AttributeError異常。所以,這里大概我們可以人為__getattr__方法是屬性查找的最后一個關卡。
示例1
很簡單的示例,通過__getattr__像訪問屬性一樣訪問鍵值對
class ObjectDict(dict): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 繼承父類dict的構造方法 def __getattr__(self, name): value = self[name] # self ==> {'asf': {'a': 1}, 'd': True} if isinstance(value, dict): value = ObjectDict(value) return value if __name__ == '__main__': od = ObjectDict(asf={'a': 1}, d=True) # 實例化對象od ==> {'asf': {'a': 1}, 'd': True} print(od.asf) # {'a': 1} print(od.asf.a) # 1 print(od.d) # True
實例化對象od,通過.attribute的方式來獲取key對應的value
示例2
class WidgetShowLazyLoad: def fetch_complex_attr(self, attrname): return attrname def __getattr__(self, name): if name not in self.__dict__: # 沒在__dict__字典內找到key self.__dict__[name] = self.fetch_complex_attr(name) # 添加attribute鍵值對 return self.__dict__[name] if __name__ == '__main__': w = WidgetShowLazyLoad() print('before', w.__dict__) # 剛開始實例化的時候,__dict__是空的字典 w.lazy_loaded_attr # 屬性查找,沒找到,調用__getattr__方法 print('after', w.__dict__) # {'lazy_loaded_attr': 'lazy_loaded_attr'}
結果:
before {} after {'lazy_loaded_attr': 'lazy_loaded_attr'}
這里的核心,就是利用了__getattr__屬性的機制,在查找不存在的屬性的時候進行改寫,動態懶加載出來一個字典。這個例子就是類實例的惰性初始化
示例3
import functools class lazy_attribute: """ A property that caches itself to the class object. """ def __init__(self, func): functools.update_wrapper(self, func, updated=[]) self.getter = func # complex_attr_may_not_need def __get__(self, obj, cls): # 調用類本身, obj自身調用為空 value = self.getter(cls) # complex_attr_may_not_need(Widget) setattr(cls, self.__name__, value) # self ==> complex_attr_may_not_need=lazy_attribute(complex_attr_may_not_need) # self 所以是lazy_attribute的對象,裝飾器的原理就是complex_attr_may_not_need=lazy_attribute實例化對象,所以self.__name__就是complex_attr_may_not_need # {'complex_attr_may_not_need': 332833500} return value class Widget: @lazy_attribute # complex_attr_may_not_need=lazy_attribute(complex_attr_may_not_need) def complex_attr_may_not_need(clz): print('complex_attr_may_not_need is needed now') return sum(i*i for i in range(1000)) if __name__ == '__main__': print(Widget.__dict__.get('complex_attr_may_not_need')) # <__main__.lazy_attribute object at 0x02B12450> Widget.complex_attr_may_not_need # complex_attr_may_not_need is needed now print(Widget.__dict__.get('complex_attr_may_not_need')) # 332833500
上面的代碼里面,用到了一個類裝飾器,它的使用其實也還沒沒有離開裝飾器的基礎定義,被裝飾的函數相當於
complex_attr_may_not_need=lazy_attribute(complex_attr_may_not_need)
這相當於實例化了這個函數,所以可以發現,__init__方法內有一個func參數。
functool.update_wrapper
使用update_wrapper(),從原始對象拷貝或加入現有對象
它可以把被封裝函數的__name__、__module__、__doc__和 __dict__都復制到封裝函數去
所以上述過程的執行流程可以理解為:
1、print(Widget.__dict__.get('complex_attr_may_not_need'))
此方法實際上是獲取Wdiget方法的__dict__字典內的complex_attr_may_not_need的key,但是因為complex_attr_may_not_need這個方法被類裝飾器lazy_attribute裝飾(裝飾器的本質,其實就是把被裝飾的方法傳進去),所以此時的結果應該是指向lazy_attribute的實例化對象的。
2、Widget.complex_attr_may_not_need
首先需要注意的是,這個方法傳入的參數complex_attr_may_not_need(clz)。類裝飾器初始化的時候,傳入了func就是被裝飾的方法,並賦值給了實例屬性getter,此時lazy_attribute里面有__get__()方法,所以lazy_attribute是一個描述符descriptor。因為是外部的Widget類調用的complex_attr_may_not_need方法,所以此時會先運訓__get__方法。value = self.getter(cls),其中self.getter=func即complex_attr_may_not_need方法,cls是調用的類本身即Widget,變成value = self.complex_attr_may_not_need(Widget),執行此方法,打印出complex_attr_may_not_need is needed now,value=計算的和,並內部設置了Widget類的complex_attr_may_not_need對應的value為計算和。
3、print(Widget.__dict__.get('complex_attr_may_not_need'))
現在這一句就很好理解了,取值並打印。
關於setattr不理解可以看下下面隨手寫的案例
class Foo: def __init__(self): setattr(Foo, 'aaa', 'bbb') def aaa(self): return 'a' f = Foo() print(Foo.__dict__)
執行一次,再把f = Foo()注釋掉執行一下,看看aaa的值就知道了。
實例4
class adaptee: def foo(self): print('foo in adaptee') def bar(self): print('bar in adaptee') class adapter: def __init__(self): self.adaptee = adaptee() def foo(self): print('foo in adapter') self.adaptee.foo() def __getattr__(self, name): return getattr(self.adaptee, name) if __name__ == '__main__': a = adapter() a.foo() # 1、foo in adapter 2、foo in adaptee a.bar() # 1、bar in adaptee
執行a.foo()應該是沒什么問題的,順序執行而已,但是在執行a.bar()的時候,因為adapter里面沒有此屬性,所以會走到最后一道關卡__getattr__方法,所以就很好理解了。
__getattr__使得實現adapter wrapper模式非常容易,我們都知道“組合優於繼承”,__getattr__實現的adapter就是以組合的形式。
實例5
class AlgoImpA: def __init__(self): self.obj_attr = 'obj_attr in AlgoImpA' def foo(self): print('foo in AlgoImpA') def bar(self): print('bar in AlgoImpA') class AlgoImpB(object): def __init__(self): self.obj_attr = 'obj_attr in AlgoImpB' def foo(self): print('foo in AlgoImpB') def bar(self): print('bar in AlgoImpB') class Algo: def __init__(self): self.imp_a = AlgoImpA() self.imp_b = AlgoImpB() self.cur_imp = self.imp_a def switch_imp(self): if self.cur_imp == self.imp_a: self.cur_imp = self.imp_b else: self.cur_imp = self.imp_a def __str__(self): return 'Algo with imp %s' % str(self.cur_imp) def __getattr__(self, name): return getattr(self.cur_imp, name) if __name__ == '__main__': algo = Algo() # imp_a print(algo) # Algo with imp IMPA print(algo.obj_attr) # obj_attr in AlgoImpA algo.foo() # foo in AlgoImpA algo.switch_imp() print(algo) # Algo with imp IMPB print(algo.obj_attr) # obj_attr in AlgoImpB algo.bar() # bar in AlgoImpB
3、延遲初始化(lazy property)
概念
Python對象的延遲初始化是指,當它第一次被創建時才進行初始化,或者保存第一次創建的結果,然后每次調用的時候直接返回結果。
延遲初始化主要是用於提高性能,避免浪費計算,並減少程序的內存需求。
property
首先,再來回顧下property的用法,property可以將屬性的訪問轉變成方法的調用
class Circle(object): def __init__(self, radius): self.radius = radius @property def area(self): return 3.14 * self.radius ** 2 c = Circle(4) print(c.radius) print(c.area)
area被定義成一個方法的形式,但是加上@property后,可以直接用c.area來調用,當成屬性訪問。
但這樣寫面臨的一個問題就是,每次調用c.are都會去計算一次,浪費cpu,怎么養才能只計算一次呢?這就是延遲初始化lazy property
lazy property
這里,我們趁熱打鐵,使用文件描述符來來實現。
class lazy(object): def __init__(self, func): self.func = func def __get__(self, instance, cls): val = self.func(instance) setattr(instance, self.func.__name__, val) return val class Circle(object): def __init__(self, radius): self.radius = radius @lazy def area(self): print('evalute') return 3.14 * self.radius ** 2 c = Circle(4) print(c.radius) print(c.area) print(c.area)
結果:
4
evalute
50.24
50.24
可以發現evalute只輸出一次。在lazy類里面,因為定義了__get__()方法,所以它是一個描述符。當第一次執行c.are時,python解釋器會先從_c._ditc__中查找,沒有找到就會去Circle.__dict__中進行查找,這個時候因為area被定義為描述符,所以調用__get__方法。
上面已經鋪墊過def __get__(self, instance, cls)里面三個參數的代表什么,所以很明了val = self.func(instance) ,是執行了area方法,並返回結果,最后setattr完成了賦值操作。這樣相當於設置c.__dict__['area']=val。
當我們再次調用c.area時,直接從c.__dict__中進行查找,這時就會直接返回之前計算好的值了。
這里再提供另一種方法
def lazy_property(func): attr_name = "_lazy_" + func.__name__ @property def _lazy_property(self): if not hasattr(self, attr_name): setattr(self, attr_name, func(self)) return getattr(self, attr_name) return _lazy_property class Circle(object): def __init__(self, radius): self.radius = radius @lazy_property def area(self): print('evalute') return 3.14 * self.radius ** 2 c = Circle(4) print("before first visit") print(c.__dict__ ) c.area print("after first visit") print(c.__dict__)
結果:
before first visit {'radius': 4} evalute after first visit {'radius': 4, '_lazy_area': 50.24}
表示,其實樓主也還是不太懂,等看明白了再來注解。
參考自http://python.jobbole.com/85553/
