• 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')
    
    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> func2():
        </span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">'</span><span style="color: #800000;">这里是func2</span><span style="color: #800000;">'</span><span style="color: #000000;">)
    </span><span style="color: #0000ff;">return</span> func2  <span style="color: #008000;">#</span><span style="color: #008000;"> 把func2当成返回值返回</span>
    

    ret = func1() # 调用func1,把返回值赋值给ret
    ret() # 调用ret

    复制代码

    闭包

    灵魂三问

    首先我们来看一个例子:

    复制代码
    def func1():
        name = '张三'
    
    <span style="color: #0000ff;">def</span><span style="color: #000000;"> func2(arg):
        </span><span style="color: #0000ff;">print</span><span style="color: #000000;">(arg)
    func2(name)
    

    func1()

    复制代码

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

    复制代码
    def func1():
        name = '张三'
    
    <span style="color: #0000ff;">def</span><span style="color: #000000;"> func2():
        </span><span style="color: #0000ff;">print</span>(name)  <span style="color: #008000;">#</span><span style="color: #008000;"> 能够访问到外层作用域的变量</span>
    

    func2()

    func1()

    复制代码

    最后再看一个例子:

    复制代码
    def func1(name):
    
    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> func2():
        </span><span style="color: #0000ff;">print</span>(name)  <span style="color: #008000;">#</span><span style="color: #008000;"> 能够访问到外层作用域的变量</span>
    

    func2()

    func1('张三')

    复制代码

    闭包的定义

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

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

    复制代码
    def func1():
        name = '张三'
    
    <span style="color: #0000ff;">def</span><span style="color: #000000;"> func2():
        </span><span style="color: #0000ff;">print</span>(name)  <span style="color: #008000;">#</span><span style="color: #008000;"> 能够访问到外层作用域的变量</span>
    

    func2()
    print(func2.closure) # (<cell at 0x1036c7438: str object at 0x10389d088>,)

    func1()
    print(func1.closure) # None

    复制代码

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

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

    复制代码
    def func1():
        name = '张三'
    
    <span style="color: #0000ff;">def</span><span style="color: #000000;"> func2():
        </span><span style="color: #0000ff;">print</span><span style="color: #000000;">(name)
    
    </span><span style="color: #0000ff;">return</span> func2  <span style="color: #008000;">#</span><span style="color: #008000;"> 把内部函数当成是返回值返回</span>
    

    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):
    # 这是外层函数
    
    <span style="color: #0000ff;">def</span><span style="color: #000000;"> printer():
    </span><span style="color: #008000;">#</span><span style="color: #008000;"> 这是内层函数</span>
        <span style="color: #0000ff;">print</span><span style="color: #000000;">(msg)
    
    </span><span style="color: #0000ff;">return</span> printer  <span style="color: #008000;">#</span><span style="color: #008000;"> 返回内层函数</span>
    

    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")
    
    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> inner1():
        </span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">"</span><span style="color: #800000;">inner1</span><span style="color: #800000;">"</span><span style="color: #000000;">)
        </span><span style="color: #0000ff;">return</span> <span style="color: #800000;">"</span><span style="color: #800000;">&lt;i&gt;{}&lt;/i&gt;</span><span style="color: #800000;">"</span><span style="color: #000000;">.format(func())
    
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> inner1
    

    def foo2(func):
    print("d2")

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> inner2():
        </span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">"</span><span style="color: #800000;">inner2</span><span style="color: #800000;">"</span><span style="color: #000000;">)
        </span><span style="color: #0000ff;">return</span> <span style="color: #800000;">"</span><span style="color: #800000;">&lt;b&gt;{}&lt;/b&gt;</span><span style="color: #800000;">"</span><span style="color: #000000;">.format(func())
    
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> 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 = "装饰"
    
    <span style="color: #0000ff;">def</span> <span style="color: #800080;">__call__</span>(self, *args, **<span style="color: #000000;">kwargs):
        </span><span style="color: #0000ff;">if</span> self.mode == <span style="color: #800000;">"</span><span style="color: #800000;">装饰</span><span style="color: #800000;">"</span><span style="color: #000000;">:
            self.func </span>= args[0]  <span style="color: #008000;">#</span><span style="color: #008000;"> 默认第一个参数是被装饰的函数</span>
            self.mode = <span style="color: #800000;">"</span><span style="color: #800000;">调用</span><span style="color: #800000;">"</span>
            <span style="color: #0000ff;">return</span><span style="color: #000000;"> self
        </span><span style="color: #008000;">#</span><span style="color: #008000;"> 当self.mode == "调用"时,执行下面的代码(也就是调用使用类装饰的函数时执行)</span>
        <span style="color: #0000ff;">if</span><span style="color: #000000;"> self.a:
            </span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">"</span><span style="color: #800000;">欢迎来到{}页面。</span><span style="color: #800000;">"</span><span style="color: #000000;">.format(self.a))
        </span><span style="color: #0000ff;">else</span><span style="color: #000000;">:
            </span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">"</span><span style="color: #800000;">欢迎来到首页。</span><span style="color: #800000;">"</span><span style="color: #000000;">)
        self.func(</span>*args, **<span style="color: #000000;">kwargs)
    

    @D()
    def index(name):
    print("Hello {}.".format(name))

    @D("电影")
    def movie(name):
    print("Hello {}.".format(name))

    if name == 'main':
    index(
    '张三')
    movie(
    '张三')

    复制代码

    @Wrapper('电视')步骤分解:

    Wrapper('电视') --> 对象 --> @对象 --> 对象(func) --> 第一次调用(call)方法为对象创建func属性=被装饰的函数

    --> 返回新对象==hello并将标志属性mode翻转状态 --> hello(name)=对象(name)调用call方法(只会执行return后面的代码)

    总结:可以将类理解成给最外层给装饰器传参数的那一层,初始的实例化对象为装饰器,

    对象调用一次call后更改属性后返回的新对象为inner,return后面的代码为inner的内容

    装饰类

    我们上面所有的例子都是装饰一个函数,返回一个可执行函数。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
    
    </span><span style="color: #0000ff;">def</span> <span style="color: #800080;">__get__</span><span style="color: #000000;">(self, instance, owner):
        </span><span style="color: #0000ff;">if</span> instance <span style="color: #0000ff;">is</span><span style="color: #000000;"> None:
            </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> self
        </span><span style="color: #0000ff;">else</span><span style="color: #000000;">:
            value </span>=<span style="color: #000000;"> self.func(instance)
            setattr(instance, self.func.</span><span style="color: #800080;">__name__</span><span style="color: #000000;">, value)
            </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> value
    

    import math

    class Circle:
    def init(self, radius):
    self.radius
    = radius

    @lazyproperty
    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> area(self):
        </span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">'</span><span style="color: #800000;">计算面积</span><span style="color: #800000;">'</span><span style="color: #000000;">)
        </span><span style="color: #0000ff;">return</span> math.pi * self.radius ** 2<span style="color: #000000;">
    

    c1 = Circle(10)
    print(c1.area)
    print(c1.area)

    复制代码
  • 相关阅读:
    男孩的眼泪
    清冷
    Java随笔
    Java随笔
    skip a transaction in goldengate(跳过一个事务OGG)
    Oracle性能问题一般排查方法
    Oracle GoldenGate(ogg)安装经验大汇总,采坑总结,绝对干货!
    ORACLE 11G 性能诊断优化之ASH实战分析详解
    Oracle SQL性能优化40条 | 收藏了!
    GoldenGate OGG ORACLE数据复制实施方案
  • 原文地址:https://www.cnblogs.com/Kingfan1993/p/9948341.html
Copyright © 2020-2023  润新知