當前位置:
首頁 > 知識 > 一篇文章踩遍Python中的坑

一篇文章踩遍Python中的坑


程序君個人微信

和我聊聊編程和創業的事

加好友



作者丨未知


http://python.jobbole.com/86869/




下面的代碼會報錯,為什麼?



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-45646

dbd84a7> 

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 。



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

]




裝飾器




我想寫一個類裝飾器用來度量函數/方法運行時間



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





但是運行在方法上會報錯,為什麼?



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)




如果我堅持使用類裝飾器,應該如何修改?




使用類裝飾器後,在調用 func 函數的過程中其對應的 instance 並不會傳遞給 call 方法,造成其 mehtod unbound ,那麼解決方法是什麼呢?描述符賽高:



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 對吧,好的,我現在想作死,又寫出了如下的代碼,



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__ 中尋找屬性。因此,就會出現如上所述的情況。




 推薦↓↓↓ 






??

16個技術公眾號

】都在這裡!


涵蓋:程序員大咖、源碼共讀、程序員共讀、數據結構與演算法、黑客技術和網路安全、大數據科技、編程前端、Java、Python、Web編程開發、Android、iOS開發、Linux、資料庫研發、幽默程序員等。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發 的精彩文章:

可能是最淺顯易懂的一篇文章,關於Python引用、賦值、複製
python3 拼接字元串的7種方法

TAG:Python開發 |