带参数的Python装饰器让你的代码更优雅

引言

在上一篇文章中,我们介绍了 Python 装饰器的基本概念及其简单用法。

前面讲到的装饰器都是不带参数的装饰器,在需要对装饰器做一些针对性的处理的时候就不太适用了,这个时候需要对装饰器传入一些参数,根据传入的参数进行不同的处理。

带参数装饰器在实际开发中能够灵活地调整函数行为,广泛应用于日志记录、权限验证和缓存等场景。。

如何定义带参数的装饰器

带参数的装饰器的结构稍微复杂一些。我们要定义一个外层函数来接收装饰器的参数,并在这个外层函数中定义实际的装饰器函数,相当于将之前的装饰器用一个正常的函数包装了一层。下面来看一下带参数的装饰器和不带参数的装饰器的对比。

不带参数的装饰器

python 复制代码
def decorator(func):
    def wrapper():
        print(f"Decorator")
        return func()

    return wrapper

带参数的装饰器:

python 复制代码
def decorator_with_args(arg1=None, arg2=None):
    def decorator(func):
        def wrapper():
            print(f"Decorator arguments: {arg1}, {arg2}")
            return func()

        return wrapper

    return decorator

在上面带参数的装饰器中,decorator_with_args 是外层装饰器函数,它接收两个参数 arg1arg2。内部的 decorator 函数是实际的装饰器,而 wrapper 函数则是用来包装被装饰的函数。

如何使用带参数的装饰器

python 复制代码
@decorator_with_args("Hello", "World")
def greet():
    print(f"Greeting name")

greet()

输出:

yaml 复制代码
Decorator arguments: Hello, World
Greeting name

在这个例子中,greet 函数被 decorator_with_args 装饰,传入了两个参数。装饰器在调用 greet 函数之前,先打印了装饰器的参数。

注意:带参数的装饰器有两个可选参数,可以选择不传。但需要注意的是,@decorator_with_args() 不能简写为 @decorator_with_args,因为 decorator_with_args 本质上是一个函数,必须调用后才能生效。

两种装饰器用法区别

使用不带参数的装饰器时,用法如下,要注意区别。

python 复制代码
def decorator(func):
    def wrapper():
        print(f"Decorator")
        return func()

    return wrapper


@decorator
def greet():
    print(f"Greeting name")


greet()

处理可变参数

在很多时候,我们会遇到被装饰的函数有参数的情况,这时要怎么处理呢。

为了解决这个问题,我们可以使用 *args**kwargs 来接收函数的所有参数。

装饰器处理可变参数

python 复制代码
def func_decorator(decorator_arg):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"Decorator argument: {decorator_arg}")
            print(f"Function arguments: {args}, {kwargs}")
            return func(*args, **kwargs)

        return wrapper

    return decorator


@func_decorator("Test")
def add(*args):
    return sum(args)


result = add(1, 2, 3, 4)
print(f"Result: {result}")

输出:

yaml 复制代码
Decorator argument: Test
Function arguments: (1, 2, 3, 4), {}
Result: 10

在这个示例中,func_decorator 装饰器可以处理在被装饰的函数有参数的情况,并且可以自适应任何参数,装饰器能够正确处理这些参数。

带参数装饰器的使用案例

自定义日志格式

在实际开发中,我们常常需要记录函数的执行日志。通过带参数的装饰器,我们可以自定义被装饰函数的参数和返回值,甚至可以做到修改被装饰函数的参数和返回值。

python 复制代码
def log_decorator(fun_args=False, fun_result=False):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if fun_args:
                print(f'func args is: {args} {kwargs}')
            if fun_result:
                result = func(*args, **kwargs)
                print(f'func result is: {result}')
                return result
            else:
                return func(*args, **kwargs)

        return wrapper

    return decorator


@log_decorator(True, True)
def multiply(x, y):
    return x * y


print(f"Result: {multiply(3, 5)}")


@log_decorator(False, False)
def multiply(x, y):
    return x * y


print(f"Result: {multiply(3, 5)}")

结果:

python 复制代码
func args is: (3, 5) {}
func result is: 15
Result: 15
---
Result: 15

在这个示例中,log_decorator 装饰器接受两个布尔型参数,用于判断是否要输出函数参数和函数返回值。

此处日志输出比较简单,仅做为示例参考,生产环境要做一些比较复杂的判断并且要将日志持久化到磁盘的日志文件中。

缓存机制

python 复制代码
import time
from functools import lru_cache


class Timer:
    def __init__(self):
        self.start = None

    def __enter__(self):
        import time
        self.start = time.time()

    def __exit__(self, type, value, traceback):
        print(f'time is:{time.time() - self.start}')


@lru_cache(maxsize=20)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)


with Timer() as timer:
    print(fibonacci(15))

with Timer() as timer2:
    def fibonacci(n):
        if n <= 1:
            return n
        return fibonacci(n - 1) + fibonacci(n - 2)


    print(fibonacci(15))

结果:

python 复制代码
14 13
13 12
12 11
11 10
10 9
9 8
8 7
7 6
6 5
5 4
4 3
3 2
2 1
1 0
610
time is:4.076957702636719e-05
--------------------
14 13
13 12
12 11
11 10
10 9
9 8
8 7
7 6
--- 此处数据量太大,省略,完整的输出大约 1000 行,大家可以自行运行查看结果
1 0
610
time is:0.002106189727783203

此处我使用了 functools 包中提供的 lru_cache 缓存装饰器,用于缓存在计算斐波那契数列时重复的计算值。

从运行结果可以看到,不使用装饰器时,函数进行了大量的重复计算,导致最后的运行耗时和使用了装饰器的结果差了好几个数量级。

总结

带参数的装饰器为我们提供了极大的灵活性,使得我们可以根据不同的需求来调整函数的行为。在实际应用中,带参数的装饰器可以用于多种场景,如自定义日志、缓存、控制函数的执行行为等等。

相关推荐
DEARM LINER13 分钟前
mysql 巧妙的索引
数据库·spring boot·后端·mysql
开心工作室_kaic3 小时前
ssm010基于ssm的新能源汽车在线租赁管理系统(论文+源码)_kaic
java·前端·spring boot·后端·汽车
代码吐槽菌3 小时前
基于SSM的汽车客运站管理系统【附源码】
java·开发语言·数据库·spring boot·后端·汽车
Ellie陈5 小时前
Java已死,大模型才是未来?
java·开发语言·前端·后端·python
wclass-zhengge6 小时前
SpringBoot篇(运维实用篇 - 临时属性)
运维·spring boot·后端
2401_857600956 小时前
商场应急管理:SpringBoot技术解决方案
java·spring boot·后端
半夏之沫7 小时前
✨最新金九银十✨大厂后端面经✨
java·后端·面试
小宇7 小时前
The valid characters are defined in RFC 7230 and RFC 3986
java·开发语言·后端·tomcat
Moment7 小时前
💯💯💯 历经四个月,我们开源了一个协同的在线代码编辑器,还支持执行 Node 项目哦!
前端·后端·github