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 教程。