下面的代碼會報錯,爲何?python
In [1]: class A(object): ...: x = 1 ...: gen = (x for _ in xrange(10)) ...: In [2]: if __name__ == "__main__": ...: print(list(A.gen)) ...: --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-2-bb15a85da3e4> in <module>() 1 if __name__ == "__main__": ----> 2 print(list(A.gen)) 3 <ipython-input-1-45646dbd84a7> in <genexpr>((_,)) 1 class A(object): 2 x = 1 ----> 3 gen = (x for _ in xrange(10)) 4 NameError: global name 'x' is not defined
這個問題是變量做用域問題,在gen=(x for _ in xrange(10))中gen是一個generator,在generator中變量有本身的一套做用域,與其他做用域空間相互隔離。所以,將會出現這樣的 NameError: name ‘x’ is not defined的問題,那麼解決方案是什麼呢?答案是:用lambda 。app
In [7]: class A(object): ...: x = 1 ...: gen = (lambda x: (x for _ in xrange(10)))(x) ...: In [9]: if __name__ == "__main__": print(list(A.gen)) ...: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
裝飾器函數
我想寫一個類裝飾器用來度量函數/方法運行時間ui
In [10]: import time In [11]: class Timeit(object): ....: def __init__(self, func): ....: self._wrapped = func ....: def __call__(self, *args, **kws): ....: start_time = time.time() ....: result = self._wrapped(*args, **kws) ....: print("elapsed time is %s ")%(time.time() - start_time) ....: return result ....: # 這個裝飾器可以運行在普通函數上: In [12]: @Timeit ....: def func(): ....: time.sleep(2) ....: return "invoking function func" ....: In [13]: if __name__ == "__main__": ....: func() ....: elapsed time is 2.00392723083
可是運行在方法上會報錯,爲何?code
In [14]: class A(object): ....: @Timeit ....: def func(self): ....: time.sleep(1) ....: return "invoking method func" ....: In [15]: if __name__ == "__main__": ....: a = A() ....: a.func() ....: --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-15-eb4fb185288b> in <module>() 1 if __name__ == "__main__": 2 a = A() ----> 3 a.func() 4 <ipython-input-11-aedd3f23516b> in __call__(self, *args, **kws) 4 def __call__(self, *args, **kws): 5 start_time = time.time() ----> 6 result = self._wrapped(*args, **kws) 7 print("elapsed time is %s ")%(time.time() - start_time) 8 return result TypeError: func() takes exactly 1 argument (0 given)
若是我堅持使用類裝飾器,應該如何修改?ip
使用類裝飾器後,在調用 func 函數的過程當中其對應的 instance 並不會傳遞給 call 方法,形成其 mehtod unbound ,那麼解決方法是什麼呢?描述符賽高:ci
In [16]: class Timeit(object): ....: def __init__(self, func): ....: self.func = func ....: def __call__(self, *args, **kwargs): ....: print("invoking Timer") ....: def __get__(self, instance, owner): ....: return lambda *args, **kwargs: self.func(instance, *args, **kwargs) ....: In [17]: class A(object): ....: @Timeit ....: def func(self): ....: time.sleep(1) ....: return "invoking method func" ....: In [18]: if __name__ == "__main__": ....: a = A() ....: a.func() ....:
Python調用機制作用域
咱們知道 call 方法能夠用來重載圓括號調用,好的,覺得問題就這麼簡單?Naive!文檔
In [19]: class A(object): ....: def __call__(self): ....: print("invoking __call__ from A!") ....: In [20]: if __name__ == "__main__": ....: a = A() ....: a() ....: invoking __call__ from A!
如今咱們能夠看到a()彷佛等價於a.__call__(),看起來很 Easy 對吧,好的,我如今想做死,又寫出了以下的代碼,get
In [21]: a.__call__ = lambda: "invoking __call__ from lambda" In [22]: a.__call__() Out[22]: 'invoking __call__ from lambda' In [23]: a() invoking __call__ from A!
請大佬們解釋下,爲何a()沒有調用出a.__call__()(此題由 USTC 王子博前輩提出)
緣由在於,在 Python 中,新式類( new class )的內建特殊方法,和實例的屬性字典是相互隔離的,具體能夠看看 Python 官方文檔對於這一狀況的說明
For new-style classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary. That behaviour is the reason why the following code raises an exception (unlike the equivalent example with old-style classes):
同時官方也給出了一個例子:
In [24]: class C(object): ....: pass ....: In [25]: c = C() In [26]: c.__len__ = lambda: 5 In [27]: len(c) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-27-c6494b964a51> in <module>() ----> 1 len(c) TypeError: object of type 'C' has no len() In [28]: c.__len__ Out[28]: <function __main__.<lambda>> In [29]: c.__len__() Out[29]: 5
回到咱們的例子上來,當咱們在執行 a.__call__=lambda:"invoking call from lambda" 時,的確在咱們在 a.__dict__ 中新增長了一個 key 爲 call 的 item,可是當咱們執行 a() 時,由於涉及特殊方法的調用,所以咱們的調用過程不會從 a.__dict__ 中尋找屬性,而是從 type(a).__dict__ 中尋找屬性。所以,就會出現如上所述的狀況。