zoukankan      html  css  js  c++  java
  • Python装饰器专题-限制函数调用次数(10s调用一次)

    一、函数及变量的作用

    在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('---------------
    这是被装饰函数')
    ...     result = 2*x
    ...     print('被装饰函数执行结果为:{}
    --------------'.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                             

  • 相关阅读:
    多媒体开发(5)&音频特征:声音可以调大一点吗?
    多媒体开发(4):在视频上显示文字或图片--ffmpeg命令
    多媒体开发(3):直播
    多媒体开发(2):录制视频
    多媒体开发(1):播放
    python应用(7):输入与输出
    python应用(6):函数
    python应用(5):变量类型与数据结构
    python应用(4):变量与流程
    PHP接收$_POST表单值为字符串或数组时,对安全转义的处理函数
  • 原文地址:https://www.cnblogs.com/songhuasheng/p/10420729.html
Copyright © 2011-2022 走看看