理解Python中的装饰器
veekxt 发布于 2018-08-15 15:43:41

Python中提供了一个简洁的语法, 使用符号@来修改一个函数或类, 下面以函数为例子:

@foo
def bar():
    pass

它其实只是

def bar():
    pass

bar = foo(bar)

的语法糖而已, 并没有任何复杂的地方. 这里的foo被称作装饰器, bar就是被装饰的函数. 可以看出,foo应该是一个函数, 他以被装饰函数为参数, 返回值将替换原本的被装饰函数(bar).

所以你甚至可以写出下面的装饰器, 将任何函数变为一个整数42:

def foo(func):
    return 42

@foo
def hello():
    print("hello")

# hello 经过装饰已经变为整数, 尝试调用将出错, 下面将直接打印出它的值,输出"42"
print(hello)

应该没有人这样使用装饰器,但这个例子可以清楚地看到发生了什么. 另一个简单的例子, 修改函数名字:

def name_to_AAA(func):
    func.__name__ = "AAA"
    return func

@name_to_AAA
def hello():
    pass

print(hello.__name__) # 输出"AAA"

现在我们尝试写一个装饰器, 在函数的运行前后分别加入一些输出, 修改函数本身已经无法达到目的, 因此我们新建一个函数并返回:

def logit(func):
    def new_func(*arg,**kwarg): 
        print("will call %s" % func.__name__)
        func(*arg,**kwarg)
        print("end call %s" % func.__name__)
    return new_func

@logit
def hello():
    print("hello, world")

hello()

程序输出:

will call hello
hello, world
end call hello

装饰器也可以带有参数, 形如:

@foo(a,b)
def bar():
    pass

很明显, 它应该是:

bar = foo(a,b)(bar)

的语法糖. 这意味着foo(a,b)应该返回一个装饰器, 那么我们可以把无参数装饰器整个放入foo(a,b)中, 返回即可. 比如写一个可以定制函数前后输出的装饰器:

def logit2(mess1,mess2):
    def logit(func): #无参装饰器放入并返回
        def new_func(*arg,**kwarg):
            print(mess1+" call %s" % func.__name__)
            func(*arg,**kwarg)
            print(mess2+" call %s" % func.__name__)
        return new_func
    return logit 

@logit2("before fun", "after fun")
def hello():
    print("hello, world")

hello()

程序输出:

before fun call hello
hello, world
after fun call hello

同一个函数也可以有多个装饰器装饰, 形如:

@foo
@bar
def baz():
pass

也很明显,他应该是

baz = foo( bar( barz ) )

的语法糖, 这意味着foo修饰的并不是原始的baz, 而是bar修饰的结果. 举个小例子:

def add(func):
    def add1():
        return func()+1
    return add1

@add
@add
@add
def one():
    return 1

print(one())  #程序会输出"4"

最后有一点要注意的是,被装饰后的函数有可能已经不是原始函数了, 它的函数签名变成了装饰器中的函数, 比如最后一个例子,one.__name__实际上是add1.而且像__doc__ 等等重要属性也丢失了. 如果需要保留原函数属性, 标准库提供了 functools.wraps 装饰器来做这件事.

def add(func):
    @functools.wraps(func)
    def add1():
        return func()+1
    return add1

类的装饰器原理也是相同的, 不多赘述.