Python装饰器函数
装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数。这个新函数通常会在调用原函数之前或之后执行一些额外的操作。日志记录:可以使用装饰器在函数执行前后记录日志信息,例如函数的名称、参数、执行时间等。
Python 中可以被认为既是一种设计模式的体现,也是一种编程思想的体现。
一、作为设计模式
装饰器体现了装饰器模式。装饰器模式允许在不改变原有对象结构的情况下,动态地给对象添加新的功能。在 Python 中,装饰器就是通过将一个函数包装在另一个函数中,在不修改被装饰函数内部代码的情况下,为其添加额外的行为,这与装饰器模式的理念一致。
例如,用装饰器为一个函数添加日志记录功能,就如同给这个函数 “装饰” 上了日志记录的特性,而不改变函数本身的核心业务逻辑。
二、作为编程思想
- 代码复用:装饰器提供了一种方便的方式来复用代码。可以将一些通用的功能(如日志记录、性能测量、权限验证等)封装在装饰器中,然后应用到多个不同的函数上,避免了在每个函数中重复编写相同的代码。
- 分离关注点:它有助于将业务逻辑和横切关注点(如日志、安全等)分离开来。业务函数可以专注于实现核心业务逻辑,而装饰器负责处理与业务逻辑不直接相关的辅助功能。这样可以使代码更加清晰、易于维护和扩展。
- 灵活性:装饰器可以在运行时动态地应用或移除,这为程序提供了很大的灵活性。可以根据不同的需求和场景,选择是否应用特定的装饰器,或者组合使用多个装饰器来实现更复杂的功能。
性能测量:装饰器可以用来测量函数的执行时间,以便进行性能优化。
权限验证:在 Web 开发中,可以使用装饰器来验证用户的权限,确保只有授权用户才能访问某些函数。
缓存:可以使用装饰器来实现函数结果的缓存,避免重复计算。
装饰器的引入能大大降低代码重复,增强功能,解偶等作用,这点和Java中的自定义注解有点类似,准确点说更像Spring中的aop面向切面编程。
假设现在有一个场景时每次获取人家的统计数据需要登录才能抓取,这个登录对象可能不定时会过期,为了保证业务正常每次去检查下登录状态。
定义一个类,主要负责在调用接口时是否已经登录,如果未登录就去登录。
class Login:
def __init__(self):
self.redis = Redis(type=environment).redis_connection()
def login(self):
lock_value = str(uuid.uuid4())
if not redis.set('lock:logging_in', lock_value, nx=True, ex=30):
logger.info("正在登录,跳过此次调用")
return None
try:
fx = StaticMemory.get_fx()
if fx is None or fx.session.session_status == 'SESSION_LOST':
logger.info("登录")
fx = Client(
USER_ID=",
USER_PASSWORD="",
URL="登录地址",
CONNECTION="Real",
).login(self.session_status_changed)
logger.info(f"获取登录对象: {fx}")
StaticMemory.set_fx(fx)
except Exception as e:
logger.info(f"登录失败: {e}")
# 获取计数器
error_count = redis.incr("error_count")
if error_count == 1:
redis.expire("error_count", 120) # 只在第一次设置过期时间
if error_count > 3:
redis.publish("restart_channel", "restart")
redis.set("error_count", 0) # 重置 error_count
logger.info("登录报错正在发送重启")
traceback.print_exc()
finally:
if redis.get('lock:logging_in') == lock_value:
redis.delete('lock:logging_in')
return StaticMemory.get_fx()
@staticmethod
def ensure_logged_in(func):
is_logging_in = redis.get("lock:logging_in")
if is_logging_in:
logger.info("正在登录,跳过此次调用")
return None
def wrapper(*args, **kwargs):
# logger.info(f"Decorated function:{func}")
fx = StaticMemory.get_fx()
logger.info(f"检查登录状态: {fx.session.session_status if fx else 'None'}")
logger.info(f"检查登录内存信息: {fx.session if fx else 'None'}")
if fx is None or fx.session.session_status == 'SESSION_LOST':
is_logging_in = redis.get("lock:logging_in")
if is_logging_in is None and StaticMemory.get_fx() is None:
FxcmLogin().login()
return func(*args, **kwargs)
return wrapper
在别处使用的时候就是这样@Login.ensure_logged_in就可以了,后续获取数据就会去检查登录对象是否为空,为空就重新请求达到session续约的目的。
@Login.ensure_logged_in
def update_position_info(object, instrument):
trades_position = GetTradesPosition(fx).get_trades_position_instrument(instrument)
object.update_position_info(trades_position)
del trades_position