Python 装饰器

装饰器(decorator)是Python中的高级语法。装饰的意思就是动态扩展被装饰对象的功能。装饰器可以用于装饰函数、方法和类。

学习装饰器之前,先了解函数的嵌套和闭包。

函数嵌套

所谓函数的嵌套就是在函数中再定义一个函数。

# 定义一个外层函数
def foo():
    # 定义了一个内层函数
    def bar():
        print("hello world")

    return bar


func = foo()  # func --> bar 函数名即变量名
func()  # 这里执行func其实就相当于执行了在foo函数内部定义的bar函数,打印 hello world

闭包

看一个例子

def foo():
    name = "lcg"  # 定义了一个foo函数内部的局部变量

    def bar():
        print(name)  # 在bar函数内部引用了其外部函数foo的局部变量name

    return bar

现在,我们来调用一下 foo 函数:

func = foo()
func()

上面的代码,输出结果是:

lcg

bar函数不需要传参数,就能获取到外面的变量。这就是典型的一个闭包现象。

闭包的定义

bar在foo函数的代码块中定义。我们称bar是foo的内部函数。
在bar的局部作用域中可以直接访问foo局部作用域中定义的name变量。
简单的说,这种内部函数可以使用外部函数变量的行为,就叫闭包。

注意,即使你在调用 func 之前,定义一个全局变量 name,它还是会使用原来foo 函数内部的 name

func = foo()
name = "lucky boy"
func()

输出:

lcg

此时调用 func函数时它会使用自己定义阶段引用的外部函数的局部变量 name ,而不使用调用阶段的全局变量。

闭包的实质

闭包是由函数和与它相关的引用环境组合而成的实体。
Python中可以通过以下命令查看闭包相关信息:

print(func.__closure__)

输出:

(<cell at 0x0000021EC09A55B8: str object at 0x0000021EC0A38068>,)

__closure__ 属性定义的是一个包含 cell 对象的元组,其中元组中的每一个 cell 对象用来保存外部函数作用域中内部函数调用的变量的值。

看一下对比:

# 引用内部函数的变量

def foo():
    name = "lcg"

    def bar():
        x = 1
        print(name, x)

    return bar


func = foo()
print(func.__closure__)

# (<cell at 0x0000023C860055B8: str object at 0x0000023C86098068>,)


# 引用外部函数的变量

def foo():
    name = "lcg"
    x = 1

    def bar():
        print(name, x)

    return bar


func = foo()
print(func.__closure__)

# (<cell at 0x0000023EC39155B8: str object at 0x0000023EC39A8068>, <cell at 0x0000023EC39155E8: int object at 0x00000000548E60E0>)
__closure__

可以通过以下命令查看闭包具体包含的变量值:

print(func.__closure__[0].cell_contents)

输出:

lcg

像下面的 func函数就不能称为闭包,因为它并没有使用包含它的外部函数的局部变量。

name = "lcg"  # 定义了一个全局变量


def foo():
    def bar():
        print(name)  # 在bar函数内部引用了全局变量name

    return bar


func = foo()

此时:

print(func.__closure__)

输出:

None

闭包的第二种形态:

def foo(name):  # 给一个函数传参也相当于给函数定义了一个局部变量
    # 定义了一个内部函数
    def bar():
        print(name)  # 内部函数同样可以获取到传到外部函数的变量(参数)

    return bar


func = foo("lcg")  # 把“lcg”当成参数传入foo函数 --> 其内部定义的bar函数也能拿到这个“lcg”
func()  

# lcg

装饰器

为什么要有装饰器?
在学习装饰器之前,一定要了解一个开放封闭原则。软件开发都应该遵循开放封闭原则。
开放封闭原则:

  • 对扩展是开放的
  • 对修改是封闭的

为什么说要对扩展是开放的呢?
因为软件开发过程不可能一次性把所有的功能都考虑完全,肯定会有不同的新功能需要不停添加。也就是说需要我们不断地去扩展已经存在代码的功能,这是非常正常的情况。
那为什么说对修改是封闭的呢?
比如说已经上线运行的源代码,比如某个函数内部的代码是不建议直接修改的。因为函数的使用分为两个阶段:函数的定义阶段和函数的调用阶段。因为你不确定这个函数究竟在什么地方被调用了,你如果粗暴的修改了函数内部的源代码,对整个程序造成的影响是不可控的。
总结一下就是:不修改源代码,不修改调用方式,同时还要加上新功能。
在Python中就可以使用装饰器来实现上面的需求。
什么是装饰器?

简单理解就是装饰其他对象(可调用对象)的工具。

装饰器应用了函数闭包的原理

装饰器例子:

# 还是定义一个外层函数
def foo(func):  # 我接收的参数是一个函数名
    # 定义了一个内部函数
    def bar():
        print("这是新功能。。。")  # 新功能
        func()  # 函数名加()就相当于执行

    return bar


# 定义一个被装饰的函数
def f1():
    print("hello world.")


# 用foo函数装饰f1函数
f1 = foo(f1)
# 不改变f1的调用方式
f1()  # --> 此时函数已经扩展了新功能

输出:

这是新功能。。。
hello world.

语法糖

使用f1 = foo(f1)语法装饰的话稍显啰嗦,Python就提供了@语法,让装饰过程更简便

def foo(name):
    def bar():
        print("这是新功能。。。")
        name()

    return bar


@foo
def f1():
    print("hello world.")


f1()  # --> 此时函数已经扩展了新功能

输出:

这是新功能。。。
hello world.

现在,我们已经明白了装饰器的原理。接下来,我们还有很多事情需要搞清楚。比如:装饰带参数的函数、多个装饰器同时装饰一个函数、带参数的装饰器和类装饰器。

装饰带参数的函数

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)

输出:

这里是新功能...
100+200=300

多个装饰器

def wrapper1(func):
    def inner():
        print('w1,before')
        func()
        print('w1,after')

    return inner


def wrapper2(func):
    def inner():
        print('w2,before')
        func()
        print('w2,after')

    return inner


@wrapper2
@wrapper1
def foo():
    print('foo')


foo()

输出:

w2,before
w1,before
foo
w1,after
w2,after

带参数装饰器

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

回头看我们上面写得那些装饰器,它们默认把被装饰的函数当成唯一的参数。但是呢,有时候我们需要为我们的装饰器传递参数,这种情况下应该怎么办呢?
接下来,我们就一步步实现带参数的装饰器
首先我们来回顾下上面的代码:

 

 

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

    return f2

从上面的代码,我们发现了什么?
我的装饰器如果有参数的话,没地方写了…怎么办呢?
还是要使用闭包函数!
我们需要知道,函数除了可以嵌套两层,还能嵌套更多层:

# 三层嵌套的函数
def f1():
    def f2():
        name = "lcg"

        def f3():
            print(name)

        return f3

    return f2


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

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

def f1():
    name = "lcg"

    def f2():
        def f3():
            print(name)

        return f3

    return f2


f = f1() 
ff = f() 
ff()  # --> lcg

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

# 带参数的装饰器需要定义一个三层的嵌套函数
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):  # 定义一个外层函数,给装饰器传参数a默认是None
    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 wish(name):
    print("{}祝您前程似锦".format(name))


@d("亲爱的园友")  # 给装饰器传一个参数
def greet_wish(name):
    print("{}祝您前程似锦".format(name))


if __name__ == '__main__':
    wish("lcg")
    print('-' * 50)
    greet_wish("lcg")

输出:

您好
lcg祝您前程似锦
--------------------------------------------------
亲爱的园友您好
lcg祝您前程似锦

类装饰器和装饰类

类装饰器

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

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 wish(name):
    print("{}祝您前程似锦".format(name))


@D("亲爱的园友")
def greet_wish(name):
    print("{}您前程似锦".format(name))


if __name__ == '__main__':
    wish("lcg")
    print('-' * 50)
    greet_wish("lcg")

输出:

您好
lcg祝您前程似锦
--------------------------------------------------
亲爱的园友您好
lcg您前程似锦

装饰类

我们上面所有的例子都是装饰一个函数,返回一个可执行函数。Python中的装饰器除了能装饰函数外,还能装饰类。
可以使用装饰器,来批量修改被装饰类的某些方法:

# 定义一个类装饰器
class D(object):
    def __call__(self, cls):
        class Inner(cls):
            # 重写被装饰类的f方法
            def f(self):
                print("Hello lcg.")

        return Inner


@D()
class C(object):  # 被装饰的类
    # 有一个实例方法
    def f(self):
        print("Hello world.")


if __name__ == '__main__':
    c = C()
    c.f()

输出:

Hello lcg.

装饰器修复技术

官方解释:https://docs.python.org/3/library/functools.html#functools.wraps

from functools import wraps


def deco(func):
    @wraps(func)  # 加在最内层函数正上方
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper


@deco
def index():
    '''哈哈哈哈'''
    x = 10
    print('from index')


print(index.__name__)
print(index.__doc__)

# 加wraps
# index
# 哈哈哈哈

# 不加waps
# wrapper
# None

  

posted @ 2017-12-04 19:08  0bug  阅读(449)  评论(0编辑  收藏  举报