zoukankan      html  css  js  c++  java
  • Python之装饰器

    装饰器

    此文可能是有史以来最全的关于Python装饰器的Blog了...

    函数名的运用

    关于函数名

    函数名是⼀个变量,但它是⼀个特殊的变量。与括号配合可以执⾏函数的变量。

    查看函数名的内存地址:

    def func():
        print('呵呵')
    
    print(func)  # <function func at 0x10983c048>

    做变量

    def func():
        print('呵呵')
    
    
    a = func  # 把函数当成变量赋值给另外一个变量
    a()  # 通过变量a调用函数

    做容器的元素

    def func1():
        print('func1')
        
    
    def func2():
        print('func2')
        
        
    def func3():
        print('func3')
        
        
    def func4():
        print('func4')
    
    
    list1 = [func1, func2, func3, func4]
    for i in list1:
        i()

    做参数

    def func1():
        print('func1')
    
    
    def func2(arg):
        print('start')
        arg()  # 执行传递进来的arg
        print('end')
    
    func2(func1)  # 把func1当成参数传递给func2

    做返回值

    def func1():
        print('这里是func1')
    
        def func2():
            print('这里是func2')
        return func2  # 把func2当成返回值返回
    
    
    ret = func1()  # 调用func1,把返回值赋值给ret
    ret()  # 调用ret

    闭包

    灵魂三问

    首先我们来看一个例子:

    def func1():
        name = '张三'
    
        def func2(arg):
            print(arg)
        func2(name)
    
    func1()

    理解了上面的例子,我们再看一个例子:

    def func1():
        name = '张三'
    
        def func2():
            print(name)  # 能够访问到外层作用域的变量
        func2()
    
    func1()

    最后再看一个例子:

    def func1(name):
    
        def func2():
            print(name)  # 能够访问到外层作用域的变量
        func2()
    
    func1('张三')

    闭包的定义

    一个内层函数中,引用了外层函数(非全局)的变量,这个内层函数就可以成为闭包。

    在Python中,我们可以使用__closure__来检测函数是否是闭包。

    def func1():
        name = '张三'
    
        def func2():
            print(name)  # 能够访问到外层作用域的变量
        func2()
        print(func2.__closure__)  # (<cell at 0x1036c7438: str object at 0x10389d088>,)
    
    func1()
    print(func1.__closure__)  # None

    问题来了,我们如何在函数外边调用函数内部的函数呢?

    当然是把内部函数当成返回值返回了。

    def func1():
        name = '张三'
    
        def func2():
            print(name)
    
        return func2  # 把内部函数当成是返回值返回
    
    
    ret = func1()  # 把返回值赋值给变量ret
    ret()  # 调用内部函数

    内部函数当然还可包含其他的函数,多层嵌套的道理都是一样的。

    def func1():
        def func2():
            def func3():
                print('func3')
            return func3
        return func2
    
    
    ret1 = func1()  # func2
    ret2 = ret1()  # func3
    ret2()

    接下来我们看下面这个例子,来更深刻的理解一下闭包的含义:

    def print_msg(msg):
    # 这是外层函数
    
        def printer():
        # 这是内层函数
            print(msg)
    
        return printer  # 返回内层函数
    
    
    func = print_msg("Hello")
    func()

    现在我们进行如下操作:

    >>> del print_msg
    >>> func()
    Hello
    >>> print_msg("Hello")
    Traceback (most recent call last):
    ...
    NameError: name 'print_msg' is not defined

    我们知道如果一个函数执行完毕,则这个函数中的变量以及局部命名空间中的内容都将会被销毁。在闭包中内部函数会引用外层函数的变量,而且这个变量将不会随着外层函数的结束而销毁,它会在内存中保留。

    也就是说,闭包函数可以保留其用到的变量的引用。

    闭包面试题

    # 编写代码实现func函数,使其实现以下效果:
    foo = func(8)
    print(foo(8))  # 输出64
    print(foo(-1))  # 输出-8

    装饰器

    装饰器来历

    在说装饰器之前,我们先说⼀个软件设计的原则: 开闭原则, ⼜被成为开放封闭原则。

    开放封闭原则是指对扩展代码的功能是开放的,但是对修改源代码是封闭的。这样的软件设计思路可以保证我们更好的开发和维护我们的代码。

    我们先来写一个例子,模拟一下女娲造人:

    def create_people():
        print('女娲真厉害,捏个泥吹口气就成了人!')
        
    
    create_people()

    好吧,现在问题来了。上古时期啊,天气很不稳定,这个时候突然大旱三年。女娲再去捏人啊,因为太干了就捏不到一块儿去了,需要在捏人之前洒点水才行。

    def create_people():
        print('洒点水')
        print('女娲真厉害,捏个泥吹口气就成了人!')
    
    
    create_people()

    这不就搞定了么?但是呢,我们是不是违背了开放封闭原则呢?我们是添加了新的功能,但是我们是直接修改了源代码。在软件开发中我们应该对直接修改源代码是谨慎的。

    比如,女娲为了防止浪费,想用剩下点泥巴捏个鸡、鸭、鹅什么的,也需要洒点水。那我们能在每个造鸡、造鸭、造鹅函数的源代码中都手动添加代码么?肯定是不现实的。

    怎么办?再写一个函数不就OK了么?

    def create_people():
        print('女娲真厉害,捏个泥吹口气就成了人!')
    
    
    def create_people_with_water():
        print('洒点水')
        create_people()
    
    
    create_people_with_water()

    不让我直接修改源代码,那我重新写一个函数不就可以了吗?

    但是,你有没有想过一个问题,女娲造人也很累的,她后来开了很多分店,每家分店都是调用了之前的create_people函数造人,那么你修改了之后,是不是所有调用原来函数的人都需要修改调用函数的名称呢?很麻烦啊!!!

    总结一句话就是如何在不改变函数的结构和调用方式的基础上,动态的给函数添加功能?

    def create_people():
        print('女娲真厉害,捏个泥吹口气就成了人!')
    
    
    def a(func):
        def b():
            print('洒点水')
            func()
        return b
    
    ret = a(create_people)
    ret()

    利用闭包函数不就可以了么?

    但是,你这最后调用的是ret啊,不还是改变了调用方式么?

    再往下看:

    def create_people():
        print('女娲真厉害,捏个泥吹口气就成了人!')
    
    
    def a(func):
        def b():
            print('洒点水')
            func()
        return b
    
    create_people = a(create_people)
    create_people()

    上面这段代码是不是完美解决了我们的问题呢?

    看一下它的执行过程吧:

    1. 首先访问a(create_people)
    2. 把create_people函数赋值给了a函数的形参func,记住后续执行func的话实际上是执行了最开始传入的create_people函数。
    3. a函数执行过程就是一句话,返回了b函数。这个时候把b函数赋值给了create_people这个变量
    4. 执行create_people的时候,相当于执行了b函数,先打印洒点水再执行func,也就是我们最开始传入的create_people函数

    我们巧妙的使用闭包实现了,把一个函数包装了一下,然后再赋值给原来的函数名。

    装饰器语法糖

    上面的代码就是一个装饰器的雏形,Python中针对于上面的功能提供了一个快捷的写法,俗称装饰器语法糖。

    使用装饰器语法糖的写法,实现同样功能的代码如下:

    def a(func):
        def b():
            print('洒点水')
            func()
        return b
    
    
    @a  # 装饰器语法糖
    def create_people():
        print('女娲真厉害,捏个泥吹口气就成了人!')
    
    create_people()

    装饰器进阶

    装饰带返回值的函数

    如果被装饰的函数有返回值,我们应该怎么处理呢?

    请看下面的示例:

    def foo(func):  # 接收的参数是一个函数名
        def bar():  # 定义一个内层函数
            print("这里是新功能...")  # 新功能
            r = func()  # 在内存函数中拿到被装饰函数的结果
            return r  # 返回被装饰函数的执行结果
        return bar
    
    
    # 定义一个有返回值的函数
    @foo
    def f1():
        return '嘿嘿嘿'
    
    
    # 调用被装饰函数
    ret = f1()  # 调用被装饰函数并拿到结果
    print(ret)

    装饰带参数的函数

    def foo(func):  # 接收的参数是一个函数名
        def bar(x, y):  # 这里需要定义和被装饰函数相同的参数
            print("这里是新功能...")  # 新功能
            func(x, y)  # 被装饰函数名和参数都有了,就能执行被装饰函数了
        return bar
    
    
    # 定义一个需要两个参数的函数
    @foo
    def f1(x, y):
        print("{}+{}={}".format(x, y, x+y))
    
    
    # 调用被装饰函数
    f1(100, 200)

    带参数的装饰器

    被装饰的函数可以带参数,装饰器同样也可以带参数。

    回头看我们上面写得那些装饰器,它们默认把被装饰的函数当成唯一的参数。但是呢,有时候我们需要为我们的装饰器传递参数,这种情况下应该怎么办呢?

    接下来,我们就一步步实现带参数的装饰器:

    首先我们来回顾下上面的代码:

    def f1(func):  # f1是我们定义的装饰器函数,func是被装饰的函数
        def f2(*arg, **kwargs):  # *args和**kwargs是被装饰函数的参数
            func(*arg, **kwargs)
        return f2

    从上面的代码,我们发现了什么?

    我的装饰器如果有参数的话,没地方写了…怎么办呢?

    还是要使用闭包函数!

    我们需要知道,函数除了可以嵌套两层,还能嵌套更多层:

    # 三层嵌套的函数
    def f1():    
        def f2():
            name = "张三"       
            def f3():
                print(name)
            return f3    
        return f2

    嵌套三层之后的函数调用:

    f = f1()  # f --> f2
    ff = f()  # ff --> f3
    ff()  # ff()  --> f3()  --> print(name)  --> 张三

    注意:在内部函数f3中能够访问到它外层函数f2中定义的变量,当然也可以访问到它最外层函数f1中定义的变量。

    # 三层嵌套的函数2
    def f1():
        name = '张三'
        def f2():
            def f3():
                print(name)
            return f3
        return f2

    调用:

    f = f1()  # f --> f2
    ff = f()  # ff --> f3
    ff()  # ff()  --> f3()  --> print(name)  --> 张三

    好了,现在我们就可以实现我们的带参数的装饰器函数了:

    # 带参数的装饰器需要定义一个三层的嵌套函数
    def d(name):  # d是新添加的最外层函数,为我们原来的装饰器传递参数,name就是我们要传递的函数
        def f1(func):  # f1是我们原来的装饰器函数,func是被装饰的函数
            def f2(*arg, **kwargs):  # f2是内部函数,*args和**kwargs是被装饰函数的参数
                print(name)  # 使用装饰器函数的参数
                func(*arg, **kwargs)  # 调用被装饰的函数
            return f2
        return f1

    上面就是一个带参装饰器的代码示例,现在我们来写一个完整的应用:

    def d(a=None):  # 定义一个外层函数,给装饰器传参数--role
        def foo(func):  # foo是我们原来的装饰器函数,func是被装饰的函数
            def bar(*args, **kwargs):  # args和kwargs是被装饰器函数的参数
                # 根据装饰器的参数做一些逻辑判断
                if a:
                    print("欢迎来到{}页面。".format(a))
                else:
                    print("欢迎来到首页。")
                # 调用被装饰的函数,接收参数args和kwargs
                func(*args, **kwargs)
            return bar
        return foo
    
    
    @d()  # 不给装饰器传参数,使用默认的'None'参数
    def index(name):
        print("Hello {}.".format(name))
    
    
    @d("电影")  # 给装饰器传一个'电影'参数
    def movie(name):
        print("Hello {}.".format(name))
    
    if __name__ == '__main__':
        index('张三')
        movie('张三')

    装饰器修复技术

    被装饰的函数最终都会失去本来的__doc__等信息, Python给我们提供了一个修复被装饰函数的工具。

    def a(func):
        @wraps(func)
        def b():
            print('洒点水')
            func()
        return b
    
    
    @a  # 装饰器语法糖
    def create_people():
        """这是一个女娲造人的功能函数"""
        print('女娲真厉害,捏个泥吹口气就成了人!')
    
    create_people()
    print(create_people.__doc__)
    print(create_people.__name__)

    多个装饰器装饰同一函数

    同一个函数可以被多个装饰器装饰,此时需要注意装饰器的执行顺序。

    def foo1(func):
        print("d1")
    
        def inner1():
            print("inner1")
            return "<i>{}</i>".format(func())
    
        return inner1
    
    
    def foo2(func):
        print("d2")
    
        def inner2():
            print("inner2")
            return "<b>{}</b>".format(func())
    
        return inner2
    
    
    @foo1
    @foo2
    def f1():
        return "Hello Andy"
    
    # f1 = foo2(f1)  ==> print("d2") ==> f1 = inner2
    # f1 = foo1(f1)  ==> print("d1") ==> f1 = foo1(inner2) ==> inner1
    
    ret = f1()  # 调用f1() ==> inner1()  ==> <i>inner2()</i>  ==> <i><b>inner1()</b></i> ==> <i><b>Hello Andy</b></i>
    print(ret)

    装饰器终极进阶

    类装饰器

    我们除了可以使用函数装饰函数外,还可以用类装饰函数。

    class D(object):
        def __init__(self, a=None):
            self.a = a
            self.mode = "装饰"
    
        def __call__(self, *args, **kwargs):
            if self.mode == "装饰":
                self.func = args[0]  # 默认第一个参数是被装饰的函数
                self.mode = "调用"
                return self
            # 当self.mode == "调用"时,执行下面的代码(也就是调用使用类装饰的函数时执行)
            if self.a:
                print("欢迎来到{}页面。".format(self.a))
            else:
                print("欢迎来到首页。")
            self.func(*args, **kwargs)
    
    
    @D()
    def index(name):
        print("Hello {}.".format(name))
    
    
    @D("电影")
    def movie(name):
        print("Hello {}.".format(name))
    
    if __name__ == '__main__':
        index('张三')
        movie('张三')

    装饰类

    我们上面所有的例子都是装饰一个函数,返回一个可执行函数。Python中的装饰器除了能装饰函数外,还能装饰类。

    可以使用装饰器,来批量修改被装饰类的某些方法:

    # 定义一个类装饰器
    class D(object):
        def __call__(self, cls):
            class Inner(cls):
                # 重写被装饰类的f方法
                def f(self):
                    print('Hello 张三.')
            return Inner
    
    
    @D()
    class C(object):  # 被装饰的类
        # 有一个实例方法
        def f(self):
            print("Hello world.")
    
    
    if __name__ == '__main__':
        c = C()
        c.f()

    举个实际的应用示例:

    我们把类中的一个只读属性定义为property属性方法,只有在访问它时才参与计算,一旦访问了该属性,我们就把这个值缓存起来,下次再访问的时候无需重新计算。

    class lazyproperty:
        def __init__(self, func):
            self.func = func
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            else:
                value = self.func(instance)
                setattr(instance, self.func.__name__, value)
                return value
    
    
    import math
    
    
    class Circle:
        def __init__(self, radius):
            self.radius = radius
    
        @lazyproperty
        def area(self):
            print('计算面积')
            return math.pi * self.radius ** 2
    
    
    c1 = Circle(10)
    print(c1.area)
    print(c1.area)
  • 相关阅读:
    Spring 中出现Element : property Bean definitions can have zero or more properties. Property elements correspond to JavaBean setter methods exposed by the bean classes. Spring supports primitives, refer
    java定时器schedule和scheduleAtFixedRate区别
    hql语句中的select字句和from 字句
    使用maven搭建hibernate的pom文件配置
    Failure to transfer org.apache.maven:maven-archiver:pom:2.5 from http://repo.maven.apache.org/ maven2 was cached in the local repository, resolution will not be reattempted until the update interv
    对于文件File类型中的目录分隔符
    hibernate的事务管理和session对象的详解
    解决mac 中的myeclipse控制台中文乱码问题
    ibatis selectKey用法问题
    Java中getResourceAsStream的用法
  • 原文地址:https://www.cnblogs.com/Skeener/p/9962966.html
Copyright © 2011-2022 走看看