Python进阶 - 装饰器

作者: ChaoesLuol | 来源:发表于2020-02-22 18:26 被阅读0次

    函数进阶知识

    函数名只是一个指向函数的变量

    在python中,一切皆对象。函数名只是一个指向函数的变量,为了验证这一点,我们可以查看修改和删除函数名对程序的影响:

    def someFunc():  # someFunc只是一个变量名,它指向了我们创建的函数
        print("Now in a function")
    
    
    if __name__ == '__main__':
        anotherFunc = someFunc  # 用另一个变量名指向刚创建的函数
        anotherFunc()  # 输出Now in a function
        # 删除了一个指向函数的变量名,对函数本身并没有影响;
        # 因为还有一个变量anotherFunc指向函数,因此函数本体并不会被回收
        del someFunc
        anotherFunc()  # 还是输出Now in a function
    

    可以看到,在python中函数名和一个普通变量一样,只不过它指向了一组语句。

    嵌套函数和变量作用域

    在函数内是可以定义另一个函数的,这叫做嵌套函数。作用域是程序运行时变量可被访问的范围,定义在函数内的变量是局部变量,局部变量的作用范围只能是函数内部范围内,它不能在函数外引用。根据这个原理,对于嵌套函数来说,在内层的函数可以访问外层函数定义的变量。

    如下例所示:

    def nested_func():
        msg = "This is a message"
    
        # 在函数内部定义一个函数,打印消息
        def print_message():
            # 在嵌套函数内层可以访问外层函数的变量
            print(msg)
    
        # 调用打印消息的函数
        print_message()  # output: This is a message
    
    
    if __name__ == '__main__':
        nested_func()
    

    将函数作为返回值

    函数由于是一个对象,它是可以作为另一个函数的返回值或者作为参数传递给其他函数。

    def nested_func(msg):
        # 在函数内部定义一个函数,打印消息
        def print_message():
            # 在嵌套函数内层可以访问外层函数的变量
            print(msg)
    
        return print_message
    
    
    if __name__ == '__main__':
        msg = "This is a message"
        aFunction = nested_func(msg)  # 将aFunction指向了nested_Func(msg)的返回值,即print_message
        aFunction()  # 等同于调用了print_message()
    

    这里将返回的函数赋值给另一个函数,这样可以实现对内部函数从外部进行传值。

    闭包(Closure)

    什么是闭包

    两个嵌套的函数,内部函数使用外部函数的变量。这套变量+内部函数整体,就叫做闭包。

    闭包避免了使用全局变量,此外,闭包允许将函数与其所操作的某些数据(环境)关连起来。这一点与面向对象编程是非常类似的,在面对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

    为什么需要闭包

    刚才提到过,闭包可以实现对内部函数从外部进行传值。实现这种功能,有两套方案:使用类和使用闭包。使用闭包的方案我们前面已经介绍过,我们下面看看如何用类实现这个功能:

    class someClass(object):
        def __call__(self, msg):
            print(msg)
    
    
    if __name__ == '__main__':
        msg = "This is a message"
        aFunction = someClass()
        aFunction(msg) # 输出 This is a message
    

    这里就实现了和上面用嵌套函数一样的功能,在这里这个aFunction虽然是一个实例对象,但是我们定义了__call__方法之后,它也可以像函数一样被调用。

    这种实现方式的缺陷在于,python3的类默认会继承object(Python2 略有不同,继承了object的叫做新式类,没有继承的叫做经典类),它里面自带了一系列我们用不到的方法,会占用相对较大的空间。

    print(dir(aFunction))
    # 输出结果:
    # ['__call__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
    

    而闭包则更加精简,占用的空间更小。

    装饰器的内部执行原理

    什么是装饰器

    装饰器就是一种闭包,只是这个闭包的函数名等于原先的函数名,从而改变了原先函数的指向。

    def my_decorator(func):
        def print_msg():
            print("new message has been added!")
            func()
    
        return print_msg
    
    
    def plain_print_msg():
        print("entering plain print msg")
    
    
    if __name__ == '__main__':
        # plain_print_msg作为实参被传给my_decorator
        # aFunc指向my_decorator的返回值,也就是print_msg
        aFunc = my_decorator(plain_print_msg)
        # 相当于在指向print_msg,其内部的func = plain_print_msg
        aFunc()
        print("-" * 10)
    
        # 将原先的函数名plain_print_msg直接指向my_decorator的返回值
        # 可以看到和上面部分的代码实现的功能是相同的
        plain_print_msg = my_decorator(plain_print_msg)
        plain_print_msg()
    

    可以看到,执行下面两行代码之后,函数名称并没有变, 但是却指向了一个新的函数,在新的函数内,除了执行原函数之外,还加了一些其他的功能,这就是装饰器的实现原理。

    在实际使用装饰器时,我们并不需要手动修改函数名的指向,而是可以由@运算符来进行代劳,上面的代码可以改写如下:

    def my_decorator(func):
        def print_msg():
            print("new message has been added!")
            func()
    
        return print_msg
    
    
    @my_decorator
    def plain_print_msg():
        print("entering plain print msg")
    
    
    if __name__ == '__main__':
        plain_print_msg()
    

    这里的@my_decorator和我们上面手动实现的代码功能是一样的。

    为什么需要使用装饰器

    那么在开发过程中进行功能扩充时,为什么不直接修改函数源码,而要使用装饰器呢?

    开放封闭原则:已经实现的功能代码不允许修改,但是可以被扩展。也即已经实现的功能代码块封闭,而对扩展开发开放。这是因为在比较大的系统中,可能有很多模块使用同一个函数,如果为了修改功能,直接对函数进行修改,那么其他地方对函数的引用可能会出错。而使用了装饰器之后,可以在装饰器内梳理代码逻辑,这样不该动其他地方的代码也可以正常加上新功能。

    装饰器什么时候开始生效的

    装饰器的生效不会等到函数进行调用,而是在解释器读到装饰符开始,就会对函数进行装饰。为了说明这一点,我们可以看下面这一段代码:

    def my_decorator(func):
        print("entering my decorator!")
    
        def print_msg():
            print("new message has been added!")
            func()
    
        return print_msg
    
    
    @my_decorator
    def plain_print_msg():
        print("entering plain print msg")
    
    
    if __name__ == '__main__':
        pass # 注意这里并没有执行函数调用
    

    输出结果:

    entering my decorator!
    

    可以看到函数并没有被调用,但是装饰器已经被加装在函数之上了。

    对带参数的函数进行装饰

    通过上面对函数的学习,我们已经知道装饰器的工作原理了,因此如果需要对带参数的函数进行装饰,无非就是在闭包的内层函数上加上相应的参数。示例如下:

    import time
    
    
    def time_wrapper(func):
        def timer(cnt):
            start_time = time.time()
            func(cnt)
            end_time = time.time()
            print("time consumed by %s: %s second" % (func.__name__, str(end_time - start_time)))
    
        return timer
    
    
    @time_wrapper
    def count1(cnt):
        lst = []
        for i in range(cnt):
            lst.insert(0, i)
    
    
    @time_wrapper
    def count2(cnt):
        lst = []
        for i in range(cnt):
            lst.append(i)
    
    
    if __name__ == '__main__':
        count1(10000)
        count2(10000)
    

    对有返回值的函数进行装饰

    在了解了装饰器的原理之后,我们就应该知道如果原函数带有返回值的话,我们需要在装饰器的什么地方进行返回了:

    def my_decorator(func):
        print("entering my decorator!")
    
        def print_msg():
            print("new message has been added!")
            return func() # 将函数的返回值作为返回值
    
        return print_msg
    
    
    @my_decorator
    def plain_print_msg():
        print("entering plain print msg")
        return "OK"  # 函数带有返回值,指示打印状态
    
    
    if __name__ == '__main__':
        # 这里给的实际上是print_msg()的返回值,也就是func()
        # 形参func = plain_print_msg,因此返回值和原函数plain_print_msg的返回值是相同的
        ret = plain_print_msg() 
        print(ret)
    

    多个装饰器装饰同一个函数

    当有多个装饰器装饰同一个函数时,最靠近函数的(位于程序最下端的)装饰器最先装饰,然后依次由近及远进行装饰。而调用闭包时,由远及近进行调用。

    def decorator1(func):
        print("entering decorator1")
    
        def add_msg():
            print("using decorator1: add_msg")
            func()
    
        return add_msg
    
    
    def decorator2(func):
        print("entering decorator2")
    
        def add_another_msg():
            print("using decorator2: add_another_msg")
    
        return add_another_msg
    
    
    @decorator1  # 距离函数较远的装饰器后装饰,先调用(包在外层)
    @decorator2  # 距离函数较近的装饰器先装饰,后调用(包在内层)
    def plain_print_msg():
        print("entering plain print msg")
        return "OK"  # 函数带有返回值,指示打印状态
    
    
    if __name__ == '__main__':
        plain_print_msg()
    

    输出的结果:

    entering decorator2
    entering decorator1
    using decorator1: add_msg
    using decorator2: add_another_msg
    

    用类实现装饰器

    明白了装饰器的实现原理之后,就可以知道用类也是可以实现装饰器的。这就是我们之前讲的用类实现闭包功能的@装饰符版本。

    class Test():
        def __init__(self, func):
            self._func = func
    
        def __call__(self, *args, **kwargs):
            print("----调用装饰器----")
            self._func(*args, **kwargs)
    
    
    @Test
    def my_fun(a, b, c):
        print(a, b, c)
    
    
    if __name__ == '__main__':
        my_fun(10, 20, 'main')
    

    输出结果:

    ----调用装饰器----
    10 20 main
    

    带参数的装饰器

    当装饰器带有参数时,调用过程分为两步:

    • 调用装饰器函数(闭包外层的函数)并将参数作为实参传递
    • 用上一步的返回值当作装饰器对函数进行装饰

    因此需要给装饰器加上参数时,我们要对闭包进行再一层包装,在最外层传递入装饰器中传进来的参数。

    一个对操作进行权限检查的例子如下:

    def get_level(level):
        def level_check_wrapper(func):
            def level_check():
                print("正在检查权限:")
                if level == 1:
                    print("权限等级为1")
                    return func()
                elif level == 2:
                    print("权限等级为2")
                    return func()
    
            return level_check
    
        return level_check_wrapper
    
    
    @get_level(1)
    def low_level_op():
        print("低级权限操作")
        print([i for i in range(5)])
    
    
    @get_level(2)
    def high_level_op():
        print("高级权限操作")
        print([item for item in "abcdefgh"])
    
    
    if __name__ == '__main__':
        low_level_op()
        high_level_op()
    

    相关文章

      网友评论

        本文标题:Python进阶 - 装饰器

        本文链接:https://www.haomeiwen.com/subject/gefrqhtx.html