Ⅰ python裝飾器有什麼用
先來個形象比方
內褲可以用來遮羞,但是到了冬天它沒法為我們防風禦寒,聰明的人們發明了長褲,有了長褲後寶寶再也不冷了,裝飾器就像我們這里說的長褲,在不影響內褲作用的前提下,給我們的身子提供了保暖的功效。
再回到我們的主題
裝飾器本質上是一個Python函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外功能,裝飾器的返回值也是一個函數對象。它經常用於有切面需求的場景,比如:插入日誌、性能測試、事務處理、緩存、許可權校驗等場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼並繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。
先來看一個簡單例子:
def foo():
print('i am foo')
現在有一個新的需求,希望可以記錄下函數的執行日誌,於是在代碼中添加日誌代碼:
def foo():
print('i am foo')
logging.info("foo is running")
bar()、bar2()也有類似的需求,怎麼做?再寫一個logging在bar函數里?這樣就造成大量雷同的代碼,為了減少重復寫代碼,我們可以這樣做,重新定義一個函數:專門處理日誌 ,日誌處理完之後再執行真正的業務代碼
def use_logging(func):
logging.warn("%s is running" % func.__name__)
func()def bar():
print('i am bar')use_logging(bar)
邏輯上不難理解,
但是這樣的話,我們每次都要將一個函數作為參數傳遞給use_logging函數。而且這種方式已經破壞了原有的代碼邏輯結構,之前執行業務邏輯時,執行運行bar(),但是現在不得不改成use_logging(bar)。那麼有沒有更好的方式的呢?當然有,答案就是裝飾器。
簡單裝飾器
def use_logging(func):
def wrapper(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapperdef bar():
print('i am bar')bar = use_logging(bar)bar()
函數use_logging就是裝飾器,它把執行真正業務方法的func包裹在函數裡面,看起來像bar被use_logging裝飾了。在這個例子中,函數進入和退出時
,被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。
@符號是裝飾器的語法糖,在定義函數的時候使用,避免再一次賦值操作
def use_logging(func):
def wrapper(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper@use_loggingdef foo():
print("i am foo")@use_loggingdef bar():
print("i am bar")bar()
如上所示,這樣我們就可以省去bar =
use_logging(bar)這一句了,直接調用bar()即可得到想要的結果。如果我們有其他的類似函數,我們可以繼續調用裝飾器來修飾函數,而不用重復修改函數或者增加新的封裝。這樣,我們就提高了程序的可重復利用性,並增加了程序的可讀性。
裝飾器在Python使用如此方便都要歸因於Python的函數能像普通的對象一樣能作為參數傳遞給其他函數,可以被賦值給其他變數,可以作為返回值,可以被定義在另外一個函數內。
帶參數的裝飾器
裝飾器還有更大的靈活性,例如帶參數的裝飾器:在上面的裝飾器調用中,比如@use_logging,該裝飾器唯一的參數就是執行業務的函數。裝飾器的語法允許我們在調用時,提供其它參數,比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator@use_logging(level="warn")def foo(name='foo'):
print("i am %s" % name)foo()
上面的use_logging是允許帶參數的裝飾器。它實際上是對原有裝飾器的一個函數封裝,並返回一個裝飾器。我們可以將它理解為一個含有參數的閉包。當我
們使用@use_logging(level="warn")調用的時候,Python能夠發現這一層的封裝,並把參數傳遞到裝飾器的環境中。
類裝飾器
再來看看類裝飾器,相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。使用類裝飾器還可以依靠類內部的\_\_call\_\_方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
bar()
functools.wraps
使用裝飾器極大地復用了代碼,但是他有一個缺點就是原函數的元信息不見了,比如函數的docstring、__name__、參數列表,先看例子:
裝飾器
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
函數
@loggeddef f(x):
"""does some math"""
return x + x * x
該函數完成等價於:
def f(x):
"""does some math"""
return x + x * xf = logged(f)
不難發現,函數f被with_logging取代了,當然它的docstring,__name__就是變成了with_logging函數的信息了。
print f.__name__ # prints 'with_logging'print f.__doc__ # prints None
這個問題就比較嚴重的,好在我們有functools.wraps,wraps本身也是一個裝飾器,它能把原函數的元信息拷貝到裝飾器函數中,這使得裝飾器函數也有和原函數一樣的元信息了。
from functools import wrapsdef logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging@loggeddef f(x):
"""does some math"""
return x + x * xprint f.__name__ # prints 'f'print f.__doc__ # prints 'does some math'
內置裝飾器
@staticmathod、@classmethod、@property
裝飾器的順序
@a@b@cdef f ():
等效於
f = a(b(c(f)))
Ⅱ python 針對以下的函數,正確的函數調用有哪些
arg1是位置參數,調用時必須傳入數值(排除:B),位置參數傳入時順序要正確,且不能放在關鍵字參數的後面(排除:D),余:AC
Ⅲ python裝飾器是什麼意思
裝飾器是程序開發中經常會用到的一個功能,用好了裝飾器,開發效率如虎添翼,所以這也是Python面試中必問的問題,但對於好多小白來講,這個功能 有點繞,自學時直接繞過去了,然後面試問到了就掛了,因為裝飾器是程序開發的基礎知識,這個都 不會,別跟人家說你會Python, 看了下面的文章,保證你學會裝飾器。
1、先明白這段代碼
####第一波####
deffoo():
print'foo'
foo#表示是函數
foo()#表示執行foo函數
####第二波####
deffoo():
print'foo'
foo=lambdax:x+1
foo()#執行下面的lambda表達式,而不再是原來的foo函數,因為函數foo被重新定義了
2、需求來了
初創公司有N個業務部門,1個基礎平台部門,基礎平台負責提供底層的功能,如:資料庫操作、redis調用、監控API等功能。業務部門使用基礎功能時,只需調用基礎平台提供的功能即可。如下:
###############基礎平台提供的功能如下###############
deff1():
print'f1'
deff2():
print'f2'
deff3():
print'f3'
deff4():
print'f4'
###############業務部門A調用基礎平台提供的功能###############
f1()
f2()
f3()
f4()
###############業務部門B調用基礎平台提供的功能###############
f1()
f2()
f3()
f4()
目前公司有條不紊的進行著,但是,以前基礎平台的開發人員在寫代碼時候沒有關注驗證相關的問題,即:基礎平台的提供的功能可以被任何人使用。現在需要對基礎平台的所有功能進行重構,為平台提供的所有功能添加驗證機制,即:執行功能前,先進行驗證。
老大把工作交給 Low B,他是這么做的:
跟每個業務部門交涉,每個業務部門自己寫代碼,調用基礎平台的功能之前先驗證。誒,這樣一來基礎平台就不需要做任何修改了。
當天Low B 被開除了…
老大把工作交給 Low BB,他是這么做的:
###############基礎平台提供的功能如下###############
deff1():
#驗證1
#驗證2
#驗證3
print'f1'
deff2():
#驗證1
#驗證2
#驗證3
print'f2'
deff3():
#驗證1
#驗證2
#驗證3
print'f3'
deff4():
#驗證1
#驗證2
#驗證3
print'f4'
###############業務部門不變###############
###業務部門A調用基礎平台提供的功能###
f1()
f2()
f3()
f4()
###業務部門B調用基礎平台提供的功能###
f1()
f2()
f3()
f4()
過了一周 Low BB 被開除了…
老大把工作交給 Low BBB,他是這么做的:
只對基礎平台的代碼進行重構,其他業務部門無需做任何修改
###############基礎平台提供的功能如下###############
defcheck_login():
#驗證1
#驗證2
#驗證3
pass
deff1():
check_login()
print'f1'
deff2():
check_login()
print'f2'
deff3():
check_login()
print'f3'
deff4():
check_login()
print'f4'
老大看了下Low BBB 的實現,嘴角漏出了一絲的欣慰的笑,語重心長的跟Low BBB聊了個天:
老大說:
寫代碼要遵循開發封閉原則,雖然在這個原則是用的面向對象開發,但是也適用於函數式編程,簡單來說,它規定已經實現的功能代碼不允許被修改,但可以被擴展,即:
封閉:已實現的功能代碼塊
開放:對擴展開發
如果將開放封閉原則應用在上述需求中,那麼就不允許在函數 f1 、f2、f3、f4的內部進行修改代碼,老闆就給了Low BBB一個實現方案:
defw1(func):
definner():
#驗證1
#驗證2
#驗證3
returnfunc()
returninner
@w1
deff1():
print'f1'
@w1
deff2():
print'f2'
@w1
deff3():
print'f3'
@w1
deff4():
print'f4'
對於上述代碼,也是僅僅對基礎平台的代碼進行修改,就可以實現在其他人調用函數 f1 f2 f3 f4 之前都進行【驗證】操作,並且其他業務部門無需做任何操作。
Low BBB心驚膽戰的問了下,這段代碼的內部執行原理是什麼呢?
老大正要生氣,突然Low BBB的手機掉到地上,恰恰屏保就是Low BBB的女友照片,老大一看一緊一抖,喜笑顏開,交定了Low BBB這個朋友。詳細的開始講解了:
單獨以f1為例:
defw1(func):
definner():
#驗證1
#驗證2
#驗證3
returnfunc()
returninner
@w1
deff1():
print'f1'
當寫完這段代碼後(函數未被執行、未被執行、未被執行),python解釋器就會從上到下解釋代碼,步驟如下:
def w1(func): ==>將w1函數載入到內存
@w1
沒錯,從表面上看解釋器僅僅會解釋這兩句代碼,因為函數在沒有被調用之前其內部代碼不會被執行。
從表面上看解釋器著實會執行這兩句,但是 @w1 這一句代碼里卻有大文章,@函數名是python的一種語法糖。
如上例@w1內部會執行一下操作:
執行w1函數,並將 @w1 下面的函數作為w1函數的參數,即:@w1 等價於 w1(f1)
所以,內部就會去執行:
def inner:
#驗證
return f1() # func是參數,此時 func 等於 f1
return inner # 返回的 inner,inner代表的是函數,非執行函數
其實就是將原來的 f1 函數塞進另外一個函數中
將執行完的 w1 函數返回值賦值給@w1下面的函數的函數名
w1函數的返回值是:
def inner:
#驗證
return 原來f1() # 此處的 f1 表示原來的f1函數
然後,將此返回值再重新賦值給 f1,即:
新f1 =def inner:
#驗證
return 原來f1()
所以,以後業務部門想要執行 f1 函數時,就會執行 新f1 函數,在 新f1 函數內部先執行驗證,再執行原來的f1函數,然後將 原來f1 函數的返回值 返回給了業務調用者。
如此一來, 即執行了驗證的功能,又執行了原來f1函數的內容,並將原f1函數返回值 返回給業務調用著
Low BBB 你明白了嗎?要是沒明白的話,我晚上去你家幫你解決吧!!!
先把上述流程看懂,之後還會繼續更新…
3、問答時間
問題:被裝飾的函數如果有參數呢?
#一個參數
defw1(func):
definner(arg):
#驗證1
#驗證2
#驗證3
returnfunc(arg)
returninner
@w1
deff1(arg):
print'f1'
#兩個參數
defw1(func):
definner(arg1,arg2):
#驗證1
#驗證2
#驗證3
returnfunc(arg1,arg2)
returninner
@w1
deff1(arg1,arg2):
print'f1'
#三個參數
defw1(func):
definner(arg1,arg2,arg3):
#驗證1
#驗證2
#驗證3
returnfunc(arg1,arg2,arg3)
returninner
@w1
deff1(arg1,arg2,arg3):
print'f1'
問題:可以裝飾具有處理n個參數的函數的裝飾器?
defw1(func):
definner(*args,**kwargs):
#驗證1
#驗證2
#驗證3
returnfunc(*args,**kwargs)
returninner
@w1
deff1(arg1,arg2,arg3):
print'f1'
問題:一個函數可以被多個裝飾器裝飾嗎?
defw1(func):
definner(*args,**kwargs):
#驗證1
#驗證2
#驗證3
returnfunc(*args,**kwargs)
returninner
defw2(func):
definner(*args,**kwargs):
#驗證1
#驗證2
#驗證3
returnfunc(*args,**kwargs)
returninner
@w1
@w2
deff1(arg1,arg2,arg3):
print'f1'
問題:還有什麼更吊的裝飾器嗎?
#!/usr/bin/envpython
#coding:utf-8
defBefore(request,kargs):
print'before'
defAfter(request,kargs):
print'after'
defFilter(before_func,after_func):
defouter(main_func):
defwrapper(request,kargs):
before_result=before_func(request,kargs)
if(before_result!=None):
returnbefore_result;
main_result=main_func(request,kargs)
if(main_result!=None):
returnmain_result;
after_result=after_func(request,kargs)
if(after_result!=None):
returnafter_result;
returnwrapper
returnouter
@Filter(Before,After)
defIndex(request,kargs):
print'index'
Ⅳ python中如何限制輸入次數
python 限制函數調用次數的實例講解
發布時間:2018-04-21 09:58:18 作者:隨便起個名字啊
下面小編就為大家分享一篇python 限制函數調用次數的實例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
如下代碼,限制某個函數在某個時間段的調用次數,
靈感來源:python裝飾器-限制函數調用次數的方法(10s調用一次) 歡迎訪問
原博客中指定的是緩存,我這里換成限制訪問次數,異曲同工
#newtest.py
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import time
def stat_called_time(func):
cache={}
limit_times=[10]
def _called_time(*args,**kwargs):
key=func.__name__
if key in cache.keys():
[call_times,updatetime]=cache[key]
if time.time()-updatetime <60:
cache[key][0]+=1
else:
cache[key]=[1,time.time()]
else:
call_times=1
cache[key]=[call_times,time.time()]
print('調用次數: %s' % cache[key][0])
print('限制次數: %s' % limit_times[0])
if cache[key][0] <= limit_times[0]:
res=func(*args,**kwargs)
cache[key][1] = time.time()
return res
else:
print("超過調用次數了")
return None
return _called_time
@stat_called_time
def foo():
print("I'm foo")
if __name__=='__main__':
for i in range(10):
foo()
#test.py
from newtest import foo
import time
for i in range(30):
foo()
print('*'*20)
foo()
foo()
print('*'*20)
for i in range(60):
print(i)
time.sleep(1)
for i in range(11):
foo()
Ⅳ python新手問此def為何出錯
你的n生命周期在bar被調用的時候存在,隨著bar調用結束就消亡了,所以在bar()+n中,n已經不存在了,所以就報錯了
下面這個方法就不會報錯了,輸出結果5
deffoo():
m=1
defbar():
globaln
n=2
returnm+n
m=bar()+n
printm
foo()
Ⅵ 我執行一段python腳本報錯了,怎麼解決
在程序運行的過程中,如果發生了錯誤,可以事先約定返回一個錯誤代碼,這樣,就可以知道是否有錯,以及出錯的原因。在操作系統提供的調用中,返回錯誤碼非常常見。比如打開文件的函數open(),成功時返迴文件描述符(就是一個整數),出錯時返回-1。
用錯誤碼來表示是否出錯十分不便,因為函數本身應該返回的正常結果和錯誤碼混在一起,造成調用者必須用大量的代碼來判斷是否出錯:
復制代碼代碼如下:
def foo():
r = some_function()
if r==(-1):
return (-1)
# do something
return r
def bar():
r = foo()
if r==(-1):
print 'Error'
else:
pass
一旦出錯,還要一級一級上報,直到某個函數可以處理該錯誤(比如,給用戶輸出一個錯誤信息)。
所以高級語言通常都內置了一套try...except...finally...的錯誤處理機制,Python也不例外。
try
讓我們用一個例子來看看try的機制:
復制代碼代碼如下:
try:
print 'try...'
r = 10 / 0
print 'result:', r
except ZeroDivisionError, e:
print 'except:', e
finally:
print 'finally...'
print 'END'
當我們認為某些代碼可能會出錯時,就可以用try來運行這段代碼,如果執行出錯,則後續代碼不會繼續執行,而是直接跳轉至錯誤處理代碼,即except語句塊,執行完except後,如果有finally語句塊,則執行finally語句塊,至此,執行完畢。
上面的代碼在計算10 / 0時會產生一個除法運算錯誤:
復制代碼代碼如下:
try...
except: integer division or molo by zero
finally...
END
從輸出可以看到,當錯誤發生時,後續語句print 'result:', r不會被執行,except由於捕獲到ZeroDivisionError,因此被執行。最後,finally語句被執行。然後,程序繼續按照流程往下走。
如果把除數0改成2,則執行結果如下:
復制代碼代碼如下:
try...
result: 5
finally...
END
由於沒有錯誤發生,所以except語句塊不會被執行,但是finally如果有,則一定會被執行(可以沒有finally語句)。
你還可以猜測,錯誤應該有很多種類,如果發生了不同類型的錯誤,應該由不同的except語句塊處理。沒錯,可以有多個except來捕獲不同類型的錯誤:
復制代碼代碼如下:
try:
print 'try...'
r = 10 / int('a')
print 'result:', r
except ValueError, e:
print 'ValueError:', e
except ZeroDivisionError, e:
print 'ZeroDivisionError:', e
finally:
print 'finally...'
print 'END'
int()函數可能會拋出ValueError,所以我們用一個except捕獲ValueError,用另一個except捕獲ZeroDivisionError。
此外,如果沒有錯誤發生,可以在except語句塊後面加一個else,當沒有錯誤發生時,會自動執行else語句:
復制代碼代碼如下:
try:
print 'try...'
r = 10 / int('a')
print 'result:', r
except ValueError, e:
print 'ValueError:', e
except ZeroDivisionError, e:
print 'ZeroDivisionError:', e
else:
print 'no error!'
finally:
print 'finally...'
print 'END'
Python的錯誤其實也是class,所有的錯誤類型都繼承自BaseException,所以在使用except時需要注意的是,它不但捕獲該類型的錯誤,還把其子類也「一網打盡」。比如:
復制代碼代碼如下:
try:
foo()
except StandardError, e:
print 'StandardError'
except ValueError, e:
print 'ValueError'
第二個except永遠也捕獲不到ValueError,因為ValueError是StandardError的子類,如果有,也被第一個except給捕獲了。
Python所有的錯誤都是從BaseException類派生的
使用try...except捕獲錯誤還有一個巨大的好處,就是可以跨越多層調用,比如函數main()調用foo(),foo()調用bar(),結果bar()出錯了,這時,只要main()捕獲到了,就可以處理:
復制代碼代碼如下:
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except StandardError, e:
print 'Error!'
finally:
print 'finally...'
也就是說,不需要在每個可能出錯的地方去捕獲錯誤,只要在合適的層次去捕獲錯誤就可以了。這樣一來,就大大減少了寫try...except...finally的麻煩。
調用堆棧
如果錯誤沒有被捕獲,它就會一直往上拋,最後被Python解釋器捕獲,列印一個錯誤信息,然後程序退出。來看看err.py:
復制代碼代碼如下:
# err.py:
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
bar('0')
main()
執行,結果如下:
復制代碼代碼如下:
$ python err.py
Traceback (most recent call last):
File "err.py", line 11, in <mole>
main()
File "err.py", line 9, in main
bar('0')
File "err.py", line 6, in bar
return foo(s) * 2
File "err.py", line 3, in foo
return 10 / int(s)
ZeroDivisionError: integer division or molo by zero
出錯並不可怕,可怕的是不知道哪裡出錯了。解讀錯誤信息是定位錯誤的關鍵。我們從上往下可以看到整個錯誤的調用函數鏈:
錯誤信息第1行:
復制代碼代碼如下:
Traceback (most recent call last):
告訴我們這是錯誤的跟蹤信息。
第2行:
復制代碼代碼如下:
File "err.py", line 11, in <mole>
main()
調用main()出錯了,在代碼文件err.py的第11行代碼,但原因是第9行:
復制代碼代碼如下:
File "err.py", line 9, in main
bar('0')
調用bar('0')出錯了,在代碼文件err.py的第9行代碼,但原因是第6行:
復制代碼代碼如下:
File "err.py", line 6, in bar
return foo(s) * 2
原因是return foo(s) * 2這個語句出錯了,但這還不是最終原因,繼續往下看:
復制代碼代碼如下:
File "err.py", line 3, in foo
return 10 / int(s)
原因是return 10 / int(s)這個語句出錯了,這是錯誤產生的源頭,因為下面列印了:
復制代碼代碼如下:
ZeroDivisionError: integer division or molo by zero
根據錯誤類型ZeroDivisionError,我們判斷,int(s)本身並沒有出錯,但是int(s)返回0,在計算10 / 0時出錯,至此,找到錯誤源頭。
記錄錯誤
如果不捕獲錯誤,自然可以讓Python解釋器來列印出錯誤堆棧,但程序也被結束了。既然我們能捕獲錯誤,就可以把錯誤堆棧列印出來,然後分析錯誤原因,同時,讓程序繼續執行下去。
Python內置的logging模塊可以非常容易地記錄錯誤信息:
復制代碼代碼如下:
# err.py
import logging
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except StandardError, e:
logging.exception(e)
main()
print 'END'
同樣是出錯,但程序列印完錯誤信息後會繼續執行,並正常退出:
復制代碼代碼如下:
$ python err.py
ERROR:root:integer division or molo by zero
Traceback (most recent call last):
File "err.py", line 12, in main
bar('0')
File "err.py", line 8, in bar
return foo(s) * 2
File "err.py", line 5, in foo
return 10 / int(s)
ZeroDivisionError: integer division or molo by zero
END
通過配置,logging還可以把錯誤記錄到日誌文件里,方便事後排查。
拋出錯誤
因為錯誤是class,捕獲一個錯誤就是捕獲到該class的一個實例。因此,錯誤並不是憑空產生的,而是有意創建並拋出的。Python的內置函數會拋出很多類型的錯誤,我們自己編寫的函數也可以拋出錯誤。
如果要拋出錯誤,首先根據需要,可以定義一個錯誤的class,選擇好繼承關系,然後,用raise語句拋出一個錯誤的實例:
復制代碼代碼如下:
# err.py
class FooError(StandardError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n
執行,可以最後跟蹤到我們自己定義的錯誤:
復制代碼代碼如下:
$ python err.py
Traceback (most recent call last):
...
__main__.FooError: invalid value: 0
只有在必要的時候才定義我們自己的錯誤類型。如果可以選擇Python已有的內置的錯誤類型(比如ValueError,TypeError),盡量使用Python內置的錯誤類型。
最後,我們來看另一種錯誤處理的方式:
復制代碼代碼如下:
# err.py
def foo(s):
n = int(s)
return 10 / n
def bar(s):
try:
return foo(s) * 2
except StandardError, e:
print 'Error!'
raise
def main():
bar('0')
main()
在bar()函數中,我們明明已經捕獲了錯誤,但是,列印一個Error!後,又把錯誤通過raise語句拋出去了,這不有病么?
其實這種錯誤處理方式不但沒病,而且相當常見。捕獲錯誤目的只是記錄一下,便於後續追蹤。但是,由於當前函數不知道應該怎麼處理該錯誤,所以,最恰當的方式是繼續往上拋,讓頂層調用者去處理。
raise語句如果不帶參數,就會把當前錯誤原樣拋出。此外,在except中raise一個Error,還可以把一種類型的錯誤轉化成另一種類型:
復制代碼代碼如下:
try:
10 / 0
except ZeroDivisionError:
raise ValueError('input error!')
只要是合理的轉換邏輯就可以,但是,決不應該把一個IOError轉換成毫不相乾的ValueError。
小結
Python內置的try...except...finally用來處理錯誤十分方便。出錯時,會分析錯誤信息並定位錯誤發生的代碼位置才是最關鍵的。
程序也可以主動拋出錯誤,讓調用者來處理相應的錯誤。但是,應該在文檔中寫清楚可能會拋出哪些錯誤,以及錯誤產生的原因。
Ⅶ 如何理解Python裝飾器
理解Python中的裝飾器
@makebold
@makeitalic
def say():
return "Hello"
列印出如下的輸出:
<b><i>Hello<i></b>
你會怎麼做?最後給出的答案是:
def makebold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
def makeitalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makebold
@makeitalic
def hello():
return "hello world"
print hello() ## 返回 <b><i>hello world</i></b>
現在我們來看看如何從一些最基礎的方式來理解Python的裝飾器。英文討論參考Here。
裝飾器是一個很著名的設計模式,經常被用於有切面需求的場景,較為經典的有插入日誌、性能測試、事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函數中與函數功能本身無關的雷同代碼並繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。
1.1. 需求是怎麼來的?
裝飾器的定義很是抽象,我們來看一個小例子。
def foo():
print 'in foo()'
foo()
這是一個很無聊的函數沒錯。但是突然有一個更無聊的人,我們稱呼他為B君,說我想看看執行這個函數用了多長時間,好吧,那麼我們可以這樣做:
import time
def foo():
start = time.clock()
print 'in foo()'
end = time.clock()
print 'used:', end - start
foo()
很好,功能看起來無懈可擊。可是蛋疼的B君此刻突然不想看這個函數了,他對另一個叫foo2的函數產生了更濃厚的興趣。
怎麼辦呢?如果把以上新增加的代碼復制到foo2里,這就犯了大忌了~復制什麼的難道不是最討厭了么!而且,如果B君繼續看了其他的函數呢?
1.2. 以不變應萬變,是變也
還記得嗎,函數在Python中是一等公民,那麼我們可以考慮重新定義一個函數timeit,將foo的引用傳遞給他,然後在timeit中調用foo並進行計時,這樣,我們就達到了不改動foo定義的目的,而且,不論B君看了多少個函數,我們都不用去修改函數定義了!
import time
def foo():
print 'in foo()'
def timeit(func):
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
timeit(foo)
看起來邏輯上並沒有問題,一切都很美好並且運作正常!……等等,我們似乎修改了調用部分的代碼。原本我們是這樣調用的:foo(),修改以後變成了:timeit(foo)。這樣的話,如果foo在N處都被調用了,你就不得不去修改這N處的代碼。或者更極端的,考慮其中某處調用的代碼無法修改這個情況,比如:這個函數是你交給別人使用的。
1.3. 最大限度地少改動!
既然如此,我們就來想想辦法不修改調用的代碼;如果不修改調用代碼,也就意味著調用foo()需要產生調用timeit(foo)的效果。我們可以想到將timeit賦值給foo,但是timeit似乎帶有一個參數……想辦法把參數統一吧!如果timeit(foo)不是直接產生調用效果,而是返回一個與foo參數列表一致的函數的話……就很好辦了,將timeit(foo)的返回值賦值給foo,然後,調用foo()的代碼完全不用修改!
#-*- coding: UTF-8 -*-
import time
def foo():
print 'in foo()'
# 定義一個計時器,傳入一個,並返回另一個附加了計時功能的方法
def timeit(func):
# 定義一個內嵌的包裝函數,給傳入的函數加上計時功能的包裝
def wrapper():
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
# 將包裝後的函數返回
return wrapper
foo = timeit(foo)
foo()
這樣,一個簡易的計時器就做好了!我們只需要在定義foo以後調用foo之前,加上foo = timeit(foo),就可以達到計時的目的,這也就是裝飾器的概念,看起來像是foo被timeit裝飾了。在在這個例子中,函數進入和退出時需要計時,這被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。與傳統編程習慣的從上往下執行方式相比較而言,像是在函數執行的流程中橫向地插入了一段邏輯。在特定的業務領域里,能減少大量重復代碼。面向切面編程還有相當多的術語,這里就不多做介紹,感興趣的話可以去找找相關的資料。
這個例子僅用於演示,並沒有考慮foo帶有參數和有返回值的情況,完善它的重任就交給你了 :)
上面這段代碼看起來似乎已經不能再精簡了,Python於是提供了一個語法糖來降低字元輸入量。
import time
def timeit(func):
def wrapper():
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
return wrapper
@timeit
def foo():
print 'in foo()'
foo()
重點關注第11行的@timeit,在定義上加上這一行與另外寫foo = timeit(foo)完全等價,千萬不要以為@有另外的魔力。除了字元輸入少了一些,還有一個額外的好處:這樣看上去更有裝飾器的感覺。
-------------------
要理解python的裝飾器,我們首先必須明白在Python中函數也是被視為對象。這一點很重要。先看一個例子:
def shout(word="yes") :
return word.capitalize()+" !"
print shout()
# 輸出 : 'Yes !'
# 作為一個對象,你可以把函數賦給任何其他對象變數
scream = shout
# 注意我們沒有使用圓括弧,因為我們不是在調用函數
# 我們把函數shout賦給scream,也就是說你可以通過scream調用shout
print scream()
# 輸出 : 'Yes !'
# 還有,你可以刪除舊的名字shout,但是你仍然可以通過scream來訪問該函數
del shout
try :
print shout()
except NameError, e :
print e
#輸出 : "name 'shout' is not defined"
print scream()
# 輸出 : 'Yes !'
我們暫且把這個話題放旁邊,我們先看看python另外一個很有意思的屬性:可以在函數中定義函數:
def talk() :
# 你可以在talk中定義另外一個函數
def whisper(word="yes") :
return word.lower()+"...";
# ... 並且立馬使用它
print whisper()
# 你每次調用'talk',定義在talk裡面的whisper同樣也會被調用
talk()
# 輸出 :
# yes...
# 但是"whisper" 不會單獨存在:
try :
print whisper()
except NameError, e :
print e
#輸出 : "name 'whisper' is not defined"*
函數引用
從以上兩個例子我們可以得出,函數既然作為一個對象,因此:
1. 其可以被賦給其他變數
2. 其可以被定義在另外一個函數內
這也就是說,函數可以返回一個函數,看下面的例子:
def getTalk(type="shout") :
# 我們定義另外一個函數
def shout(word="yes") :
return word.capitalize()+" !"
def whisper(word="yes") :
return word.lower()+"...";
# 然後我們返回其中一個
if type == "shout" :
# 我們沒有使用(),因為我們不是在調用該函數
# 我們是在返回該函數
return shout
else :
return whisper
# 然後怎麼使用呢 ?
# 把該函數賦予某個變數
talk = getTalk()
# 這里你可以看到talk其實是一個函數對象:
print talk
#輸出 : <function shout at 0xb7ea817c>
# 該對象由函數返回的其中一個對象:
print talk()
# 或者你可以直接如下調用 :
print getTalk("whisper")()
#輸出 : yes...
還有,既然可以返回一個函數,我們可以把它作為參數傳遞給函數:
def doSomethingBefore(func) :
print "I do something before then I call the function you gave me"
print func()
doSomethingBefore(scream)
#輸出 :
#I do something before then I call the function you gave me
#Yes !
這里你已經足夠能理解裝飾器了,其他它可被視為封裝器。也就是說,它能夠讓你在裝飾前後執行代碼而無須改變函數本身內容。
手工裝飾
那麼如何進行手動裝飾呢?
# 裝飾器是一個函數,而其參數為另外一個函數
def my_shiny_new_decorator(a_function_to_decorate) :
# 在內部定義了另外一個函數:一個封裝器。
# 這個函數將原始函數進行封裝,所以你可以在它之前或者之後執行一些代碼
def the_wrapper_around_the_original_function() :
# 放一些你希望在真正函數執行前的一些代碼
print "Before the function runs"
# 執行原始函數
a_function_to_decorate()
# 放一些你希望在原始函數執行後的一些代碼
print "After the function runs"
#在此刻,"a_function_to_decrorate"還沒有被執行,我們返回了創建的封裝函數
#封裝器包含了函數以及其前後執行的代碼,其已經准備完畢
return the_wrapper_around_the_original_function
# 現在想像下,你創建了一個你永遠也不遠再次接觸的函數
def a_stand_alone_function() :
print "I am a stand alone function, don't you dare modify me"
a_stand_alone_function()
#輸出: I am a stand alone function, don't you dare modify me
# 好了,你可以封裝它實現行為的擴展。可以簡單的把它丟給裝飾器
# 裝飾器將動態地把它和你要的代碼封裝起來,並且返回一個新的可用的函數。
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#輸出 :
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
現在你也許要求當每次調用a_stand_alone_function時,實際調用卻是a_stand_alone_function_decorated。實現也很簡單,可以用my_shiny_new_decorator來給a_stand_alone_function重新賦值。
a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#輸出 :
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
# And guess what, that's EXACTLY what decorators do !
裝飾器揭秘
前面的例子,我們可以使用裝飾器的語法:
@my_shiny_new_decorator
def another_stand_alone_function() :
print "Leave me alone"
another_stand_alone_function()
#輸出 :
#Before the function runs
#Leave me alone
#After the function runs
當然你也可以累積裝飾:
def bread(func) :
def wrapper() :
print "</''''''\>"
func()
print "<\______/>"
return wrapper
def ingredients(func) :
def wrapper() :
print "#tomatoes#"
func()
print "~salad~"
return wrapper
def sandwich(food="--ham--") :
print food
sandwich()
#輸出 : --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs :
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>
使用python裝飾器語法:
@bread
@ingredients
def sandwich(food="--ham--") :
print food
sandwich()
#輸出 :
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>
Ⅷ python問題。 編程語言
你的意思是不是指如果 foo 這個函數里的 a 變數如果獲取到的是 True 的時候,要讓 foo 這個函數外面的傳入的變數的 b 值變成 False?
如果是,那麼代碼如下(注意程序縮進):
a=False
b=True
def foo():
global a # 將變數提檔,使用公共變數而不是私有變數
global b
if a:
b=False
return '123'
if b:
return '456'
a=True
b=True
print(foo())
print('Now B is: ' + str(b))
主要問題是公共變數與私有變數的問題。
Ⅸ 如何理解 Python
簡單例子:
def foo():
print('i am foo')
現在有一個新的需求,希望可以記錄下函數的執行日誌,於是在代碼中添加日誌代碼:
def foo():
print('i am foo')
logging.info("foo is running")
bar()、bar2()也有類似的需求,怎麼做?再寫一個logging在bar函數里?這樣就造成大量雷同的代碼,為了減少重復寫代碼,我們可以這樣做,重新定義一個函數:專門處理日誌 ,日誌處理完之後再執行真正的業務代碼
def use_logging(func):
logging.warn("%s is running" % func.__name__)
func()
def bar():
print('i am bar')
use_logging(bar)
邏輯上不難理解, 但是這樣的話,我們每次都要將一個函數作為參數傳遞給use_logging函數。而且這種方式已經破壞了原有的代碼邏輯結構,之前執行業務邏輯時,執行運行bar(),但是現在不得不改成use_logging(bar)。那麼有沒有更好的方式的呢?當然有,答案就是裝飾器。
簡單裝飾器
def use_logging(func):
def wrapper(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper
def bar():
print('i am bar')
bar = use_logging(bar)
bar()
函數use_logging就是裝飾器,它把執行真正業務方法的func包裹在函數裡面,看起來像bar被use_logging裝飾了。在這個例子中,函數進入和退出時 ,被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。
@符號是裝飾器的語法糖,在定義函數的時候使用,避免再一次賦值操作
def use_logging(func):
def wrapper(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper
@use_logging
def foo():
print("i am foo")
@use_logging
def bar():
print("i am bar")
bar()
如上所示,這樣我們就可以省去bar = use_logging(bar)這一句了,直接調用bar()即可得到想要的結果。如果我們有其他的類似函數,我們可以繼續調用裝飾器來修飾函數,而不用重復修改函數或者增加新的封裝。這樣,我們就提高了程序的可重復利用性,並增加了程序的可讀性。
裝飾器在Python使用如此方便都要歸因於Python的函數能像普通的對象一樣能作為參數傳遞給其他函數,可以被賦值給其他變數,可以作為返回值,可以被定義在另外一個函數內。
帶參數的裝飾器
裝飾器還有更大的靈活性,例如帶參數的裝飾器:在上面的裝飾器調用中,比如@use_logging,該裝飾器唯一的參數就是執行業務的函數。裝飾器的語法允許我們在調用時,提供其它參數,比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name='foo'):
print("i am %s" % name)
foo()
上面的use_logging是允許帶參數的裝飾器。它實際上是對原有裝飾器的一個函數封裝,並返回一個裝飾器。我們可以將它理解為一個含有參數的閉包。當我 們使用@use_logging(level="warn")調用的時候,Python能夠發現這一層的封裝,並把參數傳遞到裝飾器的環境中。
類裝飾器
再來看看類裝飾器,相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。使用類裝飾器還可以依靠類內部的\_\_call\_\_方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
bar()
functools.wraps
使用裝飾器極大地復用了代碼,但是他有一個缺點就是原函數的元信息不見了,比如函數的docstring、__name__、參數列表,先看例子:
裝飾器
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
函數
@logged
def f(x):
"""does some math"""
return x + x * x
該函數完成等價於:
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
不難發現,函數f被with_logging取代了,當然它的docstring,__name__就是變成了with_logging函數的信息了。
print f.__name__ # prints 'with_logging'
print f.__doc__ # prints None
這個問題就比較嚴重的,好在我們有functools.wraps,wraps本身也是一個裝飾器,它能把原函數的元信息拷貝到裝飾器函數中,這使得裝飾器函數也有和原函數一樣的元信息了。
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print f.__name__ # prints 'f'
print f.__doc__ # prints 'does some math'
內置裝飾器
@staticmathod、@classmethod、@property
裝飾器的順序
@a
@b
@c
def f ():
等效於
f = a(b(c(f)))