跳到主要内容

Python装饰器

· 阅读需 5 分钟
Skyone
科技爱好者

装饰器(Decorators)是 Python 的一个重要部分。举一个不太恰当的比方,装饰器是一个函数,它以函数为参数,先执行一些操作,再调用作为参数的函数,然后再执行以下操作,例如下面的函数:

def my_decorator(func):
print("Do something before call func.")
func() # 调用func()
print("Do something after call func.")

没错,就像C语言里的回调函数一样

可以这样使用它:

def greet():
print("Hello!")

my_decorator(greed) # 注意这里不是调用 greed
"""输出
Do something before call func.
Hello
Do something after call func.
"""

然而,python作为一个优雅的语言,使用了特殊语法简化了上面的操作,下面的例子和上面的是等效的:

def my_decorator(func):
print("Do something before call func.")
func() # 调用func()
print("Do something after call func.")

@my_decorator # 也不是调用
def greet():
print("Hello!")

greet
"""输出
Do something before call func.
Hello
Do something after call func.
"""

装饰器的使用

适配带有参数的函数

前面的最简单的装饰器不能修饰带有参数的函数,而且修饰后的函数不能做参数之类的(想一想为什么)

其实也很容易解决,既然函数可以作为另一个函数的参数,那么它可不可以作为返回值呢?

当然可以!这是因为python一切皆对象,函数和变量究其本质都是对象我怎么联想到了Linux一切皆文件@_@

现在我们将其修改一下:

def a_new_decorator(func):
# python 可以在函数里再定义函数,即嵌套函数
def wrapped(*args, **kwargs):
print("Do something before call func.")
func(*args, **kwargs)
print("Do something after call func.")

return wrapped


@a_new_decorator
def greet():
print("Hello!")


greet()

保持函数名

现在我们在上面的代码后面再加一行:

print(greet.__name__)
"""输出
wrapped
"""

哦不,这不是我们想要的结果,函数名被装饰器改写了!

幸好functiontoolswarps装饰器可以解决这个问题,再改!

from functools import wraps


def a_new_decorator(func):
@wraps(func)
def wrapped(*args, **kwargs):
print("Do something before call func.")
func(*args, **kwargs)
print("Do something after call func.")

return wrapped


@a_new_decorator
def greet():
print("Hello!")


print(greet.__name__)
"""输出
greet
"""

带参数的装饰器

上例中wraps也是装饰器,但他为什么要加括号调用,还可以添加参数?

先看看这个:

def a():
def b():
def c():
def d():
return 1

return d

return c

return b


response = a()()()()

# response = 1

这样来看就很简单了,就是再嵌套一个函数嘛。。

有了这个思路,再改!

from functools import wraps

def final_decorator(output_filename="greet.txt"):
def a_new_decorator(func):
@wraps(func)
def wrapped(*args, **kwargs):
with open(output_filename, "a") as file:
greet_str = func(*args, **kwargs)
file.write(greet_str)
return greet_str

return wrapped

return a_new_decorator

@final_decorator("233.txt")
def greet(name):
return "Hello " + name

print(greet("skyone"))
"""输出
Hello skyone
"""
"""文件`123.txt`
Hello skyone
"""

花样写日志

函数做装饰器

from functools import wraps


def logger(logfile="out.log", callback=None):
def logger_decorator(func):
@wraps(func)
def wrapped_func(*args, **kwargs):
log_string = "[logger] function <" + func.__name__ + "> was called"
print(log_string)
with open(logfile, "a", encoding="utf-8") as file:
file.write(log_string + "\n")
if callback is not None:
callback()
return func(*args, *kwargs)
return wrapped_func
return logger_decorator


@logger()
def a():
return 3


print(a())

类做装饰器

from functools import wraps


class Logger:
def __init__(self, logfile="out.log", callback=None):
self.logfile = logfile
self.callback = callback

def __call__(self, func):
@wraps(func)
def wrapped_func(*args, **kwargs):
log_string = "[logger] function <" + func.__name__ + "> was called"
print(log_string)
with open(self.logfile, "a", encoding="utf-8") as file:
file.write(log_string + "\n")
if self.callback is not None:
self.callback()
self.notify()
return func(*args, **kwargs)
return wrapped_func

def notify(self):
pass


@logger()
def a():
return 3


print(a())

这样做的好处是可以改写,而且比嵌套函数的方式更加整洁,下面的例子来自菜鸟教程

创建一个子类:

class email_logger(Logger):
'''
一个logit的实现版本,可以在函数调用时发送email给管理员
'''
def __init__(self, email='admin@myproject.com', *args, **kwargs):
self.email = email
super(email_logger, self).__init__(*args, **kwargs)

def notify(self):
# 发送Email的实现···
pass