Python 装饰器
上次修改时间:2024 年 3 月 21 日
在本文中,我们将展示如何在 Python 中使用装饰器函数。
Python 函数是一等公民。这意味着函数与 Python 中的其他对象具有同等的地位。函数可以赋值给变量、存储在集合中、动态创建和删除,或者作为参数传递。
嵌套函数,也称为内部函数,是在另一个函数内部定义的函数。
Python 装饰器 扩展和修改了可调用对象的行为,而无需修改可调用对象本身。装饰器是装饰(或包装)其他函数并在包装函数运行前后执行代码的函数。
Python 装饰器通常用于日志记录、身份验证和授权、计时和缓存。
简单示例
在下一个示例中,我们创建一个简单的装饰器示例。
#!/usr/bin/python
def enclose(fun):
def wrapper():
print("***************************")
fun()
print("***************************")
return wrapper
def myfun():
print("myfun")
enc = enclose(myfun)
enc()
enclose 函数是一个装饰器,它通过向其输出添加星号来扩展被装饰的函数。
def enclose(fun): ...
enclose 函数接受一个函数作为参数。
def wrapper():
print("***************************")
fun()
print("***************************")
return wrapper
wrapper 用星号装饰传递的函数。返回包装器函数。
def myfun():
print("myfun")
这是一个要装饰的常规函数。
enc = enclose(myfun) enc()
myfun 被传递给 enclose 函数,并在其中进行扩展。返回并调用包装器函数。
$ python main.py *************************** myfun ***************************
这是输出。装饰器在常规函数的输出前后添加星号。
使用 @ 符号
Python 允许使用 @ 符号来标记要用装饰器装饰的方法。
#!/usr/bin/python
def enclose(fun):
def wrapper():
print("***************************")
fun()
print("***************************")
return wrapper
@enclose
def myfun():
print("myfun")
myfun()
从功能上讲,该示例与前一个示例等效。仅使用了不同的语法。
使用参数装饰函数
以下示例展示了如何装饰带有参数的函数。
#!/usr/bin/python
def enclose(fun):
def wrapper(val):
print("***************************")
fun(val)
print("***************************")
return wrapper
@enclose
def myfun(val):
print(f"myfun with {val}")
myfun('falcon')
在此代码示例中,常规函数接受一个参数。
#!/usr/bin/python
def enclose(fun):
def wrapper(*args, **kwargs):
print("***************************")
fun(*args, **kwargs)
print("***************************")
return wrapper
@enclose
def myfun(name, age):
print(f'{name} is {age} years old')
myfun(name='Peter', age=32)
myfun('Roman', 29)
此示例展示了如何使用 *args, **kwargs 语法处理可变数量的参数。
修改数据
装饰器函数可以修改被装饰函数的数据。
#!/usr/bin/python
def uppercase(fun):
def wrapper():
res = fun()
modified = res.upper()
return modified
return wrapper
@uppercase
def gen_message():
return 'Hello there!'
msg = gen_message()
print(msg)
@uppercase 装饰器将返回的文本更改为大写。
def uppercase(fun):
def wrapper():
res = fun()
modified = res.upper()
return modified
return wrapper
在包装器函数内部,文本被修改并返回。
$ python main.py HELLO THERE!
多个堆叠的装饰器
可以在一个函数上应用多个装饰器。
#!/usr/bin/python
def strong(fun):
def wrapper():
return f'<strong>{fun()}</strong>'
return wrapper
def em(fun):
def wrapper():
return f'<em>{fun()}</em>'
return wrapper
@strong
@em
def message():
return 'This is some message'
print(message())
在该示例中,我们在文本上应用两个 HTML 标签。
$ python main.py <strong><em>This is some message</em></strong>
计时示例
在以下示例中,我们在函数上应用计时器装饰器。
#!/usr/bin/python
import time
import math
import sys
sys.set_int_max_str_digits(maxdigits=90000)
def timer(func):
def wrapper(*args, **kwargs):
begin = time.time()
f = func(*args, **kwargs)
end = time.time()
print("Total time taken in : ", func.__name__, end - begin)
return f
return wrapper
@timer
def factorial(num):
return math.factorial(num)
f = factorial(4580)
print(f)
该示例使用装饰器计算 factorial 函数的运行时间。
begin = time.time()
在函数运行之前,我们获取开始时间。
end = time.time()
print("Total time taken in : ", func.__name__, end - begin)
在函数运行之后,我们获取结束时间并打印差异。
functools @wraps 装饰器
应用装饰器函数后,原始函数的 __name__、__doc__ 和 __module__ 属性会丢失。这使得调试很麻烦。为了解决这个问题,我们可以使用 functool 的 @wraps 装饰器。
#!/usr/bin/python
from functools import wraps
def enclose(fun):
@wraps(fun)
def wrapper():
'''This is wrapper function'''
print("***************************")
fun()
print("***************************")
return wrapper
@enclose
def myfun():
'''this is myfun()'''
print("myfun")
myfun()
print(myfun.__name__)
print(myfun.__doc__)
在该示例中,我们在包装器函数上应用 @wraps 装饰器。保留了原始函数 (myfun) 的名称和文档字符串。
$ python main.py *************************** myfun *************************** myfun this is myfun()
类装饰器
可以使用类作为装饰器。为此,我们需要实现 __call__ 魔术方法。
#!/usr/bin/python
import functools
class CountCalls:
def __init__(self, fun):
functools.update_wrapper(self, fun)
self.fun = fun
self.num_of_calls = 0
def __call__(self, *args, **kwargs):
self.num_of_calls += 1
print(f"Call {self.num_of_calls} of {self.fun.__name__} fun")
return self.fun(*args, **kwargs)
@CountCalls
def hello():
print("Hello there!")
hello()
hello()
hello()
在该示例中,我们使用类装饰器来计算常规函数的调用次数。
def __init__(self, fun):
functools.update_wrapper(self, fun)
self.fun = fun
self.num_of_calls = 0
我们调用 update_wrapper 函数。它与 @wraps 装饰器的目的相同;即,它保留了原始函数 (__name__ 或 __doc__) 的元数据。我们保留对原始函数的引用并设置 num_of_calls 变量。
def __call__(self, *args, **kwargs):
self.num_of_calls += 1
print(f"Call {self.num_of_calls} of {self.fun.__name__} fun")
return self.fun(*args, **kwargs)
我们增加 num_of_calls 变量,打印一条消息,并调用原始函数,将可能的参数传递给它。
$ python main.py Call 1 of hello fun Hello there! Call 2 of hello fun Hello there! Call 3 of hello fun Hello there!
@staticmethod 装饰器
Python 具有 @staticmethod 内置装饰器,它在 Python 类中创建一个静态方法。静态方法属于一个类,并且在不创建实例的情况下被调用。
#!/usr/bin/python
class Math:
@staticmethod
def abs(x):
if x < 0:
return -x
return x
print(Math.abs(3))
print(Math.abs(-3))
在该示例中,我们使用 @staticmethod 装饰器创建一个静态 abs 方法。通过指定类名并使用点运算符来调用该方法:Math.abs。
Flask 装饰器
流行的 Python 框架 Flask 使用装饰器。例如,@app.route 用于定义路由。
#!/usr/bin/python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello there!'
在该示例中,使用 Flask 的 @app.route 装饰器将 hello 函数映射到根页面。
来源
在本文中,我们使用了 Python 装饰器。
作者
列出所有 Python 教程。