Python中的元编程(1)-装饰器
元编程(Meta-Programming) 这个概念平时写CRUD可能不怎么用,如果需要造轮子给别人使用,或者提高程序的抽象层次以减少重复代码,往往绕不开这个概念。
元编程所谓的“元”即“meta-”这一词根的翻译,有超越的、更高的意思,也蕴含着改变的意味。例如下面这些词汇:
- metaphysics 形而上学:meta+physics,超越物质科学的科学
- metamorphism 变形;变性: meta+morph形状+ism,变形
元编程就是这样的一种超越编程的编程、改变编程的编程。元编程涵盖的范围其实很宽广,主流语言或多或少都具有一定的元编程能力。古老如C语言可以通过宏展开在编译期实现对程序的修改,虽然只是文本的展开,但是通过一些技巧也能实现复杂代码的生成。在较新的语言中,提供了更多的特性,比如kotlin中的注解、反射。
对于python来说,主要有以下几种实现元编程的方法:
- 装饰器
- 元类
- 反射
- 描述符
- 动态代码执行
装饰器
在Python中,装饰器是一种特殊类型的函数,它可以修改其他函数的功能或行为。装饰器本质上是一个接受一个函数作为参数的函数,并返回一个新的函数。使用装饰器可以很方便地为已有函数添加功能,而无需修改原函数的代码。
简单装饰器
一个简单的装饰器通常看起来像这样:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
result = func(*args, **kwargs)
print("Something is happening after the function is called.")
return result # 返回原始函数的结果
return wrapper
@my_decorator
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
运行将会得到下面的输出:
Something is happening before the function is called.
Hello, Alice!
Something is happening after the function is called.
这种特性使得装饰器能很方便地实现日志记录、权限检查、参数检查、性能计时等需求。
带参数的装饰器
有时候有些功能很适合使用装饰器完成,但是又要去装饰器的行为有一定的变化,这时候可以通过带参数的装饰器实现。
带参数的装饰器本质上是一个装饰器工厂,通过不同参数产生不同的实际装饰器。通过这种方式,可以在装饰器中传递参数,从而根据不同的需求调整其行为。因此带参数的装饰器需要使用三层嵌套函数结构:装饰器工厂、实际装饰器和包装函数。
实现失败重试的装饰器示例:
import time
import random
def retry(max_attempts=3, delay=1):
"""
重试装饰器
:param max_attempts: 最大重试次数
:param delay: 每次重试之间的延迟时间(秒)
"""
def decorator(func):
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
print(f"尝试 {attempts}/{max_attempts} 失败,错误信息:{e}")
time.sleep(delay)
# 如果重试次数用完仍未成功,抛出异常
raise Exception(f"经过 {max_attempts} 次尝试后,函数仍未成功执行。")
return wrapper
return decorator
# 示例函数,模拟可能失败的操作
@retry(max_attempts=5, delay=2)
def risky_function():
print("正在执行 risky_function...")
if random.random() < 0.7: # 70% 的概率失败
raise ValueError("随机失败!")
return "成功执行!"
try:
result = risky_function()
print(f"最终结果:{result}")
except Exception as e:
print(f"最终失败:{e}")
类装饰器
类装饰器是 Python 中一种特殊的装饰器,它通过类来实现对函数或方法的装饰。与函数装饰器不同,类装饰器利用类的实例化和方法调用机制来增强被装饰对象的功能。类装饰器通常通过定义一个类,并在该类中实现 __call__
方法来实现装饰器的行为。一个典型的类装饰器通常包含以下部分:
- 类定义:定义一个类,通常包含
__init__
方法和__call__
方法。 __init__
方法:接收被装饰的函数或方法,并将其存储为类的属性。__call__
方法:实现装饰逻辑,每次调用被装饰的函数时,都会执行这个方法。
一个简单的类装饰器:
class CountCalls:
def __init__(self, func):
self.func = func
self.call_count = 0
def __call__(self, *args, **kwargs):
self.call_count += 1
print(f"函数 {self.func.__name__} 被调用了 {self.call_count} 次")
return self.func(*args, **kwargs)
# 使用类装饰器
@CountCalls
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
say_hello("Bob")
say_hello("Charlie")
类装饰器的优势:
- 状态管理:类装饰器可以维护状态(如调用次数、执行时间等),而函数装饰器通常需要通过闭包来实现类似功能。
- 灵活性:类装饰器可以通过继承和组合来扩展功能,而函数装饰器通常需要嵌套多层函数来实现复杂逻辑。
- 可读性:类装饰器的结构更清晰,尤其是对于复杂的逻辑,类的结构更容易理解和维护。
装饰器的其他使用技巧
1、装饰器链,可以将多个装饰器应用于同一个函数,这些装饰器会按照从外到内的顺序被调用。
2、装饰器也可以用于异步函数,注意使用await。
3、使用functools.wraps保留被装饰函数的元信息,可以让外部对原函数的变化没有感知。