一、函數及變量的作用
在python程序中,函數都會創建一個新的作用域,又稱為命名空間,當函數遇到變量時,Python就會到該函數的命名空間來尋找變量,因為Python一切都是對象,而在命名空間中,都是以字典形式存在着,這些變量名,函數名都是索引,而值就是,對應的變量值和函數內存地址。在python中可以用globals()查看全局變量,locals()局部變量。
>>> global_v = '全局變量' >>> def func(): ... local_v = '局部變量' ... print(locals()) #調用locals()輸出局部變量local_v >>> func() {'local_v': '局部變量'} #命名空間中都是以字典形式保存 >>> print(globals()) {.........,'global_v': '全局變量', 'func': <function foo at 0x00000092446C7F28>} #可以看到除了變量,函數名也作為索引,映射函數內存地址,是主程序命名空間的內容
可以看到內置函數globals()返回了一個所有python能識別的變量的字典,而func 擁有自己的命名空間,里面包含了一個{'local_v': '局部變量'}元素
二、變量的作用規則
- 在python中的變量作用域規則是:
1.變量的創建,變量的創建總是會在函數命名空間創建一個新的變量。
2.變量的訪問,是先在函數內部,訪問局部變量所在的函數命名空間,當找不到后再到外層,再到整個外層作用域去尋找該變量。 LEGB法則:索變量名的優先級:局部作用域 > 嵌套作用域 > 全局作用域 > 內置作用域
當在函數中使用未確定的變量名時,Python會按照優先級依次搜索4個作用域,以此來確定該變量名的意義。首先搜索局部作用域(L),之后是上一層嵌套結構中def或lambda函數的嵌套作用域(E),之后是全局作用域(G),最后是內置作用域(B)。按這個查找原則,在第一處找到的地方停止。找不到就報錯。NameError: name 'xxx' is not defined
3.變量的修改,當函數嘗試修改外層變量時,這是不行的,函數只能修改自己命名空間的變量。
如果你非改不可,那只能在變量前面聲明global 這樣就可以改了
>>> name = 'lina' >>> age = 22 >>> list_1 = [1, 2, 3] >>> def fun(): ... name = 'alex' #1 嘗試修改,重賦值alex 給name ... print(name) ... print(age) #2 嘗試查找函數命名空間中不存在的變量age, 沒找到就去外層作用域找到 ... list_1.append('local') # 4 此處修改list_1 ... list_1.pop(0) ... print(list_1) >>> fun() 'alex' 22 [2, 3, 'local'] >>> print(name) #3 查看全局變量name 是否被函數修改成功,顯然沒有 'lina' >>> print(list_1) [2, 3, 'local'] #4 此處修改成功
通過上一個例子,我們可以從#1處看到,嘗試給name賦值,在函數中成功了。
可是在#3處發現並沒有改變name的值,這是因為函數已經開辟內存復制了一份name的值到自己的命名空間中,創建一個同名的新變量name,當fun()運行結束后該內存釋放,而在#3處python尋找變量時直接在自己的作用域中找到name = 'lina'。
#2處在自身的內存空間沒有找到age變量,就去外層找到age= 22輸出。
而在#1處就是所說的函數只能修改自身的變量,#4處對於列表、字典這種,可變對象,傳過來的是內存地址,函數是復制了內存地址,然后直接去內存地址修改了,不能同變量混為一談
三、函數的形參和實參
對於Python來說參數的傳遞是引用傳遞(不是值傳遞),形參名在函數中為局部變量。
對於不可變類型:str、number、tuple命名空間中的復制值改變不會影響外層空間的值。
但是對於可變類型如:list、dict 在函數體中的操作,可能就會改變他的值。
>>> def fun(parameter): #形式參數 ... parameter = parameter*2 ... print(parameter) >>> fun(2) #實際參數 4
四、內嵌函數
python中的內嵌函數,即在函數內部聲明函數,它所有的周期和生命力仍然適用。
>>> def out_fun(): ... a = '外層變量' ... def inner(): ... print(a) #1 ... inner() #2 >>> out_fun() 外層變量
- 處inner搜索自身命名空間,沒找到變量a然后在外層的out_fun的局部變量中尋找到,inner作為內嵌函數擁有訪問外層作用域的權限(有讀和修改的權限-指復制到自身命名空間后修改)當函數調用結束就釋放了。
- 處函數out_fun在自身命名空間中找到變量名'inner',拿到內存地址然后執行函數
五、Python中的閉包
我們現在把內嵌函數作為out_fun的返回值,當out_fun()被執行時,就會定義inner函數,然后返回給fun變量
>>> def out_fun(): ... a = 'out變量' ... def inner(): ... print(a) #1 ... return inner >>> fun = out_fun() >>> fun.__closure__ (<cell at 0x000000A3B57F0B28: str object at 0x000000A3B5AC3088>,)
現在來理解下這個函數,如果按照變量的作用域規則,在#1處inner首先會在自己的命名空間中去尋找變量a,沒找到然后再去外層out_fun尋找。
所以當我們執行由out_fun()返回的fun時,按照道理這個程序是會報錯的。因為當out_fun()執行完畢后就會釋放內存,a變量就不存在了,所以當你執行fun時,inner無法找到a變量就會報錯。我們試試看結果如何:
>>> def out_fun(): ... a = 'out變量' ... def inner(): ... print(a) ... return inner >>> fun = out_fun() >>> fun() out變量
程序並沒有報錯,這並不矛盾,因為python支持一個名為閉包的特性,從fun.__closure__屬性我們看見了,cell at 0x000000A3B57F0B28: str object at 0x000000A3B5AC3088,
即在不是全局的定義中,定義函數inner(即嵌套函數)時,會記錄下外層函數的命名空間,這個對象就保存在.__closure__屬性中,去這兒就是找到外層函數的命名空間。
六、裝飾器
裝飾器的核心原理就是上面我們理解到的了。裝飾器是一個以函數作為參數並返回一個替換函數的可執行函數。
>>> def out_fun(fun): #1接受函數作為參數 ... def inner(a, b= 0, *args): ... print('裝飾器先運行0.0') ... result = fun(a) + b #2運行傳過來的被裝飾函數 ... print('裝飾后結果為:',result) ... return result ... return inner >>> def foo(x): #3定義foo函數 ... print('---------------\n這是被裝飾函數') ... result = 2*x ... print('被裝飾函數執行結果為:{}\n--------------'.format(result)) ... return 2*x >>> decorate_foo = out_fun(foo) #4將foo函數作為jout_fun參數執行out_fun >>> foo =decorate_foo #把裝飾過的foo函數decorate_foo 重賦值給foo,再調用foo() >>> foo() 裝飾器先運行0.0 --------------- 這是被裝飾函數 被裝飾執行結果為:4 --------------- 裝飾后結果為: 2
現在來理解下這段程序,#1處定義了一個函數,他只接受函數作為參數,#2出運行傳過來的被裝飾函數,#3定義了一個函數,#4處將#3定義的foo作為參數傳給out_fun(foo)得到被裝飾后decorate_foo,然后再將裝飾后的函數重新賦值給foo,然后當你再次運行foo函數的時候,永遠都是得到被裝飾后的結果了。
講到這兒就說個實際應用列子吧!
如汽車在公路上行駛,按照某地交通規則,在國道上限速最高80邁,無下限,高速公路上最低60邁最高120邁。
我們原始程序,通過測速傳感器傳來的參數計算出汽車當前速度,並返回該速度。
>>> status = 1 >>> def car_speed(angular_speed, radius = 0.35) #根據傳來的角速度參數,以及半徑計算出當前速度 ... current_speed = angular_speed*radius*3.6 ... return current_speed >>> >>> def slowdown(): ... pass #假設調用此函數是調用剎車、減速系統,會減慢汽車速度 >>> >>> def decorate_fun(fun): ... def inner(*args, **kwargs): ... current_speed = fun(args[0]) if len(args) = 1 else fun(args[0], radius = args[1]) ... if current_speed >110: ... sys.stdout.write('您已超速!') ... sys.stdout.flush() ... elif current_speed > 160: ... sys.stdout.write('超速50%系統已限速,請注意安全') ... sys.stdout.flush() ... slowdown() ... elif current_speed < 60: ... sys.stdout.write('該路段限速60,請注意') ... sys.stdout.flush() ... else: pass ... return current_speed ... return inner >>> >>> decorator_car_speed = decorate_fun(car_speed) >>> decorato_car_speed(120) 您已超速!
這段程序,當汽車在國道等非限速區域是,直接調用car_speed()函數就可以得到速度,而當行駛上高速公路后,就存在邊界值問題,我們可以使用裝飾后的decorate_car_speed()函數來處理。
七、裝飾器符號@ 的應用
通過前面已經了解了裝飾器原理了,這兒就簡單說下@ 的應用。@ 只是python的一種語法糖而已,讓程序看起更美觀,簡潔
>>> def decorator_foo(fun): ... def inner(*args, **kwargs): ... fun(*args, **kwargs) ... pass ... return inner >>> >>> @decorator_foo #1 >>> def foo(*args, **kwargs): #2 ... pass >>>
在#1處@decorator_foo 使用@符號+裝飾器函數,在被裝飾函數的上方,記住一定要正上方挨着不能空行,就等於前面所學的decorator = decorator_foo(foo) + foo = decorator() 這樣以后你調用foo就是調用的被裝飾后的foo了
八、講一個厲害的裝飾器應用
-
情形和需求是這樣的,比如我在django view 下做用戶驗證(不用session),有home函數處理普通用戶請求,index處理管理員請求,bbs返回論壇請求,member處理會員請求。
-
當然我們如果在每一個函數內都做一次驗證,那代碼重復就太多了,所以選擇用裝飾器,不失為一個好方法。可是現在們要求,根據不同的函數,home、bbs、member都在本地數據庫驗證,而index做ldap驗證,意思就是我們要在一個裝飾器里面,根據不同的函數做不同的驗證。
一般的驗證:
def _authentication(r): print('假設使用這個函數做本地用戶認證,過了返回True,錯誤返回False') return #返回驗證結果 def auth(fun): #裝飾器函數 def wrapper(request, *args, **kwargs): if _authentication(request): #調用驗證函數 result = fun(request) return result else: return '用戶名或密碼錯了,重新登錄吧!' return wrapper @auth def index(request): pass @auth def home(request): pass @auth def bbs(request): pass @auth def member(request): pass
全部代碼我就不寫了,太多復雜了,就用偽代碼,邏輯描述來代替了。
可以看出來,我們這個函數可以實現用戶驗證功能,不管你使用cookie也好,去本地數據庫取數據也罷。但是我們上面說的需求,把index來的請求分離出來,做ldap驗證,顯然這樣的裝飾器是沒法做到的。無法識別誰來的請求。
@裝飾器還提供了一功能,能解決這個問題,往下看:
def _authentication(r): print('假設使用這個函數做本地用戶認證,過了返回True,錯誤返回False') return #返回驗證結果 def _ldap(r): print('ldap驗證') return #返回ldap驗證結果 def auth(souce_type): #這兒的souce_type參數就是@auth(v)運行時傳過來的參數 def outer(fun): def wrapper(request, *args, **kwargs): if souce_type == 'local': #* 1 如果請求來源標記是'local'就本地驗證 if _authentication(request): result = fun(request) return result else: return '用戶名或密碼錯了,重新登錄吧!' elif souce_type == 'ldap': #* 1 如果請求來源標記是'ldap'就ldap驗證 if _ldap(request): return fun(request) else: return '用戶名或密碼錯了,重新登錄吧!' return wrapper return outer @auth(souce_type = 'ldap') #3 裝飾 def index(request): pass @auth(souce_type = 'local') #4 def home(request): pass
- 注意#3,#4處,我們把auth('parameter')加參數運行了一次,而裝飾器函數auth里面進行了三層嵌套,auth---->outer----->wrapper,你可以這樣理解,原來的@auth @符號會把后面的內容auth運行一次直接就返回了wrapper, 現在,我們自己把auth('parameter')加參數運行了一次得到outer,@auth(parameter)就等同於 @outer,@符號把后面的outer運行一次后再得到wrapper並賦給被修飾函數,而函數souce_type來源也被我們帶進了裝飾器。
人生還有意義。那一定是還在找存在的理由 轉自:https://www.cnblogs.com/shiqi17/p/9331002.html
----------------------------------------------------------------------------------------------------------------------------------------------------------------
#不帶參數的裝飾器 @dec1 @dec2 def func(): ... #這個函數聲明等價於 func = dec1(dec2(func)) #帶參數的裝飾器 @dec(some_args) def func(): ... #這個函數聲明等價於 func = dec(some_args)(func)
不帶參數的裝飾器需要注意的一些細節
1. 關於裝飾器函數(decorator)本身
因此一個裝飾器一般對應兩個函數,一個是decorator函數,用來進行一些初始化操作處理,一個是decorated_func用來實現對被裝飾的函數func的額外處理。並且為了保持對func的引用,decorated_func一般作為decorator的內部函數
def decorator(func): def decorator_func() func() return decorated_func
decorator函數只在函數聲明的時候被調用一次
裝飾器實際上是語法糖,在聲明函數之后就會被調用,產生decorated_func,並把func符號的引用替換為decorated_func。之后每次調用func函數,實際調用的是decorated_func(這個很重要,裝飾之后,其實每次調用的是decorated_func)。
>> def decorator(func): ... def decorated_func(): ... func(1) ... return decorated_func ... #聲明時就被調用 >>> @decorator ... def func(x): ... print x ... decorator being called #使用func()函數實際上使用的是decorated_func函數 >>> func() 1 >>> func.__name__ 'decorated_func'
如果要保證返回的decorated_func的函數名與func的函數名相同,應當在decorator函數返回decorated_func之前,加入decorated_func.name = func.name, 另外functools模塊提供了wraps裝飾器,可以完成這一動作。
#@wraps(func)的操作相當於 #在return decorated_func之前,執行 #decorated_func.__name__ = func.__name__ #func作為裝飾器參數傳入, #decorated_func則作為wraps返回的函數的參數傳入 >>> def decorator(func): ... @wraps(func) ... def decorated_func(): ... func(1) ... return decorated_func ... #聲明時就被調用 >>> @decorator ... def func(x): ... print x ... decorator being called #使用func()函數實際上使用的是decorated_func函數 >>> func() 1 >>> func.__name__ 'func'
decorator函數局部變量的妙用
因為closure的特性(詳見(1)部分閉包部分的詳解),decorator聲明的變量會被decorated_func.func_closure引用,所以調用了decorator方法結束之后,decorator方法的局部變量也不會被回收,因此可以用decorator方法的局部變量作為計數器,緩存等等。值得注意的是,如果要改變變量的值,該變量一定要是可變對象,因此就算是計數器,也應當用列表來實現。並且聲明一次函數調用一次decorator函數,所以不同函數的計數器之間互不沖突,例如:
#!/usr/bin/env python #filename decorator.py def decorator(func): #注意這里使用可變對象 a = [0] def decorated_func(*args,**keyargs): func(*args, **keyargs) #因為閉包是淺拷貝,如果是不可變對象,每次調用完成后符號都會被清空,導致錯誤 a[0] += 1 print "%s have bing called %d times" % (func.__name__, a[0]) return decorated_func @decorator def func(x): print x @decorator def theOtherFunc(x): print x
下面我們開始寫代碼:
#coding=UTF-8 #!/usr/bin/env python #filename decorator.py import time from functools import wraps def decorator(func): "cache for function result, which is immutable with fixed arguments" print "initial cache for %s" % func.__name__ cache = {} @wraps(func) def decorated_func(*args,**kwargs): # 函數的名稱作為key key = func.__name__ result = None #判斷是否存在緩存 if key in cache.keys(): (result, updateTime) = cache[key] #過期時間固定為10秒 if time.time() -updateTime < 10: print "limit call 10s", key result = updateTime else : print "cache expired !!! can call " result = None else: print "no cache for ", key #如果過期,或則沒有緩存調用方法 if result is None: result = func(*args, **kwargs) cache[key] = (result, time.time()) return result return decorated_func @decorator def func(x): print 'call func'
隨便測試了下,基本沒有問題。
>>> from decorator import func initial cache for func >>> func(1) no cache for func call func >>> func(1) limit call 10s func 1488082913.239092 >>> func(1) cache expired !!! can call call func >>> func(1) limit call 10s func 1488082923.298204 >>> func(1) cache expired !!! can call call func >>> func(1) limit call 10s func 1488082935.165979 >>> func(1) limit call 10s func 1488082935.165979
博客地址:http://www.cnblogs.com/elyw/p/python_function_decorator_and_lambda.html
