Python装饰器

基本概念

定义

装饰器本质上是一个函数,该函数接受一个函数作为参数,并返回一个新的函数。(可以理解为输入一个函数,返回装饰后的函数,所谓装饰,一般来说就是为该函数添加一些功能)

装饰器的核心部分只有两部分:

  1. 定义一个内部函数:为原函数添加功能(在参数与返回类型上应与原函数保持一致)
  2. 返回该内部函数

这是一个简单装饰器的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time

def calculate(a,b):
sum =0
for i in range(a,b):
sum = sum+i
print(sum)
return sum

def decorator_cal(func):
def wapper(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
end_time =time.time()
diff_time = end_time-start_time
print(f"{func.__name__} took {diff_time:.10f} seconds")
return res
return wapper

calculate= decorator_cal(calculate)

calculate(6,10263)
  1. 首先我们定义了一个简单的求和函数calculate(a,b),输入是区间[a,b),返回区间和。

  2. 接下来我们定义了一个装饰器函数

    其中的关键是

    • *args, **kwargs:这些是可变参数,用于接收任意数量的非关键字参数和关键字参数。这样可以确保 wapper 函数能够接收和处理 func 所有的参数。

    • res = func(*args, **kwargs):调用传入的 func 函数,并把原始的参数传递给它。执行完 func 函数后,会将结果保存在变量 res 中。

    • return res 由于原函数包含返回值,所以wapper也需要有和原函数相同的返回值。如果原函数没有返回值,wapper自然也不需要返回值。

  3. 最后我们显示调用该装饰器函数:calculate= decorator_cal(calculate),从此以后,调用calculate就是调用装饰过后的calculate了(注意我们一般通过@符号完成该步骤,这里是方便理解)

@符号

@decorator本质上是一个语法糖,当他添加在某函数(func)上时,它等价与func = decorator(func)

装饰器通常使用@decorator_name的语法糖直接应用到需要装饰的函数上。

带参数的装饰器

有时,装饰器本身需要接受参数。此时可以使用多一层嵌套来实现。

比如,我想控制打印精度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time
def Precision_control(precision):
def decorator_cal(func):
def wapper(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
end_time =time.time()
diff_time = end_time-start_time
print(f"{func.__name__} took {diff_time:.{precision}f} seconds")
return res
return wapper
return decorator_cal

@Precision_control(5)
def calculate(a,b):
sum =0
for i in range(a,b):
sum = sum+i
print(sum)
return sum

calculate(6,10263)

示例

我们可以通过 Python 装饰器来实现多种功能,比如日志记录、权限验证、性能测量、缓存、输入检查等。

日志记录(Logging)

日志记录用于在函数执行时记录相关信息,帮助调试和了解系统行为。可以通过装饰器自动记录函数的调用情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import logging
from functools import wraps

# 设置日志
logging.basicConfig(level=logging.INFO)

def log_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"Running {func.__name__} with args: {args} and kwargs: {kwargs}")
result = func(*args, **kwargs)
logging.info(f"{func.__name__} returned {result}")
return result
return wrapper

@log_decorator
def add(a, b):
return a + b

add(2, 3)

日志输出:

1
2
INFO:root:Running add with args: (2, 3) and kwargs: {}
INFO:root:add returned 5

权限验证(Authentication)

权限验证用于在执行某些敏感操作之前验证用户身份或权限。可以使用装饰器来检查用户是否有权限执行特定操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def check_permissions(user):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if user['role'] != 'admin':
raise PermissionError(f"User {user['name']} does not have admin rights")
return func(*args, **kwargs)
return wrapper
return decorator

user = {'name': 'Alice', 'role': 'user'}

@check_permissions(user)
def delete_data():
print("Data deleted.")

try:
delete_data()
except PermissionError as e:
print(e)

输出:

1
User Alice does not have admin rights

性能测量(Performance Monitoring)

性能测量用于评估函数的执行时间。可以通过装饰器来记录函数的开始时间和结束时间,并计算执行时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import time

def timer_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
return result
return wrapper

@timer_decorator
def slow_function():
time.sleep(2)
return "Finished"

slow_function()

输出:

1
slow_function took 2.0001 seconds

缓存(Caching)

缓存用于存储函数的返回值,从而避免多次计算相同输入的结果。可以使用装饰器来实现简单的缓存机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def cache_decorator(func):
cache = {}

@wraps(func)
def wrapper(*args):
if args in cache:
print("Using cached result")
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper

@cache_decorator
def expensive_computation(x):
print(f"Computing for {x}")
return x * x

expensive_computation(4)
expensive_computation(4)

输出:

1
2
Computing for 4
Using cached result

输入检查(Validation)

输入检查用于验证函数的参数是否合法,避免无效的输入导致错误。可以使用装饰器自动检查参数的类型或范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def validate_decorator(func):
@wraps(func)
def wrapper(a, b):
if not isinstance(a, int) or not isinstance(b, int):
raise ValueError("Both arguments must be integers")
return func(a, b)
return wrapper

@validate_decorator
def add(a, b):
return a + b

try:
add(2, "three")
except ValueError as e:
print(e)

输出:

1
Both arguments must be integers

重试机制(Retry)

重试机制用于在函数失败时自动重试,特别是在处理不稳定的外部资源(如网络请求)时很有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def retry_decorator(retries=3):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempt = 0
while attempt < retries:
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Attempt {attempt + 1} failed with error: {e}")
attempt += 1
raise Exception(f"Function failed after {retries} retries")
return wrapper
return decorator

@retry_decorator(retries=5)
def unstable_function():
raise ValueError("Random failure")

try:
unstable_function()
except Exception as e:
print(e)

输出:

1
2
3
4
5
6
Attempt 1 failed with error: Random failure
Attempt 2 failed with error: Random failure
Attempt 3 failed with error: Random failure
Attempt 4 failed with error: Random failure
Attempt 5 failed with error: Random failure
Function failed after 5 retries

@atexit装饰器

atexit装饰器用于在程序退出之前自动执行某些操作,比如清理资源或记录日志。Python的atexit模块可以注册函数在程序退出时执行。

1
2
3
4
5
6
import atexit

def goodbye():
print("Program is exiting. Goodbye!")

atexit.register(goodbye)

当程序正常退出时,会打印:

1
Program is exiting. Goodbye!

@deprecated装饰器

deprecated装饰器用于标记一个函数已不推荐使用,提醒开发者在调用时使用替代方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import warnings

def deprecated(func):
@wraps(func)
def wrapper(*args, **kwargs):
warnings.warn(f"{func.__name__} is deprecated", category=DeprecationWarning)
return func(*args, **kwargs)
return wrapper

@deprecated
def old_function():
print("This function is old and deprecated.")

old_function()

输出:

1
2
old_function is deprecated
This function is old and deprecated.

Django中的装饰器

在 Django 框架中,装饰器广泛用于简化代码、增强功能和实现一些常见的功能需求。

功能:

@login_required 是 Django 中最常用的装饰器之一,确保用户在访问特定视图之前必须已经登录。它会检查当前请求的用户是否已经登录,未登录的用户将被重定向到登录页面。

使用示例:

1
2
3
4
5
from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
return HttpResponse("You are logged in!")

如果用户没有登录,Django 会将用户重定向到默认的登录页面(可以通过 LOGIN_URL 配置自定义登录页面)。

自定义登录页面:

1
2
3
@login_required(login_url='/custom-login/')
def my_view(request):
return HttpResponse("Custom login page if not authenticated.")

@permission_required

功能:

@permission_required 用于检查用户是否具有特定的权限。它要求用户具备指定的权限才能访问视图。如果用户不具备该权限,系统会显示一个“403 Forbidden”错误或将用户重定向到指定页面。

使用示例:

1
2
3
4
5
from django.contrib.auth.decorators import permission_required

@permission_required('app_label.permission_codename')
def my_view(request):
return HttpResponse("You have the necessary permission!")

可以自定义权限不足时的重定向页面:

1
2
3
@permission_required('app_label.permission_codename', login_url='/no-permission/')
def my_view(request):
return HttpResponse("You are redirected due to insufficient permissions.")

@user_passes_test

功能:

@user_passes_test 是一个通用装饰器,允许你根据自定义的测试条件决定是否允许用户访问某个视图。你可以通过传递一个函数来进行验证。如果测试未通过,用户将被重定向到登录页面或指定页面。

使用示例:

1
2
3
4
5
6
7
8
from django.contrib.auth.decorators import user_passes_test

def check_if_superuser(user):
return user.is_superuser

@user_passes_test(check_if_superuser)
def superuser_view(request):
return HttpResponse("You are a superuser.")

自定义重定向页面:

1
2
3
@user_passes_test(check_if_superuser, login_url='/no-access/')
def superuser_view(request):
return HttpResponse("Only superusers can see this.")

@csrf_exempt

功能:

Django 默认开启了跨站请求伪造 (CSRF) 防护机制。@csrf_exempt 装饰器用于排除某些视图不进行 CSRF 检查,特别是在处理第三方系统的请求时非常有用。

使用示例:

1
2
3
4
5
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def my_view(request):
return HttpResponse("CSRF protection is disabled for this view.")

注意:在禁用 CSRF 防护时要小心,确保这个视图只用于安全的外部系统或只接受安全的请求。

@require_http_methods

功能:

@require_http_methods 限制视图只允许通过指定的 HTTP 方法(如 GET、POST 等)访问。它是简化对请求类型验证的一种快捷方式。

使用示例:

1
2
3
4
5
from django.views.decorators.http import require_http_methods

@require_http_methods(["GET", "POST"])
def my_view(request):
return HttpResponse("This view only allows GET and POST requests.")

如果客户端发送了非指定的 HTTP 方法(如 PUT、DELETE 等),服务器将返回 405 状态码(Method Not Allowed)。

@require_GET, @require_POST

功能:

  • @require_GET:确保视图只能通过 GET 请求访问。
  • @require_POST:确保视图只能通过 POST 请求访问。

使用示例:

1
2
3
4
5
6
7
8
9
from django.views.decorators.http import require_GET, require_POST

@require_GET
def my_get_view(request):
return HttpResponse("This view only allows GET requests.")

@require_POST
def my_post_view(request):
return HttpResponse("This view only allows POST requests.")

@cache_page

功能:

@cache_page 装饰器用于缓存视图的输出。它可以显著提升性能,尤其是对于经常被访问但不常变化的页面。

使用示例:

1
2
3
4
5
from django.views.decorators.cache import cache_page

@cache_page(60 * 15) # 缓存时间为 15 分钟
def my_view(request):
return HttpResponse("This view is cached for 15 minutes.")

你可以通过缓存的时间长度来控制缓存失效的时间(以秒为单位)。此外,你可以根据 URL 或请求参数实现更复杂的缓存策略。

@never_cache

功能:

@never_cache 装饰器确保视图的响应永远不会被缓存。它可以用于那些内容经常变化且不应该被缓存的视图。

使用示例:

1
2
3
4
5
from django.views.decorators.cache import never_cache

@never_cache
def my_view(request):
return HttpResponse("This view should never be cached.")

功能:

  • @vary_on_headers:根据请求的 HTTP 头信息来区分缓存内容。
  • @vary_on_cookie:根据请求中的 Cookie 信息来区分缓存内容。

使用示例:

1
2
3
4
5
6
7
8
9
from django.views.decorators.vary import vary_on_headers, vary_on_cookie

@vary_on_headers("User-Agent")
def my_view(request):
return HttpResponse("Vary on User-Agent.")

@vary_on_cookie
def my_cookie_view(request):
return HttpResponse("Vary on cookies.")

这些装饰器通常结合缓存机制一起使用,可以实现针对不同用户、浏览器或请求的缓存控制。

@transaction.atomic

功能:

@transaction.atomic 装饰器用于将视图的数据库操作封装在一个事务中。如果在视图执行过程中出现错误,所有的数据库操作都将回滚,确保数据一致性。

使用示例:

1
2
3
4
5
6
from django.db import transaction

@transaction.atomic
def my_view(request):
# 所有的数据库操作将在这里封装成一个事务
...

如果在视图中抛出异常,数据库的变更将不会被保存。

@sensitive_post_parameters

功能:

@sensitive_post_parameters 用于在处理敏感数据(如密码、信用卡号等)的视图中,防止这些数据出现在错误报告或日志中。它会将指定的 POST 参数标记为敏感信息,避免暴露。

使用示例:

1
2
3
4
5
6
7
from django.views.decorators.debug import sensitive_post_parameters

@sensitive_post_parameters('password', 'credit_card_number')
def my_view(request):
password = request.POST['password']
credit_card_number = request.POST['credit_card_number']
...

Python装饰器与Java注解

Python 装饰器和 Java 注解是两种不同语言中用于增强代码功能的机制,虽然它们的语法和使用方式不同,但都可以用来对函数、类、方法或变量进行修饰或附加行为。

定义与用途

  • Python 装饰器
    Python 装饰器是一种高阶函数,通常用于在不改变原有函数或方法代码的情况下,为其添加额外的功能。装饰器接受一个函数或类作为参数,返回一个被包装后的函数或类。常见用途包括日志记录、权限检查、性能监测、缓存等。

  • Java 注解
    Java 注解(Annotation)是一种元数据标注形式,它为类、方法、字段等元素提供额外的信息。注解本身并不直接执行代码,而是通过编译器、框架或运行时机制(如反射)解析注解,并做出相应的处理。常见用途包括文档生成、代码检查、运行时行为定制等。

运行机制

Python 装饰器

  • 运行时处理:Python 装饰器在代码运行时对函数或类进行修饰。当装饰器被调用时,实际会执行包装的逻辑。它可以在函数调用之前、之后或者替换整个函数的行为。

  • 动态性强:装饰器可以动态地修改或增强函数的行为,不需要对原有的代码进行修改。由于 Python 是动态语言,这种动态修改的能力非常灵活。

Java 注解

  • 编译时或运行时处理:Java 注解可以在编译时或运行时处理。像 @Override 这样的注解是在编译时检查的,而像 @Entity@Transactional 等注解是由框架在运行时通过反射机制处理的。

  • 元数据驱动:注解提供的是元数据,不直接改变代码的执行逻辑。注解通常配合框架或者工具(如 Spring、Hibernate)一起使用,由这些工具根据注解来决定如何处理代码。

常见用途

Python 装饰器的用途

  • 日志记录:在函数执行前后记录日志信息。
  • 权限检查:在访问敏感功能前进行权限验证。
  • 性能监测:测量函数执行的时间。
  • 缓存:缓存函数结果,避免重复计算。
  • 输入校验:对函数输入进行参数校验。

示例:权限检查装饰器

1
2
3
4
5
6
7
8
9
10
def require_admin(func):
def wrapper(user, *args, **kwargs):
if not user.is_admin:
raise PermissionError("Admin privileges required")
return func(user, *args, **kwargs)
return wrapper

@require_admin
def delete_user(user):
print(f"User {user.name} deleted.")

Java 注解的用途

  • 编译时检查@Override 用于检查方法是否正确覆盖了父类的方法。
  • 依赖注入@Autowired 用于 Spring 框架中进行依赖注入。
  • 事务管理@Transactional 用于声明方法或类是事务性操作。
  • 映射关系@Entity@Table 等用于 Hibernate 框架中映射数据库表。
  • 测试@Test 用于 JUnit 测试框架标识测试方法。

示例:Spring 中的事务管理

1
2
3
4
5
6
7
8
9
import org.springframework.transaction.annotation.Transactional;

public class MyService {

@Transactional
public void performOperation() {
// 数据库操作将在一个事务中执行
}
}

差异与对比

维度 Python 装饰器 Java 注解
处理时间 运行时动态处理 编译时和运行时(通过反射或框架处理)
功能 直接修改或增强函数/类的行为 提供元数据,不直接修改行为,通常通过框架或工具解析
灵活性 非常灵活,可用于任意函数或类,动态修改代码 注解多为框架或工具依赖,元数据驱动
常见用途 日志、权限验证、性能监控、缓存、输入校验等 编译时检查、依赖注入、事务管理、ORM 映射等
语法 使用高阶函数实现,修饰函数或类 使用元数据标注,修饰类、方法、字段等

总结

  • Python 装饰器:是一个动态的、灵活的机制,可以修改或增强函数和类的行为,广泛用于日志记录、性能分析、权限验证等。

  • Java 注解:是一种静态的元数据标注,通常用于编译时检查或通过框架在运行时处理。Java 注解的主要作用是通过工具或框架的解释为代码增加额外的行为。

拓展

类装饰器

类装饰器的实现与函数装饰器类似,只是它接收和返回的是类对象。

类装饰器是指装饰类,即使用装饰器修饰一个类,而不仅仅是函数或方法。这种装饰器和函数装饰器类似,主要是为了增强类的功能,修改类的行为,或者为类添加某些功能。

当我们说“类装饰器”,一般是指作用在类上的装饰器,即用来修饰类的装饰器,而不是用类来实现的装饰器。类装饰器主要用于改变类的属性、方法或实例的行为,甚至可以返回一个修改过的类。

应用场景

  • 类的属性或方法自动增强:可以在不改变类定义的情况下,动态地修改或添加属性和方法。
  • 类的注册机制:将类自动注册到某个管理系统中(比如某个全局注册表)。
  • 单例模式:强制类只能创建一个实例。
  • 权限控制:为某类对象的方法添加权限检查。

类装饰器的语法

类装饰器的使用和函数装饰器非常相似,使用 @decorator_name 的语法,装饰器接受类作为参数并返回修改后的类。

1
2
3
4
5
6
7
8
def class_decorator(cls):
# 这里可以对类进行修改
return cls

@class_decorator
class MyClass:
def method(self):
pass

示例

  • 为类添加额外的方法

这是一个简单的类装饰器例子,它为类动态地添加一个新的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def add_method_decorator(cls):
def new_method(self):
return "This is a dynamically added method!"

cls.new_method = new_method
return cls

@add_method_decorator
class MyClass:
def original_method(self):
return "This is the original method."

# 创建实例并调用方法
obj = MyClass()
print(obj.original_method()) # 输出: This is the original method.
print(obj.new_method()) # 输出: This is a dynamically added method!

在这个例子中,装饰器 add_method_decorator 向类 MyClass 添加了一个 new_method 方法,而不需要修改类定义。装饰后的类实例可以调用这个新增的方法。

  • 单例模式

单例模式是一种常见的设计模式,它确保一个类只能有一个实例。使用类装饰器可以优雅地实现这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def singleton(cls):
instances = {}

def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]

return get_instance

@singleton
class MySingletonClass:
def __init__(self, value):
self.value = value

# 创建两个实例
obj1 = MySingletonClass(10)
obj2 = MySingletonClass(20)

# 确保它们是同一个实例
print(obj1 is obj2) # 输出: True
print(obj1.value) # 输出: 10
print(obj2.value) # 输出: 10

在这个例子中,singleton 装饰器通过内部字典 instances 缓存类的实例,确保每次调用时都返回同一个实例。尽管两次使用不同的参数创建对象,装饰器强制它们共享同一个实例。

  • 类注册装饰器

类装饰器还可以用于自动注册类,这在大型应用中非常有用,比如在插件系统中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
registry = {}

def register_class(cls):
registry[cls.__name__] = cls
return cls

@register_class
class MyClassA:
pass

@register_class
class MyClassB:
pass

# 输出已注册的类
print(registry)

输出:

1
{'MyClassA': <class '__main__.MyClassA'>, 'MyClassB': <class '__main__.MyClassB'>}

在这个例子中,register_class 装饰器将类注册到全局的 registry 字典中,允许程序在其他地方动态地查找和使用这些类。

使用functools.wraps

functools.wraps 是一个装饰器,通常用于在自定义装饰器中保持被装饰函数的元数据(如函数名、文档字符串、注释等),以免被装饰器的内部函数覆盖。它能够让装饰过的函数看起来依然像原来的函数,从而保持一致性。

例子

下面我们使用 functools.wraps 来构建一个装饰器,记录函数的调用日志,并保证装饰后的函数保留原函数的元信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import functools

def log_execution(func):
@functools.wraps(func) # 保留被装饰函数的元信息
def wrapper(*args, **kwargs):
print(f"Executing '{func.__name__}' with arguments {args} and keyword arguments {kwargs}")
result = func(*args, **kwargs)
print(f"'{func.__name__}' returned {result}")
return result
return wrapper

@log_execution
def add(a, b):
"""Returns the sum of two numbers."""
return a + b

# 调用函数
result = add(10, 20)
print(f"Result: {result}")
print(f"Function name: {add.__name__}")
print(f"Function docstring: {add.__doc__}")

输出:

1
2
3
4
5
Executing 'add' with arguments (10, 20) and keyword arguments {}
'add' returned 30
Result: 30
Function name: add
Function docstring: Returns the sum of two numbers.

在这个例子中,log_execution 装饰器在函数执行前后打印日志,记录调用时的参数和返回值。由于我们使用了 functools.wraps(func),装饰后的 add 函数依然保留了原函数的名称和文档字符串,否则它们会被内部的 wrapper 函数覆盖掉。

如果不使用 functools.wraps 的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def log_execution(func):
def wrapper(*args, **kwargs):
print(f"Executing '{func.__name__}' with arguments {args} and keyword arguments {kwargs}")
result = func(*args, **kwargs)
print(f"'{func.__name__}' returned {result}")
return result
return wrapper

@log_execution
def add(a, b):
return a + b

print(f"Function name: {add.__name__}")
print(f"Function docstring: {add.__doc__}")

输出:

1
2
Function name: wrapper
Function docstring: None

可以看到,如果不使用 functools.wrapsadd 函数的名称变成了 wrapper,而且原来的文档字符串也丢失了。因此,functools.wraps 非常重要,它确保装饰后的函数保留原始函数的元数据信息。