1. 概述
Python装饰器是Python闭包的一个强大工具,用于将函数包装在另一个函数中。它们是Python编程的一个常见概念,也是编写高级Python代码的重要技术。本练习题将提供几个实际的例子来帮助学习装饰器的使用。
2. 编写一个简单的装饰器
2.1 示例说明
首先,让我们看一个简单的示例,该示例展示了如何编写一个简单的装饰器。该示例中的装饰器将会打印出函数返回值,并返回原函数。
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
result = func()
print("Something is happening after the function is called.")
return result
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
这个程序会输出:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
那么这个程序具体发生了什么呢?
首先,我们定义了一个装饰器函数my_decorator。该函数的参数func是被装饰的函数。在my_decorator中,我们定义了一个函数wrapper,它包含了它自己的一些动作,同时也调用了原始的函数func(即装饰器的参数)。最后,wrapper返回结果。
请注意,有一个@my_decorator的行在say_hello函数之前。这是一个装饰器语法糖。它告诉Python,我们要用my_decorator来“包装”say_hello函数,即让my_decorator成为say_hello的装饰器。
最后,当我们调用say_hello函数时,它首先会运行装饰器函数中的代码,然后运行原始的函数,随后又运行一些更改后的代码。我们来看一个更复杂的示例。
2.2 代码实现
为了更好地理解装饰器的运作,我们现在来自己编写一个简单的装饰器,并使用它来修饰一个函数。该函数能够根据传递的参数计算斐波那契数列。我们将使用装饰器来测试该函数的性能。
import time
def time_it(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print("Function took ", end - start, " seconds to complete.")
return result
return wrapper
@time_it
def fib(num):
if num <= 1:
return num
return fib(num-1) + fib(num-2)
print(fib(20))
这个程序输出:
Function took 2.37274169921875e-05 seconds to complete.
6765
在这个例子中,我们定义了一个叫做time_it的装饰器函数。在time_it函数中,我们定义了一个内部函数wrapper,它接受任意数量的参数(使用了*args和**kwargs),然后测量fib函数的执行时间,并打印消息。最后,wrapper返回原始的结果。
请注意,我们使用了@time_it装饰器来修饰fib函数。因此,在程序运行fib函数之前,就会运行time_it函数,并且将fib函数作为参数传递给它。这意味着,当我们调用fib函数时,实际上会调用wrapper函数。
3. 编写一个带参数的装饰器
3.1 示例说明
现在,我们来编写一个带参数的装饰器。我们的示例将展示如何使用装饰器来限制函数的执行次数。具体而言,我们将编写一个带参数的装饰器,该装饰器将允许指定任意次数。如果函数超出了指定的执行次数,则将抛出一个异常。
def limited_execution(num):
def outer_wrapper(func):
def inner_wrapper(*args, **kwargs):
if inner_wrapper.count >= num:
raise Exception("Function {} has exceeded the maximum number of executions ({})".format(func.__name__, num))
inner_wrapper.count += 1
return func(*args, **kwargs)
inner_wrapper.count = 0
return inner_wrapper
return outer_wrapper
@limited_execution(3)
def factorial(num):
if num == 1:
return 1
return num * factorial(num-1)
print(factorial(3))
print(factorial(4))
这个程序输出:
6
24
在这个例子中,我们定义了一个装饰器函数limited_execution。该函数接受一个参数num,它代表了函数能够执行的最大次数。然后,我们定义outer_wrapper,它是一个内部函数,在inner_wrapper被调用时对limited_execution函数进行初始化,以便inner_wrapper中的count变量为每个函数调用重新设置为0。最后,如果inner_wrapper被调用的次数已经超出了限制,那么outer_wrapper就会抛出一个异常。
注意,我们将@limited_execution(3)语法糖应用到了factorial函数上,因此,它实际上会变成outer_wrapper闭包中的inner_wrapper函数的一个别名。这个闭包还包含了outer_wrapper函数中的count变量。
3.2 代码实现
现在我们来解释一下上面的代码。首先,我们定义了一个叫做limited_execution的函数,该函数返回了一个装饰器。我们称之为“外围装饰器”(Outer Decorator)。
def limited_execution(num):
def outer_wrapper(func):
def inner_wrapper(*args, **kwargs):
...
...
return outer_wrapper
这个函数接受一个num参数,它表示函数能够执行的最大次数。然后,函数返回了一个闭包,在该闭包中,我们定义了一个函数inner_wrapper,该函数接受任意数量的参数,并调用原始的函数。如果函数被调用的次数超过了指定的上限,则修改会抛出一个异常。
我们使用内层闭包来追踪函数的执行次数:
def limited_execution(num):
def outer_wrapper(func):
def inner_wrapper(*args, **kwargs):
if inner_wrapper.count >= num:
raise Exception("Function {} has exceeded the maximum number of executions ({})".format(func.__name__, num))
inner_wrapper.count += 1
return func(*args, **kwargs)
inner_wrapper.count = 0
return inner_wrapper
return outer_wrapper
在inner_wrapper函数中,我们首先检查当前的执行次数是否已经超过了指定的上限(num)。如果已经超过了,则抛出一个异常。然后,我们递增inner_wrapper.count的值,并调用原始的函数。
请注意,在每次程序运行时,装饰器都会重新设置这个值,因此,该装饰器对于不同的函数是互不影响的。
4. 结论
Python装饰器是一种强大的编程技术,它允许我们把代码组织成更具可读性和可重用性的形式。在本文中,我们提供了两个有用的示例,它们展示了装饰器的一些常见用途。
第一个示例展示了如何编写一个简单的装饰器,在调用函数之前和之后执行一些操作。这个示例很好地演示了装饰器的基本概念。第二个示例则展示了如何编写一个带参数的装饰器,我们在此限制了函数的执行次数。
要使用Python装饰器,请记住,它们是一个函数,它接受一个函数作为参数,并返回一个包装了原始函数的函数。装饰器可以用于各种各样的任务,包括打印出日志、测量函数的执行时间以及验证输入参数等。
最后,在实际开发中,请谨慎使用装饰器。虽然装饰器非常有用,但是如果使用不当,可能会在debug时带来很多麻烦,降低代码的可读性。