Python装饰器用法详解
1. 引言
装饰器是Python中一项强大而优雅的特性,它允许在不修改原函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外行为。这种设计模式在Python中被广泛应用,从日志记录、性能测试到事务处理、权限验证等场景。本文将深入探讨Python装饰器的各种用法,从基础概念到高级技巧,通过详尽的代码示例和解释,帮助读者全面掌握这一重要特性。
2. 函数基础复习
在深入装饰器之前,我们需要回顾一些Python函数的基础知识,因为装饰器的工作原理与函数的这些特性密切相关。
2.1. 函数是对象
在Python中,函数是一等公民,这意味着函数可以像其他对象一样被传递和操作:
def greet(name):
return f"Hello, {name}!"
# 函数可以赋值给变量
say_hello = greet
# 通过新变量调用函数
print(say_hello("Alice")) # 输出: Hello, Alice!
# 函数可以作为参数传递
def call_func(func, name):
return func(name)
print(call_func(greet, "Bob")) # 输出: Hello, Bob!
2.2. 函数嵌套与闭包
Python允许在函数内部定义其他函数,这种嵌套函数可以访问外部函数的变量:
def outer_function(msg):
message = msg
def inner_function():
print(message)
return inner_function
# 调用outer_function会返回inner_function
my_func = outer_function("Hi, there!")
my_func() # 输出: Hi, there!
这种内部函数可以访问外部函数变量的特性称为闭包。闭包使得内部函数可以记住它被创建时的环境。
3. 装饰器的基本概念
装饰器本质上是一个接受函数作为参数并返回一个新函数的函数。它的核心思想是在不改变原函数代码的情况下,为函数添加额外的功能。
3.1. 简单装饰器示例
下面是一个最简单的装饰器,它在函数执行前后打印一些信息:
def simple_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
def say_hello():
print("Hello!")
# 使用装饰器增强say_hello函数
decorated_hello = simple_decorator(say_hello)
decorated_hello()
输出:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
3.2. 使用@语法糖
Python提供了更简洁的装饰器语法——@符号。上面的例子可以重写为:
@simple_decorator
def say_hello():
print("Hello!")
say_hello()
@语法糖等同于:say_hello = simple_decorator(say_hello)
4. 处理带参数的函数
上面的装饰器只能处理不带参数的函数。为了使装饰器能处理任意参数的函数,我们需要使用*args和kwargs:
def decorator_with_arguments(func):
def wrapper(*args, **kwargs):
print("Function arguments:")
print(f"Positional: {args}")
print(f"Keyword: {kwargs}")
return func(*args, **kwargs)
return wrapper
@decorator_with_arguments
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice")
greet("Bob", greeting="Hi")
输出:
Function arguments:
Positional: ('Alice',)
Keyword: {}
Hello, Alice!
Function arguments:
Positional: ('Bob',)
Keyword: {'greeting': 'Hi'}
Hi, Bob!
5. 带参数的装饰器
有时我们需要创建可以接受参数的装饰器。这需要再增加一层嵌套:
def repeat(num_times):
def decorator_repeat(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"Hello, {name}!")
greet("World")
输出:
Hello, World!
Hello, World!
Hello, World!
这里,repeat是一个接受参数的装饰器工厂,它返回一个真正的装饰器。
6. 保留原函数的元信息
使用装饰器后,原函数的元信息(如函数名、文档字符串等)会被装饰器函数覆盖。我们可以使用functools.wraps来保留这些信息:
from functools import wraps
def decorator_with_metadata(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""Wrapper function's docstring"""
print(f"Calling function: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@decorator_with_metadata
def greet(name):
"""Greet someone by name"""
return f"Hello, {name}!"
print(greet.__name__) # 输出: greet (而不是 wrapper)
print(greet.__doc__) # 输出: Greet someone by name (而不是 Wrapper function's docstring)
7. 装饰器类
虽然装饰器通常使用函数实现,但也可以使用类实现。类装饰器需要实现__call__方法:
class CountCalls:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"Call {self.num_calls} of {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello()
say_hello()
say_hello()
输出:
Call 1 of say_hello
Hello!
Call 2 of say_hello
Hello!
Call 3 of say_hello
Hello!
8. 多个装饰器
可以同时使用多个装饰器,它们的执行顺序是从下到上:
def decorator1(func):
def wrapper(*args, **kwargs):
print("Decorator 1 before")
result = func(*args, **kwargs)
print("Decorator 1 after")
return result
return wrapper
def decorator2(func):
def wrapper(*args, **kwargs):
print("Decorator 2 before")
result = func(*args, **kwargs)
print("Decorator 2 after")
return result
return wrapper
@decorator1
@decorator2
def test():
print("Executing test function")
test()
输出:
Decorator 1 before
Decorator 2 before
Executing test function
Decorator 2 after
Decorator 1 after
这等价于:test = decorator1(decorator2(test))
9. 内置装饰器
Python提供了一些内置装饰器,其中最常用的是@property、@classmethod和@staticmethod。
9.1. @property装饰器
@property装饰器将方法转换为属性,使得可以像访问属性一样调用方法:
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def diameter(self):
return 2 * self.radius
@property
def area(self):
return 3.14159 * self.radius ** 2
circle = Circle(5)
print(circle.diameter) # 输出: 10
print(circle.area) # 输出: 78.53975
9.2. @classmethod和@staticmethod
@classmethod装饰器将方法转换为类方法,第一个参数是类本身(cls)。@staticmethod装饰器将方法转换为静态方法,不需要特殊参数:
class MyClass:
@classmethod
def class_method(cls):
print(f"Called class method of {cls.__name__}")
@staticmethod
def static_method():
print("Called static method")
MyClass.class_method() # 输出: Called class method of MyClass
MyClass.static_method() # 输出: Called static method
10. 装饰器的应用场景
装饰器在实际开发中有广泛的应用,以下是一些常见场景:
10.1. 日志记录
def log_function_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper
@log_function_calls
def add(a, b):
return a + b
add(3, 5)
输出:
Calling add with args: (3, 5), kwargs: {}
add returned: 8
10.2. 性能测试
import time
from functools import wraps
def measure_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} executed in {end_time - start_time:.6f} seconds")
return result
return wrapper
@measure_time
def calculate_sum(n):
return sum(range(n))
calculate_sum(1000000)
输出:
calculate_sum executed in 0.037102 seconds
10.3. 权限验证
def requires_permission(permission):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
current_user = get_current_user() # 假设这个函数获取当前用户
if current_user.has_permission(permission):
return func(*args, **kwargs)
else:
raise PermissionError(f"User lacks {permission} permission")
return wrapper
return decorator
@requires_permission("admin")
def delete_user(user_id):
# 删除用户逻辑
pass
10.4. 缓存结果
from functools import wraps
def cache_results(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args in cache:
print("Returning cached result")
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@cache_results
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))
print(fibonacci(10)) # 第二次调用会使用缓存
11. 高级装饰器技巧
11.1. 有状态的装饰器
装饰器可以携带状态,记住函数调用之间的信息:
def counter(func):
func.calls = 0
@wraps(func)
def wrapper(*args, **kwargs):
func.calls += 1
print(f"{func.__name__} has been called {func.calls} times")
return func(*args, **kwargs)
return wrapper
@counter
def greet():
print("Hello!")
greet()
greet()
greet()
输出:
greet has been called 1 times
Hello!
greet has been called 2 times
Hello!
greet has been called 3 times
Hello!
11.2. 装饰器类与函数混合
可以创建既可以作为装饰器使用,又可以作为普通调用的装饰器:
class optional_debug:
def __init__(self, func=None, *, debug=False):
self.func = func
self.debug = debug
def __call__(self, *args, **kwargs):
if self.debug:
print(f"Calling {self.func.__name__}")
return self.func(*args, **kwargs)
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
if self.debug:
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
# 使用方式1:作为普通装饰器
@optional_debug
def add(a, b):
return a + b
# 使用方式2:带参数的装饰器
@optional_debug(debug=True)
def multiply(a, b):
return a * b
add(2, 3)
multiply(4, 5)
11.3. 装饰器与类
装饰器不仅可以用于函数,也可以用于类:
def add_methods(cls):
def new_method(self):
print("New method added by decorator")
cls.new_method = new_method
return cls
@add_methods
class MyClass:
pass
obj = MyClass()
obj.new_method() # 输出: New method added by decorator
11.4. 使用functools.update_wrapper
虽然@wraps是更常用的方式,但也可以使用functools.update_wrapper手动复制元信息:
from functools import update_wrapper
def simple_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
update_wrapper(wrapper, func)
return wrapper
@simple_decorator
def example():
"""Example function docstring"""
pass
print(example.__name__) # 输出: example
print(example.__doc__) # 输出: Example function docstring
12. 总结
Python装饰器是一种强大而灵活的工具,它允许我们在不修改原函数代码的情况下扩展其功能。通过本文的学习,我们了解到:
装饰器本质上是接受函数作为参数并返回新函数的函数
使用@语法糖可以简洁地应用装饰器
装饰器可以处理带参数的函数,通过*args和kwargs捕获所有参数
带参数的装饰器需要三层嵌套结构
使用functools.wraps可以保留原函数的元信息
装饰器可以使用类实现,通过__call__方法使类可调用
多个装饰器可以组合使用,执行顺序是从下到上
Python内置了@property、@classmethod和@staticmethod等常用装饰器
装饰器在实际开发中有广泛应用,如日志记录、性能测试、权限验证和结果缓存等
高级装饰器技巧包括有状态装饰器、多功能装饰器、类装饰器和手动更新元信息等
掌握装饰器不仅能够提高代码的可读性和可维护性,还能帮助我们以更优雅的方式实现横切关注点(如日志、缓存、权限等),是Python开发者必备的高级技能之一。通过不断实践和探索,您将能够充分利用装饰器的强大功能,写出更加简洁、高效和Pythonic的代码。







