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())










    
复制代码

 

posted @ 2018-12-09 16:53  从入门到出师  阅读(183)  评论(0编辑  收藏  举报