Python中装饰器的基本功能理解

1. 装饰器的概念

Python中的装饰器是一种函数,它可以装饰其他函数,以修改或增强其行为。简单来说,装饰器就是在不改变原函数代码的情况下,增加功能,实现代码的复用。

# 一个简单的装饰器示例

def my_decorator(some_function):

def wrapper():

print("Something is happening before some_function() is called.")

some_function()

print("Something is happening after some_function() is called.")

return wrapper

def say_hello():

print("Hello!")

say_hello = my_decorator(say_hello)

say_hello() # 输出:Something is happening before some_function() is called. Hello! Something is happening after some_function() is called.

2. 装饰器的语法糖(@)

Python提供了装饰器的语法糖,使用@符号可以更方便地实现装饰器功能。

# 一个实现计时功能的装饰器示例

import time

def time_it(func):

def wrapper(*args, **kwargs):

start_time = time.time()

result = func(*args, **kwargs)

end_time = time.time()

print(f"函数 {func.__name__} 的运行时间为 {end_time - start_time} 秒")

return result

return wrapper

@time_it

def do_something():

time.sleep(2)

print("任务完成!")

do_something()

# 输出(运行时间可能有所不同):

# 任务完成!

# 函数 do_something 的运行时间为 2.002633571624756 秒

3. 装饰器的应用场景

3.1 函数执行前的操作

装饰器可以在函数执行之前执行某些额外操作,比如参数检查、权限验证等。

# 参数检查装饰器示例

def check_arg_types(*expected_args):

def decorator(func):

def wrapper(*args, **kwargs):

for index, arg in enumerate(args):

if not isinstance(arg, expected_args[index]):

raise TypeError(f"The {index + 1}th argument should be {expected_args[index]}, but {type(arg)} is given.")

for key, value in kwargs.items():

if not isinstance(value, expected_args[len(args) + list(kwargs.keys()).index(key)]):

raise TypeError(f"The argument {key} should be {expected_args[len(args) + list(kwargs.keys()).index(key)]}, but {type(value)} is given.")

return func(*args, **kwargs)

return wrapper

return decorator

@check_arg_types(int, int)

def add(a, b):

return a + b

add(1, "2") # 报错:TypeError: The 2th argument should be , but is given.

3.2 函数执行后的操作

装饰器也可以在函数执行之后执行某些额外操作,比如记录日志、发送Email等。

# 日志记录装饰器示例

def log_it(func):

import time

def wrapper(*args, **kwargs):

start_time = time.time()

result = func(*args, **kwargs)

end_time = time.time()

with open("log.txt", "a") as f:

f.write(f"函数 {func.__name__} 执行时间为 {end_time - start_time} 秒,结果为 {result}\n")

return result

return wrapper

@log_it

def square(x):

return x ** 2

square(3)

# 日志文件 log.txt 中记录了:

# 函数 square 执行时间为 4.0531158447265625e-06 秒,结果为 9

3.3 缓存数据

装饰器可以实现函数的结果缓存,避免重复执行同样的计算。

# 缓存数据装饰器示例

def memoize(func):

cache = {}

def wrapper(*args):

if args in cache:

print(f"从缓存中读取结果 {cache[args]}")

return cache[args]

result = func(*args)

cache[args] = result

print(f"将结果 {result} 缓存起来")

return result

return wrapper

@memoize

def fibonacci(n):

if n == 0 or n == 1:

return n

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

fibonacci(10)

# 输出:

# 将结果 55 缓存起来

4. 多个装饰器的使用

使用多个装饰器可以实现对函数的多个增强功能。装饰器的执行顺序是由下至上的。

# 多个装饰器的使用示例

def decorator1(func):

def wrapper():

print("Decorator 1 before...")

func()

print("Decorator 1 after...")

return wrapper

def decorator2(func):

def wrapper():

print("Decorator 2 before...")

func()

print("Decorator 2 after...")

return wrapper

@decorator1

@decorator2

def say_hello():

print("Hello!")

say_hello()

# 输出:

# Decorator 1 before...

# Decorator 2 before...

# Hello!

# Decorator 2 after...

# Decorator 1 after...

5. 带参数的装饰器

装饰器也可以带参数,例如在时间计算中可以指定时间单位。

# 带参数的装饰器示例

def time_it(unit="second"):

def decorator(func):

def wrapper(*args, **kwargs):

start_time = time.time()

result = func(*args, **kwargs)

end_time = time.time()

if unit == "second":

print(f"函数 {func.__name__} 的运行时间为 {end_time - start_time} 秒")

elif unit == "millisecond":

print(f"函数 {func.__name__} 的运行时间为 {(end_time - start_time) * 1000} 毫秒")

else:

raise ValueError("Invalid unit.")

return result

return wrapper

return decorator

@time_it(unit="millisecond")

def do_something():

time.sleep(2)

print("任务完成!")

do_something()

# 输出(运行时间可能有所不同):

# 任务完成!

# 函数 do_something 的运行时间为 2002.8650760650635 毫秒

6. 注意事项

装饰器可以增强函数的功能,但是也有一些需要注意的细节问题。

装饰器会改变函数的元信息,如函数名、文档字符串、注解等。可以使用functools库中的wraps函数来解决元信息问题。

装饰器不适合用于修改函数的返回值,因为修改返回值会在函数调用后执行,而装饰器是在函数调用前执行的。如果需要修改返回值,应该在函数内部进行相应的处理。

装饰器不适合用于不确定参数个数的函数,因为装饰器的参数个数要和被装饰函数的参数个数相同。

后端开发标签