纵有疾风起
人生不言弃

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,把返回值赋值给retret()  # 调用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()  # 把返回值赋值给变量retret()  # 调用内部函数

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

def func1():    def func2():        def func3():            print('func3')        return func3    return func2ret1 = func1()  # func2ret2 = ret1()  # func3ret2()

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

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))  # 输出64print(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 bret = a(create_people)ret()

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

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

再往下看:

def create_people():    print('女娲真厉害,捏个泥吹口气就成了人!')def a(func):    def b():        print('洒点水')        func()    return bcreate_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# 定义一个有返回值的函数@foodef f1():    return '嘿嘿嘿'# 调用被装饰函数ret = f1()  # 调用被装饰函数并拿到结果print(ret)

 

装饰带参数的函数

def foo(func):  # 接收的参数是一个函数名    def bar(x, y):  # 这里需要定义和被装饰函数相同的参数        print("这里是新功能...")  # 新功能        func(x, y)  # 被装饰函数名和参数都有了,就能执行被装饰函数了    return bar# 定义一个需要两个参数的函数@foodef 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 --> f2ff = f()  # ff --> f3ff()  # ff()  --> f3()  --> print(name)  --> 张三

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

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

调用:

f = f1()  # f --> f2ff = f()  # ff --> f3ff()  # 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 inner1def foo2(func):    print("d2")    def inner2():        print("inner2")        return "<b>{}</b>".format(func())    return inner2@foo1@foo2def f1():    return "Hello Andy"# f1 = foo2(f1)  ==> print("d2") ==> f1 = inner2# f1 = foo1(f1)  ==> print("d1") ==> f1 = foo1(inner2) ==> inner1ret = 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 valueimport mathclass Circle:    def __init__(self, radius):        self.radius = radius    @lazyproperty    def area(self):        print('计算面积')        return math.pi * self.radius ** 2c1 = Circle(10)print(c1.area)print(c1.area)

 

文章转载于:https://www.cnblogs.com/liwenzhou/p/9878885.html

原著是一个有趣的人,若有侵权,请通知删除

未经允许不得转载:起风网 » Python基础之装饰器
分享到: 生成海报

评论 抢沙发

评论前必须登录!

立即登录