Python裝飾器專題-限制函數調用次數(10s調用一次)

1、函數及變量的做用html

在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': '局部變量'}元素數據庫

2、變量的做用規則django

 

    • 在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處對於列表、字典這種,可變對象,傳過來的是內存地址,函數是複製了內存地址,而後直接去內存地址修改了,不能同變量混爲一談緩存

3、函數的形參和實參安全

對於Python來講參數的傳遞是引用傳遞(不是值傳遞),形參名在函數中爲局部變量。
對於不可變類型:str、number、tuple命名空間中的複製值改變不會影響外層空間的值。
可是對於可變類型如:list、dict 在函數體中的操做,可能就會改變他的值。
cookie

 

>>> def fun(parameter):     #形式參數
...     parameter = parameter*2
...     print(parameter)
>>> fun(2)     #實際參數
4

 

4、內嵌函數session

python中的內嵌函數,即在函數內部聲明函數,它全部的週期和生命力仍然適用。閉包

 

>>> def out_fun():
...     a = '外層變量'
...     def inner():
...         print(a)   #1
...     inner()     #2
>>> out_fun()
外層變量

 

  • 處inner搜索自身命名空間,沒找到變量a而後在外層的out_fun的局部變量中尋找到,inner做爲內嵌函數擁有訪問外層做用域的權限(有讀和修改的權限-指複製到自身命名空間後修改)當函數調用結束就釋放了。
  • 處函數out_fun在自身命名空間中找到變量名'inner',拿到內存地址而後執行函數

5、Python中的閉包app

咱們如今把內嵌函數做爲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__屬性中,去這兒就是找到外層函數的命名空間。

6、裝飾器

 

裝飾器的核心原理就是上面咱們理解到的了。裝飾器是一個以函數做爲參數並返回一個替換函數的可執行函數。

 

>>> 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()函數來處理。

 7、裝飾器符號@ 的應用


 
經過前面已經瞭解了裝飾器原理了,這兒就簡單說下@ 的應用。@ 只是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了

8、講一個厲害的裝飾器應用

 

  • 情形和需求是這樣的,好比我在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                             

相關文章
相關標籤/搜索