ZetCode

Python 装饰器

上次修改时间:2024 年 3 月 21 日

在本文中,我们将展示如何在 Python 中使用装饰器函数。

Python 函数是一等公民。这意味着函数与 Python 中的其他对象具有同等的地位。函数可以赋值给变量、存储在集合中、动态创建和删除,或者作为参数传递。

嵌套函数,也称为内部函数,是在另一个函数内部定义的函数。

Python 装饰器 扩展和修改了可调用对象的行为,而无需修改可调用对象本身。装饰器是装饰(或包装)其他函数并在包装函数运行前后执行代码的函数。

Python 装饰器通常用于日志记录、身份验证和授权、计时和缓存。

简单示例

在下一个示例中,我们创建一个简单的装饰器示例。

main.py
#!/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 允许使用 @ 符号来标记要用装饰器装饰的方法。

main.py
#!/usr/bin/python

def enclose(fun):

    def wrapper():

        print("***************************")
        fun()
        print("***************************")

    return wrapper

@enclose
def myfun():
    print("myfun")

myfun()

从功能上讲,该示例与前一个示例等效。仅使用了不同的语法。

使用参数装饰函数

以下示例展示了如何装饰带有参数的函数。

main.py
#!/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')

在此代码示例中,常规函数接受一个参数。

main.py
#!/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 语法处理可变数量的参数。

修改数据

装饰器函数可以修改被装饰函数的数据。

main.py
#!/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!

多个堆叠的装饰器

可以在一个函数上应用多个装饰器。

main.py
#!/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>

计时示例

在以下示例中,我们在函数上应用计时器装饰器。

main.py
#!/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 装饰器。

main.py
#!/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__ 魔术方法。

main.py
#!/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 类中创建一个静态方法。静态方法属于一个类,并且在不创建实例的情况下被调用。

main.py
#!/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 用于定义路由。

main.py
#!/usr/bin/python

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello there!'

在该示例中,使用 Flask 的 @app.route 装饰器将 hello 函数映射到根页面。

来源

Python 语言参考

在本文中,我们使用了 Python 装饰器。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直撰写编程文章。到目前为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。我在编程教学方面拥有超过十年的经验。

列出所有 Python 教程