Python中的装饰器用来给Python中的函数添加一些辅助功能。比如我们可以把【输出日志】这个辅助功能写到一个装饰器里。只要我们在某个函数A之前添加了这个【输出日志】的装饰器,那么执行函数A的时候,就会自动地把关于函数A的日志输出出来。
这篇文章我们会一起学习Python装饰器的原理和使用方法,再进入装饰器的正题之前,我们先来回顾一些Python中的相关知识。
本文纲要预备知识将一个函数赋给一个变量
在函数中定义函数
在函数中返回函数
将函数作为参数传给另一个函数
装饰器的基本说明创建我们第一个装饰器
使用@符号来装饰一个函数
使用场景介绍(日志)
装饰器的拓展说明带参数的装饰器
装饰器类
在同一个函数上定义多个装饰器
参考资料
预备知识
将一个函数赋给一个变量
Python是一门面向对象的语言。在Python中,我们可以把函数当成对象来赋给某个变量。
# 定义一个函数hi
def hi(name="joey"):
return "hi " + name
# 将函数hi赋值给一个变量greet。
# 我们这里没有在使用小括号,因为我们并不是在调用hi函数
# 而是在将它放在greet变量里头。
greet = hi
print(greet())
# output:hi joey
在函数中定义函数
在Python中,我们可以在一个函数中定义另一个函数。
# 在函数中定义函数
def hi():
print("hi function begin.")
def greet2():
return "greet2 function."
def welcome():
return "welcome function."
print(greet2())
print(welcome())
print("hi function end.")
# 执行hi函数。
hi()
# output:
# hi function begin.
# greet2 function.
# welcome function.
# hi function end.
# 函数内的函数不能被直接访问到。
# 执行下面的方法时系统会报错。
greet2()
在函数中返回函数
在Python中,我们可以把函数当成返回值来返回。
# 我们将一个函数A放在另一个函数B当中,
# 并且把函数A作为函数B的返回值返回。
def hi(name="joey"):
# 定义greet子函数。
def greet():
return "greet function"
# 定义welcome子函数。
def welcome():
return "welcome function"
# 在if/else语句中,我们返回greet和welcome函数,
# 而不是greet()和welcome()。
# 因为加上小括号之后,这个函数就会被执行;
# 而不加小括号的时候,函数本身就会被传递出去。
if name == "joey":
return greet
else:
return welcome
将函数作为参数传给另一个函数
在Python中,我们还可以把函数作为一个另一个函数的参数来使用。
# 创建一个普通的函数hi。
def hi():
return "hi joey!"
# 创建一个以函数作为参数的函数doSomethingBeforeFunc。
def doSomethingBeforeFunc(func):
print("Do something before func.")
print(func())
# 把hi函数作为参数传递给doSomethingBeforeFunc函数。
doSomethingBeforeFunc(hi)
# output:
# Do something before func.
# hi joey!
装饰器中大概用到的知识点就是上面这些了。下面我们开始正式学习装饰器的内容。
装饰器的基本说明
创建我们第一个装饰器
Python中的装饰器主要用来补充或者优化某个函数的功能,也就是在某个函数执行之前或者执行之后加上一些操作。比如下面这个例子:
# 创建一个装饰器a_new_decorator,
# 并把某个要被装饰的函数当成参数(a_func)传递到这个装饰器里。
def a_new_decorator(a_func):
def wrapTheFunction():
# 在原函数执行之前补上一些操作。
print("Do something before a_func().")
# 执行原函数。
a_func()
# 在原函数执行之后加上一些操作。
print("Do something after a_func().")
# 把被装饰过得函数作为返回值来返回。
return wrapTheFunction
接下来我们创建一个要被装饰的函数,并且使用上面的装饰器来装饰这个函数。
# 创建一个要被装饰的函数。
def a_function_requiring_decoration():
print("I am the function which needs some decoration.")
# 将上面的函数用a_new_decorator这个函数装饰起来。
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
# 执行被装饰之后的函数
a_function_requiring_decoration()
# output:
# Do something before a_func().
# I am the function which needs some decoration.
# Do something after a_func().
使用@符号来装饰一个函数
在上面的例子中,我们通过下面的这句代码来装饰函数。
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
但是这样的写法稍微复杂了一些,在实际应用中,我们通常通过@符号来装饰函数:
# 使用@符号来装饰一个函数
@a_new_decorator
def a_function_requiring_decoration():
print("I am the function which needs some decoration too.")
# 上面的写法等效于下面的3行代码:
# def a_function_requiring_decoration():
# print("I am the function which needs some decoration.")
# a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
# 执行被装饰的函数
a_function_requiring_decoration()
# output:
# Do something before a_func().
# I am the function which needs some decoration too.# Do something after a_func().
可以看到,通过使用@符号,我们简化了装饰器的写法。
但是现在还有一个小问题,函数被装饰器装饰了之后,原函数的名字改变了:
# 我们希望的函数名是a_function_requiring_decoration,
# 但是现在却变成了wrapTheFunction.
print(a_function_requiring_decoration.__name__)
# output:
# wrapTheFunction
此时,我们可以通过functools.wraps这个函数来解决函数名被改变的问题:
# 可以通过functools.wraps这个函数来解决函数名被改变的问题
from functools import wraps
def a_new_decorator(a_func):
# 调用wraps方法,把它作为一个装饰器来使用。
@wraps(a_func)
def wrapTheFunction():
print("Do something before a_func()")
a_func()
print("Do something after a_func()")
return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
print("I am the function which needs some decoration.")
# 此时再输出函数名,我们可以看到它变成了原来的名字。
print(a_function_requiring_decoration.__name__)
# output:
# a_function_requiring_decoration
使用场景介绍(日志)
装饰器可以简化代码,优化程序的处理过程。它可以应用在很多方面,这里简单介绍如何使用装饰器来生成日志。
from functools import wraps
# 创建一个日志装饰器
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + "was called.")
return func(*args, **kwargs)
return with_logging
# 生成一个被装饰的加法计算函数。
@logit
def addition_func(x):
return x + x
# 执行被装饰的加法计算函数。
result = addition_func(4)
print("result:{}".format(result))
# output:
# addition_funcwas called.
# result:8
装饰器的拓展说明
带参数的装饰器
我们可以给装饰器的方法里加上参数。带参数的装饰器会比普通的装饰器多一层函数进行包裹,多出来的这一层函数用来传递参数。
我们改写一下上面日志的例子,使得输出的日志能够保存到日志文件当中。
from functools import wraps
# 创建一个带参数logfile的装饰器logit。
# 参数说明:
# logfile: 指定保存日志的文件。
def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called."
print(log_string)
# 打开日志文件,并写入内容
# ‘a’: 打开一个文件用于追加。
# 如果该文件已存在,新的内容将会被写入到已有内容之后。
# 如果该文件不存在,创建新文件进行写入。
with open(logfile, 'a') as opened_file:
opened_file.write(log_string + '\n')
return func(*args, **kwargs)
return wrapped_function
return logging_decorator
# 创建一个被日志装饰器装饰的函数。
@logit()
def myfunc1():
pass
# 执行下面的函数之后,可以看到当前目录下生成了一个out.log文件。
myfunc1()
# output:
# myfunc1 was called.
装饰器类
上面的例子中,我们把装饰器写成了函数的形式。事实上,我们还可以把装饰器写成类的形式。
把装饰器写成类的形式之后,我们可以很方便地继承这些装饰器类,从而生成功能更多更丰富的装饰器子类。
比如,我们可以把上面的日志装饰器改写成如下的样子:
from functools import wraps
# 创建一个可以生成日志文件的装饰器类。
class logit(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile
def __call__(self, func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打开logfile并写入
with open(self.logfile, 'a') as opened_file:
# 现在将日志打到指定的文件
opened_file.write(log_string + '\n')
# 现在,发送一个通知
self.notify()
return func(*args, **kwargs)
return wrapped_function
def notify(self):
print("Notify")
pass
# 创建一个被日志装饰器类所装饰的函数。
@logit()
def myfunc3():
pass
# 执行被装饰的函数。
myfunc3()
# output:
# myfunc3 was called.
# Notify.
我们可以继承上面的日志装饰器类,生成一个可以发送邮件的装饰器子类。
# 给上面的logit创建一个子类
class email_logit(logit):
def __init__(self, email='admin@myproject.com', *args, **kwargs):
self.email = email
super(email_logit, self).__init__(*args, **kwargs)
def notify(self):
# 这里可以用来发送一封邮件
pass
# 生成一个被日志装饰器子类装饰的函数。
@email_logit()
def myfunc5():
pass
myfunc5()
# output:
# myfun5 was called.
在同一个函数上定义多个装饰器
我们可以给同一个函数加上多个装饰器,比如下面的样子:
# 一个函数可以同时定义多个装饰器,比如:
@a
@b
@c
def f():
pass
# 它的执行顺序是从里到外,
# 最先调用最里层的装饰器,
# 最后调用最外层的装饰器,它等效于
# f = a(b(c(f)))
以上就是本文的全部内容。
参考资料菜鸟教程-Python函数装饰器本篇文章中出现的代码