• python装饰器


    学习装饰器前,我们先了解三个概念:作用域、函数(嵌套)、闭包。

    作用域

    作用域简单说就是一个变量的命名空间。代码中变量被赋值的位置,就决定了哪些范围的对象可以访问这个变量,这个范围就是命名空间。python赋值时生成了变量名,当然作用域也包括在内。

    在函数外,一段代码最始开所赋值的变量,它可以被多个函数引用,这就是全局变量

    在函数内定义的变量名,只能被函数内部引用,不能在函数外引用这个变量名,这个变量的作用域就是局部的,也叫它为局部变量 ;

    如果函数内的变量名与函数外的变量名相同,也不会发生冲突。

    x = 66
    
    def func():
        x = 88
    

    x = 66这个赋值语句所创建的变量X,作用域为全局变量;

    x = 88这个赋值语句所创建的变量X,它的作用域则为局部变量,只能在函数func()内使用。

    尽管这两个变量名是相同的,但它的作用域为它们做了区分。作用域在某种程度上也可以起到防止程序中变量名冲突的作用。

    在 Python 函数中会创建一个新的作用域。也就是说,当在函数体中遇到变量时,Python 会首先在该函数的命名空间中寻找变量名。Python 有几个函数用来查看命名空间。下面来写一个简单函数来看看局部变量和全局变量的区别。

    >>> a_string = "This is a global variable"
    >>> def foo():
    ...     print locals()
    ...
    >>> print globals()
    {'__builtins__': <module '__builtin__' (built-in)>, 'a_string': 'This is a global variable', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x00000000022DDAC8>, '__doc__': None}
    >>> foo()
    {}
    

    内建函数 globals 返回一个包含所有 Python 能识别变量的字典。

    调用了 foo 函数,在函数中打印局部变量的内容。从中可以看到,函数 foo 有自己单独的、此时为空的命名空间。

    总结:

    1、变量的作用域由代码被赋值的位置所决定

    2、变量可以在3个不同地方,对应3种不同作用域:

    • 一个变量在函数内赋值,它的作用范围被定位在函数之内;
    • 变量在函数外赋值,它作用域就是当前整个文件的全局变量;
    • 当变量是在一个嵌套的函数中赋值时,对于这个嵌套的函数来说,这个变量是非本地的。

    函数(嵌套)

    Python中一个与众不同的语法就是可以嵌套函数,所谓嵌套,并不像其他语言中的在一个函数中调用另一个函数,而是在定义一个函数的时候,函数体里还能定义另一个函数。

    内函数可以访问外函数的作用域,但不能重新赋值;外部函数不能访问内部函数的作用域。

    例:

    def foo():           # 定义函数foo(),
        m = 3            # 定义变量m=3;
        def bar():       # 在foo内定义函数bar()
            n = 4        # 定义局部变量n=4
            print m + n  # m 相当于函数bar()的全局变量
        bar()            # foo()函数内调用函数bar()
    

    闭包

    定义:如果一个内部函数里,对在外部作用域(单不是在全局作用域)的变量进行引用,那么内部函数就被认为闭包(closure)。

    def  outer():
        x = 1
        def inner():
            print(x)  # 调用外部变量
        return inner  # 函数名作为返回值
    	
    # 执行inner函数的两种方式
    outer()()
    
    func = outer()
    func()  # 相当于在外部执行inner函数
    

    inner就是相对于outer的闭包函数

    用途:

    1、当闭包执行完后,仍然能够保持住当前的运行环境。

    # 比如说,如果你希望函数的每次执行结果,都是基于这个函数上次的运行结果。我以一个类似棋盘游戏的例子来说明。
    # 假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),
    # 该函数控制棋子的运动。棋子运动的新的坐标除了依赖于方向和步长以外,当然还要根据原来所处的坐标点,用闭包就可以保持住这个棋子原来所处的坐标。
    origin = [0, 0]    # 坐标系统原点
    legal_x = [0, 50]  # x轴方向的合法坐标
    legal_y = [0, 50]  # y轴方向的合法坐标
    
    
    def create(pos=origin):
    
        def player(direction, step):
            # 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等
            # 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,就不详细写了。
            new_x = pos[0] + direction[0]*step
            new_y = pos[1] + direction[1]*step
            pos[0] = new_x
            pos[1] = new_y
            # 注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过
            return pos
        return player
    
    player = create()          # 创建棋子player,起点为原点
    print player([1, 0], 10)   # 向x轴正方向移动10步
    print player([0, 1], 20)   # 向y轴正方向移动20步
    print player([-1, 0], 10)  # 向x轴负方向移动10步
    

    输出为

    2、闭包可以根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。比如有时我们需要对某些文件的特殊行进行分析,先要提取出这些特殊行。

    def make_filter(keep): 
        def the_filter(file_name):  
            file = open(file_name)  
            lines = file.readlines()  
            file.close()  
            filter_doc = [i for i in lines if keep in i]  
            return filter_doc  
        return the_filter  
    

    如果我们需要取得文件"result.txt"中含有"pass"关键字的行,则可以这样使用例子程序

    filter = make_filter("pass")  
    filter_result = filter("result.txt")  
    

    参考博客:http://www.cnblogs.com/JohnABC/p/4076855.html

     

    装饰器

    概念:装饰器本质是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象。

    它经常用于有切面需求的场景,比如:插入日志、性能检测、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

    ==>装饰器的作用就是为已经存在的对象添加额外的功能。

    普通装饰器

    import time
    
    def show_time(func):  # 这里外加一层函数目的就是给所需功能函数提供一变量,该形参变量就是对应需要添加功能的函数名
        def get_time():
            start_time = time.time()
            func()
            end_time = time.time()
            print("Spend %s"%(end_time-start_time))
        return get_time
    
    def index():
        print("hello Wolrd")
        time.sleep(3)
    
    index = show_time(index)
    index()

    其中的show_time就是装饰器,将想要调用的函数包裹在里面,通过变量的形式来进行传递,func()表示调用的原函数。

    Python为了表示更加方便,为我们提供了语法糖,将上述index()=show_time(index) =封装=> @show_time

    import time
    
    def show_time(func):
        def get_time():
            start_time = time.time()
            func()
            end_time = time.time()
            print("Spend %s"%(end_time-start_time))
        return get_time
    
    @show_time
    def index():
        print("hello Wolrd")
        time.sleep(3)
    index()
    

    提示:

    1. 这里需要注意的问题: index=show_time(index)其实是把get_time引用的对象引用给了index,而get_time里的变量func之所以可以用,就是因为get_time是一个闭包函数。
    2. Python装饰器如此方便归功于Python函数能够像普通对象一样作为参数传递给其他函数,函数名作为变量、返回值、定义在另外一个函数内。

    含参装饰器

    import  time
    
    def show_time(func):
        def get_time(x, y):
            start_time = time.time()
            func(x, y)
            end_time = time.time()
            print("Spend %s"%(end_time-start_time))
        return get_time
    
    @show_time
    def index(x, y):  # 这里添加参数
        print("hello Wolrd")
        time.sleep(3)
        print(x + y)
    
    index(1,3)
    

    分析:

    1. 先来解封@show_time ==>index = show_time(index)
    2. 执行函数index(x, y),即执行函数get_time(x, y)
    3. 与此同时show_time(func)传递的是index(x, y)函数本身,因此对应的func(x, y)同样要带有对应的参数

    动态参数装饰器

    import time
    
    def show_time(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            func(*args, **kwargs)
            end_time = time.time()
            print('spend %s' % (end_time-start_time))
        return wrapper
    
    @show_time  # add=show_time(add)
    def add(*args, **kwargs):
        time.sleep(1)
        sum = 0
        for i in args:
            sum += i
        print(sum)
    
    add(1, 2, 3, 4)
    

    带参数的装饰器

    import time
    
    def time_logger(flag=0):  # 新添加的外层函数
        def show_time(func):  # 原外层函数
            def wrapper(*args, **kwargs):  # 功能函数
                start_time = time.time()
                func(*args, **kwargs)  # 调用函数
                end_time = time.time()
                print('spend %s' % (end_time - start_time))
                if flag:
                    print('将这个操作的时间记录到日志中')
            return wrapper
        return show_time
    
    @time_logger(1)
    def add(*args, **kwargs):
        time.sleep(1)
        sum = 0
        for i in args:
            sum += i
        print(sum)
    
    add(1, 2, 3)
    

    根据上面代码,@time_logger(1) 和@time_logger(0)结果不同,@time_logger(1)的结果比@time_logger(0)结果多输入一行内容“将这个操作的时间记录到日志中”。

    分析:

    1. 调用time_logger()函数返回的闭包函数的函数名:show_time,此时结果为:@show_time,time_logger()中的参数就是为内部函数提供可用变量
    2. 下层解封就是和前面的过程一样

    多层装饰器

    def makebold(fn):
        def wrapper():
            return "<b>" + fn() + "</b>"
        return wrapper
    
    def makeitalic(fn):
        def wrapper():
            return "<i>" + fn() + "</i>"
        return wrapper
    
    @makebold    # 装饰器1
    @makeitalic  # 装饰器2
    def hello():
        return "hello Siffre"
    
    print hello()
    

    执行结果

    分析:

    • 从最里面的装饰器依次向外执行
    • 前面的装饰器函数名都会作为下一个装饰器函数的参数
    • 结果是多个装饰器的组合

  • 相关阅读:
    剑指offer——72圆圈中最后剩下的数字
    剑指offer——71扑克牌中的顺子
    剑指offer——70n个骰子的点数
    剑指offer——69队列的最大值
    剑指offer——68队列的最大值
    剑指offer——67左旋转字符串
    剑指offer——66翻转字符串
    剑指offer——65和为S的连续正数序列
    「日常训练」COMMON 约数研究(HYSBZ-1968)
    「暑期训练」「基础DP」免费馅饼(HDU-1176)
  • 原文地址:https://www.cnblogs.com/shhnwangjian/p/6305750.html
Copyright © 2020-2023  润新知