Python装饰器函数《最详细》
装饰器函数
函数作为参数传递
在python当中一切皆对象,函数也可以当成对象一样被调用和传递
- 函数没有使用小括号,可以将其当做一个变量传递,如:
greet = hi
- 函数使用小括号调用,则该函数会立马执行并返回结果,如
greet = hi()
def hi(name="yasoob"):return "hi " + nameprint(hi())
# output: 'hi yasoob'# 我们甚至可以将一个函数赋值给一个变量,比如
greet = hi
# 我们这里没有在使用小括号,因为我们并不是在调用hi函数
# 而是在将它放在greet变量里头。我们尝试运行下这个print(greet())
# output: 'hi yasoob'# 如果我们删掉旧的hi函数,看看会发生什么!
del hi
print(hi())
#outputs: NameErrorprint(greet())
#outputs: 'hi yasoob'
函数作为参数传递给另一个函数
def hi():return "hi yasoob!"def doSomethingBeforeHi(func):print("I am doing some boring work before executing hi()")print(func())doSomethingBeforeHi(hi) # 可以发现hi没有带小括号,不会返回结果,而是将改函数作为参数传递给函数doSomethingBeforeHi,当执行doSomethingBeforeHi()时才会执行里面的回调函数hi()
可以发现hi
没有带小括号,不会返回结果,而是将改函数作为参数传递给函数doSomethingBeforeHi
,当执行doSomethingBeforeHi()
时才会执行里面的回调函数hi()
装饰器原理
其实装饰器本质上也就是将函数作为参数传入给另一个函数。
def outerfunc(func):def innerfunc():print("before func")func()print("after func")return innerfunc()def func_a():print("func_a")result = outerfunc(func_a) # 这里func_a只是作为一个参数传递给outerfunc,并不会立马执行
result() # 等价于outerfunc(func_a)(),调用函数执行结果:
before func
func_a
after func
outerfunc(func_a)传递函数,可以使用装饰器简化
def outerfunc(func):def innerfunc():print("before func")func()print("after func")return innerfunc()@outerfunc # 由于装饰器不需要加参数,因此不用加括号
def func_a():print(func_a.__name__)func_a()
结果:
before func
innerfunc # 不符合预期
after func
但是我们调用func_a().__name__
,返回的却是innerfunc,不符合我们预期,我们希望返回被装饰函数本身的函数名。
Python提供给我们一个简单的函数来解决这个问题,那就是functools.wraps
。
我们定义装饰器时使用@wraps装饰器
,用于不改变函数本身的性质,改进如下:
def outerfunc(func):@wraps(func)def innerfunc():print("before func")func()print("after func")return innerfunc()@outerfunc # 由于装饰器不需要加参数,因此不用加括号
def func_a():print(func_a.__name__)func_a()
结果:
before func
func_a # 返回本身的函数名
after func
装饰器传入参数
如果被装饰的函数携带参数,我们需要在装饰器函数内声明*args, **kwargs
用于接收被装饰函数的入参。
普通装饰器
def outerfunc(func):@wraps(func)def innerfunc(*args, **kwargs):print("before func")func(*args, **kwargs) # 函数内部接收传入函数的参数print("after func")return innerfunc()@outerfunc
def func_a(name):print(f"Hello, {name}!")func_a("Alice")
结果:
before func
Hello, Alice
after func
由于装饰器本身就是函数本身,因此装饰器也具有接受参数的功能。
带参数的装饰器
def repeat(num_times):def outerfunc(func):@wraps(func)def innerfunc(*args, **kwargs):for _ in range(num_times):func(*args, **kwargs)print("after func")return innerfunc()@outerfunc(3) # 装饰器接收参数
def func_a(name):print(f"Hello, {name}!")func_a("Alice")
结果:
before func
Hello, Alice
Hello, Alice
Hello, Alice
after func
repeat
是一个接收参数的装饰器工厂函数,它返回一个真正的装饰器outerfunc
。
多个装饰器调用
from functools import wrapsdef outerfunc1(func):@wraps(func)def innerfunc(*args, **kwargs):print("before outerfunc1")func(*args, **kwargs) # 函数内部接收传入函数的参数print("after outerfunc1")return innerfunc # 注意这里返回的是 innerfunc,而不是调用它def outerfunc2(func):@wraps(func)def innerfunc(*args, **kwargs):print("before outerfunc2")func(*args, **kwargs) # 函数内部接收传入函数的参数print("after outerfunc2")return innerfunc # 同样,返回的是 innerfunc@outerfunc2
@outerfunc1 # 执行顺序为 outerfunc1 -> outerfunc2 -> func_a
def func_a(name):print(f"Hello, {name}!")func_a("Alice")
输出结果:
before outerfunc2
before outerfunc1
Hello, Alice!
after outerfunc1
after outerfunc2
由此可见,装饰器的执行顺序
为:
outerfunc2
的innerfunc
被调用,打印"before outerfunc2"
。- 接下来是调用
outerfunc1
的innerfunc
,这个innerfunc
执行的顺序为:- 打印
"before outerfunc1"
。 - 调用原始的
func_a
,因此打印"Hello, Alice!"
。 - 然后
outerfunc1
的代码继续执行,打印"after outerfunc1"
。
- 打印
- 最后,控制返回到
outerfunc2
的innerfunc
,执行完毕后,打印"after outerfunc2"
。
换句话说,就是最内层的装饰器优先应用于func,然后装饰器由内到外
依次被调用。因此装饰器的调用顺序其实是“自下而上”的或“自内而外”
装饰器作用
装饰器: 主要用于增强或修改被装饰函数的行为,而不需要直接修改函数本身的代码,并且调用灵活。使用装饰器能够非常清晰地表达出代码的意图(例如:进行日志记录、性能监测、权限检查等)。
使用装饰器记录日志
def log(logfile='out.log'):def logging_decorator(func):@wraps(func)def wrapped_function(*args, **kwargs):log_string = func.__name__ + " was called"print(log_string)# 打开logfile,并写入内容with open(logfile, 'a') as opened_file:# 现在将日志打到指定的logfileopened_file.write(log_string + '\n')return func(*args, **kwargs)return wrapped_functionreturn logging_decorator@log()
def myfunc1():passmyfunc1()
# Output: myfunc1 was called
# 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串