在 Python 中,装饰器的作用是在不改变函数或类的代码的前提下,改变函数或类的功能。在介绍装饰器之前,我们先来复习下 Python 中的函数。
def foo():
print("Hello World!")
bar = foo
bar()
output:
Hello World!
在上面的例子中,首先定义了函数 foo()
,然后将函数 foo
赋给变量 bar
,这样我们便可以通过 bar()
来调用函数。
def foo():
print("I am foo")
def bar(func):
print("I am bar")
func()
bar(foo)
output:
I am bar
I am foo
在上面的例子中,函数 foo()
作为参数传入到 bar()
函数,然后再 bar()
函数内部通过参数来调用函数 foo()
。
def parent():
print("I am parent")
def foo_child():
print("I am foo")
def bar_child():
print("I am bar")
foo_child()
bar_child()
parent()
output:
I am parent
I am foo
I am bar
在上面的例子中,函数 foo_child()
和 bar_child()
嵌套在函数 parent()
的内部。嵌套在函数内部的函数只能在函数内部调用,从外部调用会报错。例如:
def parent():
print("I am parent")
def foo_child():
print("I am foo")
def bar_child():
print("I am bar")
foo_child()
bar_child()
foo_child()
output:
Traceback (most recent call last):
File "/Users/weisong/PycharmProjects/pythonProject/greet.py", line 14, in <module>
foo_child()
NameError: name 'foo_child' is not defined
def parent(name):
def foo_child():
print("I am foo")
def bar_child():
print("I am bar")
if name == 'foo':
return foo_child
else:
return bar_child
foo = parent("foo")
bar = parent("bar")
foo()
bar()
output:
I am foo
I am bar
在上面的例子中,parent()
函数根据传入的参数返回函数 foo_child
或者 bar_child
,得到 parent()
函数的返回值之后,我们可以使用返回值调用相应的函数。
介绍完函数的各种用法后,我们来看一个简单的装饰器(decorator)。
def my_decorator(func):
def wrapper():
print("Do something before function is called")
func()
print("Do something after function is called")
return wrapper
def foo():
print("I am foo")
foo = my_decorator(foo)
foo()
output:
Do something before function is called
I am foo
Do something after function is called
在上面的例子中,语句 foo = my_decorator(foo)
将 my_decorator
函数的返回值 wrapper
函数赋给 foo
,这样我们便可以使用 foo
来调用 wrapper
函数,在 wrapper
函数中包含着作为参数传入的函数的调用,所以会输出 I am foo
。
上面使用装饰器的方式有点笨重,Python 提供了一种更简单的方式来使用装饰器,这便是使用 @ 符号,我们称之为语法糖。使用 @ 符号,将上面的装饰器修改如下:
def my_decorator(func):
def wrapper():
print("Do something before function is called")
func()
print("Do something after function is called")
return wrapper
@my_decorator
def foo():
print("I am foo")
foo()
output:
Do something before function is called
I am foo
Do something after function is called
@my_decorator
相当于前面的 foo = my_decorator(foo)
,使用方式较前面简洁了许多。另外,如果程序中有其他的函数需要类似的装饰,只需要在它们的上方加上 @my_decorator
就可以了,大大提高了程序的可复用性和可读性。
def my_decorator(func):
def wrapper(greet):
print("Do something before function is called")
func(greet)
print("Do something after function is called")
return wrapper
@my_decorator
def foo(greet):
print(f"{greet}, I am foo")
foo("Hello")
output:
Do something before function is called
Hello, I am foo
Do something after function is called
在为函数 foo()
加了参数 greet
之后,调用函数 foo()
时便可以传入参数。当然装饰器也要做相应的修改,为函数 wrapper
也添加了参数 greet
。但是上述加参数的方式有一个缺点,当使用这个装饰器来装饰一个不带参数的函数时,调用便会发生错误。例如:
def my_decorator(func):
def wrapper(greet):
print("Do something before function is called")
func(greet)
print("Do something after function is called")
return wrapper
@my_decorator
def foo():
print("I am foo")
foo()
output:
Traceback (most recent call last):
File "/Users/weisong/PycharmProjects/pythonProject/greet.py", line 14, in <module>
foo()
TypeError: wrapper() missing 1 required positional argument: 'greet'
为了兼容带参数的调用和不带参数的调用,可以将装饰器函数修改如下:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Do something before function is called")
func(*args, **kwargs)
print("Do something after function is called")
return wrapper
@my_decorator
def foo():
print("I am foo")
@my_decorator
def bar(greet):
print(f"{greet}, I am bar")
foo()
bar("Hello")
output:
Do something before function is called
I am foo
Do something after function is called
Do something before function is called
Hello, I am bar
Do something after function is called
def repeat(num):
def my_decorator(func):
def wrapper(*args, **kwargs):
print("do something before function is called")
for i in range(num):
func(*args, **kwargs)
print("do something after function is called")
return wrapper
return my_decorator
@repeat(2)
def foo():
print("I am foo")
@repeat(3)
def bar(greet):
print(f"{greet}, I am bar")
foo()
bar("Hello")
output:
Do something before function is called
I am foo
I am foo
Do something after function is called
Do something before function is called
Hello, I am bar
Hello, I am bar
Hello, I am bar
Do something after function is called
上面的例子中,使用装饰器时传入了参数 num
,用来表示内部函数执行的次数。
还是使用前面的例子,我们打出函数 foo
和 bar()
的元信息,看看被装饰器修饰后还是不是它们自己。
def repeat(num):
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Do something before function is called")
for i in range(num):
func(*args, **kwargs)
print("Do something after function is called")
return wrapper
return my_decorator
@repeat(2)
def foo():
print("I am foo")
@repeat(3)
def bar(greet):
print(f"{greet}, I am bar")
print(foo.__name__)
help(foo)
print("**************************")
print(bar.__name__)
help(bar)
output:
wrapper
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
**************************
wrapper
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
从输出结果可以看出,在被装饰器修饰后,被修饰函数的元信息改变了。变成了 wrapper()
函数。可以使用内置的装饰器@functools.wrap
来解决这个问题,它会保留被修饰函数的元信息。
import functools
def repeat(num):
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("Do something before function is called")
for i in range(num):
func(*args, **kwargs)
print("Do something after function is called")
return wrapper
return my_decorator
@repeat(2)
def foo():
print("I am foo")
@repeat(3)
def bar(greet):
print(f"{greet}, I am bar")
print(foo.__name__)
help(foo)
print("**************************")
print(bar.__name__)
help(bar)
output:
foo
Help on function foo in module __main__:
foo()
**************************
bar
Help on function bar in module __main__:
bar(greet)
在使用@functools.wrap
之后,被修饰函数保留了原有的元信息。
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
start_time = time.perf_counter()
func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
return wrapper_timer
@timer
def waste_some_time(num_times):
for _ in range(num_times):
sum([i**2 for i in range(10000)])
waste_some_time(1)
waste_some_time(1000)
output:
Finished 'waste_some_time' in 0.0037 secs
Finished 'waste_some_time' in 3.3343 secs
import functools
import time
def slow_down(func):
@functools.wraps(func)
def wrapper_slow_down(*args, **kwargs):
time.sleep(1)
return func(*args, **kwargs)
return wrapper_slow_down
@slow_down
def countdown(from_number):
if from_number < 1:
print("Liftoff!")
else:
print(from_number)
countdown(from_number - 1)
countdown(6)
output:
6
5
4
3
2
1
Liftoff!
import functools
def debug(func):
"""Print the function signature and return value"""
@functools.wraps(func)
def wrapper_debug(*args, **kwargs):
args_repr = [repr(a) for a in args]
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
signature = ", ".join(args_repr + kwargs_repr)
print(f"Calling {func.__name__}({signature})")
value = func(*args, **kwargs)
print(f"{func.__name__!r} returned {value!r}")
return value
return wrapper_debug
@debug
def make_greeting(name, age=None):
if age is None:
return f"Howdy {name}!"
else:
return f"Whoa {name}! {age} already, you are growing up!"
make_greeting("foo")
make_greeting("foo", 18)
output:
Calling make_greeting('foo')
'make_greeting' returned 'Howdy foo!'
Calling make_greeting('foo', 18)
'make_greeting' returned 'Whoa foo! 18 already, you are growing up!'
本文讲述了装饰器的原理以及用法,装饰器的存在大大提高了代码的可复用性以及简洁性。
封面:该图片由jplenio在Pixabay上发布
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。