Python 函数装饰器详解

Python 中何时使用装饰器

在 Python 中,当你需要修改函数的行为而又不想修改函数本身时,你可以使用装饰器。一些常见的例子包括添加日志、测试性能、执行缓存、验证权限等。

你还可以在需要对多个函数运行相同代码时使用装饰器,这样可以避免重复编写代码。

创建 Python 装饰器的基本组成部分

要更好地理解装饰器的工作原理,首先要了解一些概念。

  1. 函数是一个对象。因此,可以将函数赋给一个变量,并可以通过该变量访问该函数。
def my_function():

    print('我是一个函数。')

# 将函数分配给一个变量,不要使用括号。我们不想执行该函数。

description = my_function
# 从分配给它的变量中访问函数。

print(description())

# 输出

'我是一个函数。'

  1. 函数可以嵌套在另一个函数内部。
def outer_function():

    def inner_function():

        print('我来自内部函数。')

    # 在外部函数内执行内部函数。
    inner_function()

outer_function()

# 输出

我来自内部函数。

请注意,inner_functionouter_function 外部不可用。如果我尝试在 outer_function 外部执行 inner_function,将收到 NameError 异常。

inner_function()

Traceback (most recent call last):
  File "/tmp/my_script.py", line 9, in <module>
    inner_function()
NameError: name 'inner_function' is not defined
  1. 由于函数可以嵌套在另一个函数内部,它也可以被返回。
def outer_function():
    '''分配任务给学生'''

    task = '阅读 Python 书籍第 3 章。'
    def inner_function():
        print(task)
    return inner_function

homework = outer_function()

homework()

# 输出

'阅读 Python 书籍第 5 章。'

  1. 函数可以作为参数传递给另一个函数。
def friendly_reminder(func):
    '''对丈夫的提醒'''

    func()
    print('别忘了带上你的钱包!')

def action():

    print('我要去商店买些好东西。')

# 使用 action 函数作为参数调用 friendly_reminder 函数。

friendly_reminder(action)

# 输出

我要去商店买些好东西。
别忘了带上你的钱包!

如何创建 Python 装饰器

要在 Python 中创建一个装饰器函数,我创建一个接受函数作为参数的外部函数。还有一个内部函数,它包装在装饰的函数周围。

以下是基本 Python 装饰器的语法:

def my_decorator_func(func):

    def wrapper_func():
        # 在函数之前做一些事情。
        func()
        # 在函数之后做一些事情。
    return wrapper_func

要使用装饰器,在函数上方直接放置装饰器名称,如下所示。你可以通过在装饰器函数前加上 @ 符号来使用装饰器。

@my_decorator_func
def my_func():

    pass

这是一个简单的示例。这个装饰器记录函数执行的日期和时间:

from datetime import datetime

def log_datetime(func):
    '''记录函数的日期和时间'''

    def wrapper():
        print(f'函数: {func.__name__}\n执行时间: {datetime.today().strftime("%Y-%m-%d %H:%M:%S")}')
        print(f'{"-"*30}')
        func()
    return wrapper

@log_datetime
def daily_backup():

    print('每日备份任务已完成。')

daily_backup()

# 输出

每日备份任务已完成。
函数: daily_backup
执行时间: 2021-06-06 06:54:14
------------------------------

如何向 Python 装饰器添加参数

装饰器可以接受参数。要向装饰器添加参数,我在内部函数中加入 *args**kwargs

  • *args 将接受任意类型的无限数量参数,如 10True'Brandon'
  • **kwargs 将接受无限数量的关键字参数,如 count=99is_authenticated=Truename='Brandon'

以下是带有参数的装饰器示例:

def my_decorator_func(func):

    def wrapper_func(*args, **kwargs):
        # 在函数之前做一些事情。
        func(*args, **kwargs)
        # 在函数之后做一些事情。
    return wrapper_func

@my_decorator_func
def my_func(my_arg):
    '''函数的示例文档字符串'''

    pass

装饰器隐藏了它装饰的函数。如果我检查 __name____doc__ 方法,会得到意外的结果。

print(my_func.__name__)
print(my_func.__doc__)

# 输出

wrapper_func
None

为了解决这个问题,我将使用 functoolsfunctools.wraps 将更新装饰器以包含装饰的函数属性。

from functools import wraps

def my_decorator_func(func):

    @wraps(func)
    def wrapper_func(*args, **kwargs):
        func(*args, **kwargs)
    return wrapper_func

@my_decorator_func
def my_func(my_args):
    '''函数的示例文档字符串'''

    pass

现在我得到了预期的输出。

print(my_func.__name__)
print(my_func.__doc__)

# 输出

my_func
函数的示例文档字符串

Python 装饰器实例

我创建了一个装饰器,用于测量函数的内存和速度性能

我们将使用该装饰器测试使用四种方法生成列表的性能:range、列表解析、append 和连接。

from functools import wraps
import tracemalloc
from time import perf_counter

def measure_performance(func):
    '''测量函数的性能'''

    @wraps(func)
    def wrapper(*args, **kwargs):
        tracemalloc.start()
        start_time = perf_counter()
        func(*args, **kwargs)
        current, peak = tracemalloc.get_traced_memory()
        finish_time = perf_counter()
        print(f'函数: {func.__name__}')
        print(f'方法: {func.__doc__}')
        print(f'内存使用量:\t\t {current / 10**6:.6f} MB \n'
              f'峰值内存使用量:\t {peak / 10**6:.6f} MB ')
        print(f'经过的时间(秒): {finish_time - start_time:.6f}')
        print(f'{"-"*40}')
        tracemalloc.stop()
    return wrapper

@measure_performance
def make_list1():
    '''Range'''

    my_list = list(range(100000))

@measure_performance
def make_list2():
    '''列表解析'''

    my_list = [l for l in range(100000)]

@measure_performance
def make_list3():
    '''Append'''

    my_list = []
    for item in range(100000):
        my_list.append(item)

@measure_performance
def make_list4():
    '''连接'''

    my_list = []
    for item in range(100000):
        my_list = my_list + [item]

print(make_list1())
print(make_list2())
print(make_list3())
print(make_list4())

# 输出

函数: make_list1
方法: Range
内存使用量:		        0.000072 MB
峰值内存使用量:	        3.693040 MB
经过的时间(秒):    0.049359
----------------------------------------

函数: make_list2
方法: 列表解析
内存使用量:		        0.000856 MB
峰值内存使用量:	        3.618244 MB
经过的时间(秒):    0.052338
----------------------------------------

函数: make_list3
方法: Append
内存使用量:		        0.000448 MB
峰值内存使用量:	        3.617692 MB
经过的时间(秒):    0.060719
----------------------------------------

函数: make_list4
方法: 连接
内存使用量:		        0.000440 MB
峰值内存使用量:	        4.393292 MB
经过的时间(秒):    61.649138
----------------------------------------

你也可以在类中使用装饰器。让我们看看如何在 Python 类中使用装饰器。

在此示例中,请注意没有涉及 @ 字符。使用 __call__ 方法时,装饰器在创建类的实例时执行。

这个类会跟踪查询 API 的函数被运行的次数。一旦达到限制,装饰器将阻止函数的执行。

import requests

class LimitQuery:

    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.limit = args[0]
        if self.count < self.limit:
            self.count += 1
            return self.func(*args, **kwargs)
        else:
            print(f'没有剩余查询。已使用所有 {self.count} 次查询。')
            return

@LimitQuery
def get_coin_price(limit):
    '''查看比特币价格指数(BPI)'''

    url = requests.get('https://api.coindesk.com/v1/bpi/currentprice.json')

    if url.status_code == 200:
        text = url.json()
        return f"${float(text['bpi']['USD']['rate_float']):.2f}"

print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))

# 输出

$35968.25
$35896.55
$34368.14
$35962.27
$34058.26
没有剩余查询。已使用所有 5 次查询。

这个类将跟踪类的状态。

结论

在本文中,我讲解了如何将函数分配给变量、嵌套函数、返回函数和将函数作为参数传递给另一个函数。

我还向你展示了如何创建和使用 Python 装饰器,以及一些真实世界的例子。现在我希望你能够在你的项目中添加装饰器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值