在python程序中,函數都會建立一個新的做用域,又稱爲命名空間,當函數遇到變量時,Python就會到該函數的命名空間來尋找變量,由於Python一切都是對象,而在命名空間中,都是以字典形式存在着,這些變量名,函數名都是索引,而值就是,對應的變量值和函數內存地址。在python中能夠用globals()查看全局變量,locals()局部變量。python
>>> 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': '局部變量'}元素數據庫
>>> 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處對於列表、字典這種,可變對象,傳過來的是內存地址,函數是複製了內存地址,而後直接去內存地址修改了,不能同變量混爲一談django
>>> def fun(parameter): #形式參數 ... parameter = parameter*2 ... print(parameter) >>> fun(2) #實際參數 4
>>> def out_fun(): ... a = '外層變量' ... def inner(): ... print(a) #1 ... inner() #2 >>> out_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__屬性中,去這兒就是找到外層函數的命名空間。cookie
>>> 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邁。
咱們原始程序,經過測速傳感器傳來的參數計算出汽車當前速度,並返回該速度。session
>>> 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()函數來處理。閉包
>>> 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了app
情形和需求是這樣的,好比我在django view 下作用戶驗證(不用session),有home函數處理普通用戶請求,index處理管理員請求,bbs返回論壇請求,member處理會員請求。函數
固然咱們若是在每個函數內都作一次驗證,那代碼重複就太多了,因此選擇用裝飾器,不失爲一個好方法。但是如今們要求,根據不一樣的函數,home、bbs、member都在本地數據庫驗證,而index作ldap驗證,意思就是咱們要在一個裝飾器裏面,根據不一樣的函數作不一樣的驗證。spa
通常的驗證:
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