天天看點

Python裝飾器

Python裝飾器,遞歸,子產品

先看一個Python執行過程

>>> def foo():            #定義函數

...    print 'foo'           #執行函數列印字元串foo 

...

>>> foo

<function foo at 0x7fd3a06f77d0>  #表示foo是一個函數

>>> foo()              #執行函數輸出  

foo

重新定義foo

>>>foo = lambda x:x + 1

<function <lambda> at 0x7fd3a06f75f0>

>>> foo(1)

2                   #foo函數被重新定義了,執行的是+1的函數

在看一個列子

>>> def f1(arg):

...    arg()

>>> def func():

...    print '12'

>>> f1(func)

12

執行步驟為

1,定義函數f1放入記憶體改函數傳遞一個參數arg 執行arg()代表執行一個函數

2,定義函數func 該函數執行列印數字12

3,執行函數f1調用func為參數,然後在f1内部加()執行func函數,相當于func(),結果就是列印出12

根據這個原理提出以下需求

假設公司有一個基礎平台,基礎平台提供底層的功能函數(比如調用資料庫,監控API等)這裡為了簡化就定義為輸出一個字元串

(也隻定義了一個函數,實際工作肯定有多個函數)

vim day5-1.py

#!/usr/bin/python
# -*- coding:utf-8 -*-
def f1():
    print 'f1'
f1()
      

業務部門調用基礎平台提供的函數輸出為f1

假如業務有需求要在調用前加一個驗證(為了簡化也用輸出一個字元串代替),有幾種方案

1,業務部門調用的時候加驗證

  修改代碼

#!/usr/bin/python
# -*- coding:utf-8 -*-
def f1():
    print 'f1'

print 'before'
f1()
      

每個部門在調用函數的時候自己加驗證,很明顯不行

2,修改底層函數

#!/usr/bin/python
# -*- coding:utf-8 -*-
def f1():
    print 'before'
    print 'f1'

f1()
      

業務部門不需要修改代碼,但是底層函數有很多個,一個個修改不現實

3,重新定義一個函數,在底層函數一個個插入

#!/usr/bin/python
# -*- coding:utf-8 -*-
def before():
    print 'before'
def f1():
    before()
    print 'f1'
f1()
      

隻需要在每個底層函數調用一次新的函數即可,好像可以了

但是

寫代碼要遵循開發封閉原則,雖然在這個原則是用的面向對象開發,但是也适用于函數式程式設計,簡單來說,它規定已經實作的功能代碼不允許被修改,但可以被擴充,即:

  • 封閉:已實作的功能代碼塊
  • 開放:對擴充開發

4,終極解決方案,使用裝飾器

#!/usr/bin/python
# -*- coding:utf-8 -*-
def auth(func):         #把函數auth寫入記憶體
    def inner():        #函數内部函數
        print 'before'  #實作類似于驗證的功能
        func()          #執行func()其實這裡就是執行了f1()
    return inner        #傳回内部函數inner
@auth                   #裝飾器方法,調用auth函數

def f1():               #定義底層函數這裡底層函數遵守封閉性原則不做任何改動
    print 'f1'

f1()                    #模拟各個部門調用底層函數,也未做改動
      

定義裝飾器的執行過程如下

  一,把auth函數定義到記憶體

  二,定義底層函數f1

  三,使用@加函數名調用裝飾器把函數f1作為參數傳遞給函數auth

  四,裝飾器内部先執行print 'before' 在執行f1() 傳回函數inner相當于把函數f1作為參數傳遞給函數auth然後把傳回值在指派給f1函數

  五,調用函數f1此時的輸出為執行完驗證以後的輸出

完美實作了功能,底層定義函數代碼及各個部門調用函數代碼沒有變化

裝飾器其實就是函數加Python的文法糖

PS:裝飾器傳回的是一個函數體,不是函數執行後的結果,需要執行才能出結果

如下

#!/usr/bin/python
# -*- coding:utf-8 -*-
def auth(func):       
    def inner():      
        print 'before'
        func()        
    return inner      

def f1():             
    print 'f1'


f1=auth(f1)           
f1()
      

把f1作為函數auth的參數然後在把傳回的函數值指派給f1,最後在執行一次f1函數出結果(比較low)

以上例子調用的函數f1是沒有參數的

 假如有函數是有參數的呢

重新定義一個帶參數的裝飾器即可

#!/usr/bin/python
# -*- coding:utf-8 -*-
def auth(func):       
    def inner():      
        print 'before'
        func()        
    return inner      

def auth_arg(func):
    def inner(arg):
        print 'before'
        func(arg)
    return inner

@auth                 

def f1():             
    print 'f1'

#f1()                 
@auth_arg
def f5(arg):
    print 'f5',arg
      

如果有多個參數使用上面的方式需要定義多個函數

Python提供一種通用的裝飾器方法無論提供多少個參數均可

#!/usr/bin/python
# -*- coding:utf-8 -*-
def auth(func):               
    def inner(*args,**kwargs):
        print 'before'        
        func(*args,**kwargs)  
    return inner              

@auth                         

def f1():                     
    print 'f1'

#f1()                         
@auth
def f5(arg):
    print 'f5',arg
      

小結

1,裝飾器是一個函數,至少兩層

2,執行auth函數,被裝飾的函數作為參數auth(foo)

   auth函數的傳回值,指派給被裝飾的函數的函數名

3,動态參數,可以裝飾含有n個參數的函數

4,函數的傳回值

5,多裝飾器

以上調用的函數的沒有傳回值的假如調用的基礎函數有傳回值呢

vim basic.py

#!/usr/bin/python
# -*- coding:utf-8 -*-
def login():
    name = 'alex'
    if name == 'alex':
        return True
    else:
        return False
def auth(func):                     
    def inner(*args,**kwargs):      
        is_login = login()
        if not is_login:
            return '非法使用者'
        print 'before'              
        temp = func(*args,**kwargs)
        return temp                 
    return inner                    

@auth                               

def f1():                           
    print 'f1'

#f1()                               
@auth
def f5(arg):
    print 'f5',arg

@auth
def fetch_server_list(arg):
    server_list = ['c1','c2','c3']
    return server_list
      

在auth裡面傳回了原函數的傳回值,并且模拟了一個驗證的過程

vim day5-3.py

#!/usr/bin/python
# -*- coding:utf-8 -*-
import basic
ret_list = basic.fetch_server_list('test')
print ret_list
      

調用輸出

Python裝飾器

如果使用者名不是alex則會輸出非法使用者的提示

PS:在寫web項目的時候都有使用裝飾器來做驗證的作用.

再次模拟使用以後key來驗證的過程

#!/usr/bin/python
# -*- coding:utf-8 -*-
def login(token):
    local = 'askjdhkjahsdkjahsakjsd'
    if local == token:
        return True
    else:
        return False
def auth(func):
    #fetch_server_list('test',token=key)                
    def inner(*args,**kwargs):
#        key = kwargs["token"]          #因為原函數fetch_server_list隻接受一個參數      
#        del kwargs['token']            #是以把傳遞的字典的一個參數去掉,該參數在驗證的時候已經使用過一次
        key = kwargs.pop('token')       #這句等同于以上兩句
        is_login = login(key)
        if not is_login:
            return '非法使用者'
        print 'before'
        temp = func(*args,**kwargs)
        return temp
    return inner

@auth

def f1():
    print 'f1'

#f1()                                   
@auth
def f5(arg):
    print 'f5',arg

@auth
def fetch_server_list(arg):
    server_list = ['c1','c2','c3']
    return server_list
      
#!/usr/bin/python
# -*- coding:utf-8 -*-
import basic
key = 'askjdhkjahsdkjahsakjsd'
ret_list = basic.fetch_server_list('test',token=key)
print ret_list
      

模拟傳遞一個key進行驗證,輸出結果不變

Python裝飾器

多裝飾器

vim day5-5.py

#!/usr/bin/python
# -*- coding:utf-8 -*-
def w1(func):
    def inner():
        print 'w1,before'
        func()
        print 'w1,after'
    return inner

def w2(func):
    def inner():
        print 'w2,before'
        func()
        print 'w2,after'
    return inner

#@w2
@w1
def foo():
    print 'foo'

foo()
      

單裝飾器和多裝飾器的運作結果如下,一層裝飾器就是套一層盒子最上面的就是最外面的那層盒子

多裝飾器的用途,使用者登陸後的權限不同,一般用不上.

Python裝飾器