欢迎访问宙启技术站
智能推送

Python装饰器-了解Python的装饰器,以及它们如何修改函数行为

发布时间:2023-06-22 09:37:41

Python的装饰器是一个强大、简洁的语言特性,可以用来修改函数的行为。装饰器通常被用来为函数添加额外的功能,或者修改函数的参数、返回值等。本文将介绍Python的装饰器的概念,以及如何使用它们来修改函数行为。

一、为什么需要装饰器

在Python中,函数是一等公民,也就是说函数可以像变量一样被传递、赋值、返回。这样的灵活性让我们能够编写出更加优美、简洁、可读性强的代码。但是有些时候,我们可能希望为函数增加一些额外的功能,比如:

1. 记录函数执行次数、执行时间等统计信息;

2. 对函数进行缓存,避免重复计算;

3. 检查参数、返回值的合法性;

4. 对函数进行授权,只有授权用户才能执行函数等。

这时候,我们可以手动地为每个函数添加这些额外的代码。但是对于大型项目来说,这样的代码会非常复杂,不易于维护。而装饰器能够为我们提供一种更加优雅、简单的解决方案。

二、装饰器的语法

在Python中,装饰器实际上就是一个函数,它可以取一个函数作为参数,并返回一个新的函数。这个新的函数通常会包装原来的函数,以添加额外的功能。

装饰器的语法非常简单,通常是把它放在目标函数的定义前面,并用@符号表示。例如:

@decorator

def func():

    pass

这个代码实际上就等价于:

func = decorator(func)

其中decorator就是一个装饰器函数,它可以接收func函数作为参数,并返回一个新的函数。这个新的函数就包装了原来的func函数,以添加额外的功能。

三、使用装饰器实现统计信息

下面我们来看一个实际的例子,使用装饰器来实现统计信息。我们希望统计某个函数被调用的次数和执行时间。我们可以定义一个装饰器函数,接收一个函数作为参数,并返回一个新的函数。这个新的函数会在执行原来的函数之前和之后分别记录调用次数和执行时间。例如:

import time

def stats(func):

    def wrapper(*args, **kwargs):

        wrapper.count += 1

        start_time = time.time()

        result = func(*args, **kwargs)

        end_time = time.time()

        elapse_time = end_time - start_time

        wrapper.total_time += elapse_time

        print(f"{func.__name__} was called {wrapper.count} times.")

        print(f"{func.__name__} took {wrapper.total_time:.2f} seconds to run.")

        return result

    wrapper.count = 0

    wrapper.total_time = 0

    return wrapper

使用装饰器来统计函数的调用次数和执行时间非常简单,只需要在目标函数前面添加@stats装饰器即可。例如:

@stats

def fib(n):

    if n < 2:

        return n

    return fib(n-1) + fib(n-2)

result = fib(30)

print(result)

这段代码会输出以下信息:

fib was called 2692537 times.

fib took 5.35 seconds to run.

832040

这说明我们的装饰器能够正确地记录函数的调用次数和执行时间了。

四、使用装饰器来缓存函数结果

有些函数可能要花费很长时间来计算,而且结果可能会被多次使用。为了避免重复计算,我们可以使用一个装饰器来为这个函数添加缓存功能。

我们可以定义一个字典来保存函数的结果,每次调用函数时,先检查字典中是否已经有结果了。如果有,就直接返回结果,否则就计算结果,并将结果保存到字典中。例如:

def memoize(func):

    cache = {}

    def wrapper(*args):

        if args in cache:

            return cache[args]

        result = func(*args)

        cache[args] = result

        print(f"{func.__name__}({args}) = {result}")

        return result

    return wrapper

使用装饰器来实现缓存非常简单,只需要在目标函数前面添加@memoize装饰器即可。例如:

@memoize

def fib(n):

    if n < 2:

        return n

    return fib(n-1) + fib(n-2)

result = fib(30)

print(result)

这段代码会输出以下信息:

fib(0) = 0

fib(1) = 1

fib(2) = 1

fib(3) = 2

fib(4) = 3

fib(5) = 5

fib(6) = 8

fib(7) = 13

fib(8) = 21

fib(9) = 34

fib(10) = 55

fib(11) = 89

fib(12) = 144

fib(13) = 233

fib(14) = 377

fib(15) = 610

fib(16) = 987

fib(17) = 1597

fib(18) = 2584

fib(19) = 4181

fib(20) = 6765

fib(21) = 10946

fib(22) = 17711

fib(23) = 28657

fib(24) = 46368

fib(25) = 75025

fib(26) = 121393

fib(27) = 196418

fib(28) = 317811

fib(29) = 514229

fib(30) = 832040

832040

这时候我们再次调用fib(30)的时候,就不用重新计算了,而是从缓存中直接取出结果,大大提高了代码的性能。

五、使用装饰器来检查参数和返回值

在编写函数时,我们需要考虑到函数的使用者。他们可能会犯一些低级错误,比如传递错误的参数,返回错误的结果。这时候,我们可以使用一个装饰器来检查函数的参数和返回值是否合法。

下面是一个检查函数参数的装饰器,它可以检查函数的参数是否符合指定的类型和范围。例如:

def check_input_type(types):

    def decorator(func):

        def wrapper(*args, **kwargs):

            for arg, type_ in zip(args, types):

                assert isinstance(arg, type_), f"Error: {arg} is not {type_}"

            return func(*args, **kwargs)

        return wrapper

    return decorator

使用装饰器来检查参数非常简单,只需要在目标函数前面添加@check_input_type(types)装饰器即可。例如:

@check_input_type([int])

def fib(n):

    if n < 2:

        return n

    return fib(n-1) + fib(n-2)

result = fib(30)

print(result)

这段代码会输出以下信息:

832040

现在我们再次尝试调用fib('hello')这个函数,就会得到一个异常,提示我们参数类型不正确了。

六、使用装饰器来授权函数

有些函数可能只能被特定的用户使用,这时候我们可以使用一个装饰器来授权函数的调用。例如:

def check_permission(allowed_users):

    def decorator(func):

        def wrapper(*args, **kwargs):

            user = kwargs.get("user", None)

            if user not in allowed_users:

                raise Exception("You are not allowed to execute this function.")

            return func(*args, **kwargs)

        return wrapper

    return decorator

使用装饰器来授权函数非常简单,只需要在目标函数前面添加@check_permission(allowed_users)装