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

    复制代码
    一、函数名的应用
    1、函数名是一个特殊的变量,函数名存放的是函数的内存地址
    def func():
        print('hello')
    
    
    print(func)  # <function func at 0x000001A228B01E18>
    
    
    2、函数名可以作为一个变量
    def func():
        print('hello')
    
    
    f = func  # 把函数名当成变量赋值给另外一个变量
    f()  # 通过变量f调用函数
    
    
    3、函数名可以作为容器类型的元素
    def func1():
        print('func1')
        
    
    def func2():
        print('func2')
        
        
    def func3():
        print('func3')
        
        
    def func4():
        print('func4')
    
    
    list1 = [func1, func2, func3, func4]
    for f in list1:
        f()
    
    
    4、函数名可以作为函数的参数
    def func1():
        print('func1')
    
    
    def func2(arg):
        print('func2')
        arg()  # 执行传递进来的arg
    
        
    func2(func1)  # 把func1当成参数传递给func2
    
    
    5、函数名可以作为函数的返回值
    def func1():
        print('func1')
    
        def func2():
            print('func2')
        return func2  # 把func2当成返回值返回
    
    
    ret = func1()  # 调用func1,把返回值赋值给ret
    ret()  # 调用ret
    
    
    
    二、闭包
    1、定义
    内层函数对外层函数(非全局)的变量的引用,这个内层函数就成为闭包。
    在Python中,我们可以使用__closure__来检测函数是否是闭包。
    有cell元素的是闭包。
    
    
    例如:
    def func():
        name = '番薯'
    
        def func2():
            print(name)  # 引用外层函数的变量
        func2()
        print(func2.__closure__)  # (<cell at 0x000002591EA794F8: str object at 0x000002591EACB500>)
    
    func()
    print(func.__closure__)  # None
    
    
    2、闭包的例子
    2-1、这里并没有引用外层函数的变量,而是把外层函数的变量传给func2,所以不算闭包
    def func1():
        name = '番薯'
    
        def func2(arg):
            print(arg)
        func2(name)
        print(func2.__closure__)
        
        
    func1()
    
    
    2-2def func1():
        name = '番薯'
    
        def func2():
            print(name)  # 引用外层函数的变量,形成闭包
        func2()
        print(func2.__closure__)
    
    
    func1()
    
    
    2-3def func1(name):
        def func2():
            print(name)  # 引用外层函数的变量,形成闭包
        func2()
        print(func2.__closure__)
    
    
    func1('番薯')
    
    
    3、闭包的应用
    3-1、把内部函数(闭包)当成返回值返回就可以使用闭包了
    def func1():
        name = '番薯'
    
        def func2():
            print(name)
    
        return func2  # 把内部函数当成是返回值返回
    
    
    ret = func1()  # 把返回值赋值给变量ret
    ret()  # 调用内部函数
    
    
    3-2、多层嵌套的闭包
    def func1():
        def func2():
            def func3():
                print('func3')
            return func3
        return func2
    
    
    f2 = func1()  # func2
    f3 = f2()  # func3
    f3()
    
    
    4、闭包的好处
    可以在任何时间从外界访问到内部函数。
    但是我们知道一个函数执行完毕后,这个函数中的变量以及局部命名空间中的内容都是会被销毁的。
    在闭包中如果变量被销毁了,那内部函数就不能正常执行了。
    所以一旦这个内部函数引用了外层函数中的变量形成了闭包,那么这个变量将不会随着外层函数的结束而销毁,它会在内存中保留。
    也就是说,闭包可以保留变量的引用。
    
    
    
    三、装饰器初识
    1、开放封闭原则
    软件设计的原则: 开闭原则, 又被成为开放封闭原则。
    开放封闭原则是指对扩展代码的功能是开放的,
    但是对修改源代码是封闭的。
    这样的软件设计思路可以保证我们更好的开发和维护我们的代码。
    
    
    比如:你开了一家老字号(下面的代码就相当于源码)
    def old_shop():
        print('离百年老字号还差99年')
    
    
    old_shop()
    
    
    然后你的老店要新增空调(相当于新增功能):
    def old_shop():
        print('新增空调')
        print('离百年老字号还差99年')
    
    
    old_shop()
    
    这样添加功能确实是可以的,但是违背了开放封闭原则,直接在源码上进行修改了。
    那怎么办呢?新建一个函数不就好了
    def old_shop_with_conditioner():
        print('新增空调')
        print('离百年老字号还差99年')
    
    
    old_shop_with_conditioner()
    
    但是,问题又来了,你的老字号很火,开了很多分店,每家分店都是调用了之前的old_shop函数开店,
    那么你修改了之后,所有调用原来函数的分店都需要重新调用新的函数,这么做其实是很番薯的行为。
    
    那么如何在不改变函数的结构和调用方式的基础上,动态的给函数添加功能呢?
    可以利用闭包
    def old_shop():
        print('离百年老字号还差99年')
    
    
    def a(func):
        def b():
            print('新增空调')
            func()
        return b
    
    
    ret = a(old_shop)  # 内层的b函数
    ret()
    
    然后问题又来了,现在虽然没有直接修改源码,但是函数名还是改变了,那又怎么办?
    重命名不就行了吗:
    def old_shop():
        print('离百年老字号还差99年')
    
    
    def a(func):
        def b():
            print('新增空调')
            func()
        return b
    
    
    old_shop = a(old_shop)  # 内层的b函数
    old_shop()
    
    这样不就遵循了开发封闭原则了吗,即没有修改源码,扩展代码又是开放的,也没有改变函数原来的调用方式
    
    
    2、装饰器语法糖
    刚才上面的代码只是装饰器的原理和雏形,Python中针对于上面的功能提供了一个快捷的写法,俗称装饰器语法糖。
    使用装饰器语法糖的写法,实现同样功能的代码如下:
    def a(func):  # a是我们定义的装饰器函数,func是被装饰的函数(old_shop)
        def b():
            print('新增空调')
            func()
        return b
    
    
    @a    # 相当于把被装饰的函数old_shop当成参数传给a,然后把返回值b再重新赋值给被装饰的函数名old_shop
    def old_shop():
        print('离百年老字号还差99年')
    
    
    old_shop()  # 相当于调用了内层函数b
    
    
    
    四、装饰器进阶
    1、装饰带返回值的函数
    def wrapper(func):
        def inner():
            print('新功能')  # 你要新增的功能
            result = func()  # 拿到被装饰函数的返回值
            return result  # 返回被装饰的函数的返回值
        return inner
    
    
    @wrapper
    def f():
        return 'Hello'
    
    
    ret = f()
    print(ret)
    
    
    2、装饰带参数的函数
    def wrapper(func):
        def inner(x, y):  # 实际执行函数的参数
            print('新功能')
            r = func(x, y)
            return r
        return inner
    
    
    @wrapper
    def my_sum(x, y):
        return x + y
    
    
    ret = my_sum(1, 2)  # inner(10, 20)
    print(ret)
    
    但是一般来说,我们把参数设置成动态参数会更便于拓展
    若是三个数相加,或者四个数相加,只需要修改my_sum的参数就可以了
    def wrapper(func):
        def inner(*args, **kwargs):
            print('新功能')
            r = func(*args, **kwargs)
            return r
        return inner
    
    
    @wrapper
    def my_sum(x, y, z):
        return x + y + z
    
    
    ret = my_sum(1, 2, 3)
    print(ret)
    
    
    3、带参数的装饰器
    装饰器如果要带参数的话,可以嵌套更多层:
    def outer(arg):
        def wrapper(func):
            def inner(*args, **kwargs):
                print('欢迎来到%s' % arg)
                func(*args, **kwargs)
            return inner
        return wrapper
    
    
    @outer('英雄联盟')  # 会先执行outer,然后返回函数名wrapper,相当于@wrapper,在闭包内还能使用最外层的函数变量
    def lol():
        print('这里是召唤师峡谷')
    
    
    @outer('地下城与勇士')  # @wrapper
    def dnf():
        print('这里是阿拉德大陆')
    
    
    lol()
    dnf()
    
    
    4、装饰器修复技术
    
    被装饰的函数最终都会失去本来的__doc__等信息,就是说,如果这个函数被装饰了,
    那么它里面的文档信息(注释信息,通常注释信息很重要,注释写明了改函数的功能和参数的信息等)就会消失,
     Python给我们提供了一个修复被装饰函数的工具,用于找回这些信息。
     from functools import wraps
    
    
    def wrapper(func):
        @wraps(func)
        def inner(*args, **kwargs):
            print('这是新功能')
            func(*args, **kwargs)
        return inner
    
    
    @wrapper
    def f1(x, y):
        """
        这里写这个函数的主要功能
        :param x: 这个参数的类型
        :param y: 这个参数的类型
        :return: 返回值
        """
        print('我是帅哥')
    
    
    print(f1.__doc__)  # 打印这个函数的文档信息(注释内容)
    print(f1.__name__)  # 打印这个函数名
    
    
    
    5、多个装饰器装饰同一函数
    def wrapper1(func):
        print('w1')
    
        def inner1():
            print('inner1')
            return '<i>{}</i>'.format(func())
        return inner1
    
    
    def wrapper2(func):
        print('w2')
    
        def inner2():
            print('inner2')
            return '<b>{}</b>'.format(func())
        return inner2
    
    
    @wrapper1
    @wrapper2
    def f1():
        return "小明"
    
    
    ret = f1()
    print(ret)
    
    结果:
    w2
    w1
    inner1
    inner2
    <i><b>小明</b></i>
    
    分析:
    在装饰阶段会直接执行装饰函数,并拿到返回值,即
    @wrapper2  --->  wrapper2(f1)  ---> print('w2') ---> return inner2 ---> 把变量f1重新指向inner2
    @wrapper1 --->  wrapper1(f1)[此时的f1实际上是inner2]  ---> print('w1') ---> return inner1 ---> 把变量f1重新指向inner1
    然后执行f1()相当于执行inner1()
    print('inner1')
    return '<i>{}</i>'.format(func())
    此时的func是传进来的参数inner2,所以又去执行inner2
    print('inner2')
    return '<b>{}</b>'.format(func())
    此时的func是传进来的参数f1[被装饰的f1],所以拿到返回值<b>小明</b>,拼接到inner1的返回值那里,最后
    <i><b>小明</b></i>
    
    
    
    五、数码暴龙进化装饰器
    1、类装饰器
    我们除了可以使用函数装饰函数外,还可以用类装饰函数。
    class Page(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)
    
    
    @Page()
    def index(name):
        print("Hello {}.".format(name))
    
    
    @Page("电影")
    def movie(name):
        print("Hello {}.".format(name))
    
    
    if __name__ == '__main__':
        index('番薯')
        movie('番薯')
    
    
    2、装饰类
    上面所有的例子都是装饰一个函数,返回一个可执行函数。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()
    
        
        
    六、装饰器小结(重)
    1、装饰器的标准结构
    from functools import wraps
    
    
    def wrapper(func):  # func:被装饰的函数
        @wraps(func)  # 把func指向的函数的__doc__、__name__等属性复制到inner上面
        def inner(*args, **kwargs):  # *args和**kwargs是被装饰函数的参数
            print('新功能')
            r = func(*args, **kwargs)
            print('新功能也可以在这里')
            return r
        return inner
    
    
    @wrapper  # 此时会执行wrapper,所以wrapper必须定义在这一行之前
    def hello():
        print('Hello World!')
    
    
    hello()
    
    
    2、带参数的装饰器
    from functools import wraps
    
    
    def outer(k=None):
        def wrapper(func):
            @wraps(func)
            def inner(*args, **kwargs):
                if k == 'start':
                    print('节目开始')
                    r = func(*args, **kwargs)
                    return r
                else:
                    print('节目还未开始')
            return inner
        return wrapper
    
    
    @outer('start')
    def hello():
        """这里是hello函数"""
        print('Hello World!')
    
    
    hello()
    print(hello.__doc__)
    print(hello.__name__)
    
    
    3、多个装饰器同时装饰一个函数
    """
    给Hello World!包两层标签,
    <div><p>Hello World!</p></div>
    """
    from functools import wraps
    
    
    def wrapper1(func):  # 包p标签
        @wraps(func)
        def inner1(*args, **kwargs):
            r = func(*args, **kwargs)
            return '<p>{}</p>'.format(r)
        return inner1
    
    
    def wrapper2(func):  # 包div标签
        @wraps(func)
        def inner2(*args, **kwargs):
            r = func(*args, **kwargs)
            return '<div>{}</div>'.format(r)
        return inner2
    
    
    @wrapper2
    @wrapper1
    def hello():
        return "Hello World!"
    
    
    print(hello())
    
    
    
    
    
    
    
    
    
    
        
    复制代码
  • 相关阅读:
    2019年上半年收集到的人工智能深度学习方向干货文章
    2019年上半年收集到的人工智能自然语言处理方向干货文章
    2019年上半年收集到的人工智能图神经网络干货文章
    2019年上半年收集到的人工智能自动驾驶方向干货文章
    SAP 不支持交货单中同一个物料多个行项目HU与序列号组合发货场景
    SAP S4HANA 使用BP创建供应商报错
    yum/dnf/rpm 等 查看rpm 包安装路径 (fedora 中 pygtk 包内容安装到哪里了)
    apache 允许 访问软链接 ( Apache won't follow symlinks (403 Forbidden) )
    fedora 开启 apache 并 开启目录浏览模式
    如何在 windows server 2008 上面 挂载NFS
  • 原文地址:https://www.cnblogs.com/yidashi110/p/10092003.html
Copyright © 2011-2022 走看看