“在代码的世界里,每一行都是进步的足迹,每一次挑战都是成长的机遇。”

Python装饰器函数

装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数。这个新函数通常会在调用原函数之前或之后执行一些额外的操作。日志记录:可以使用装饰器在函数执行前后记录日志信息,例如函数的名称、参数、执行时间等。

Python 中可以被认为既是一种设计模式的体现,也是一种编程思想的体现。

一、作为设计模式

装饰器体现了装饰器模式。装饰器模式允许在不改变原有对象结构的情况下,动态地给对象添加新的功能。在 Python 中,装饰器就是通过将一个函数包装在另一个函数中,在不修改被装饰函数内部代码的情况下,为其添加额外的行为,这与装饰器模式的理念一致。

例如,用装饰器为一个函数添加日志记录功能,就如同给这个函数 “装饰” 上了日志记录的特性,而不改变函数本身的核心业务逻辑。

二、作为编程思想

  1. 代码复用:装饰器提供了一种方便的方式来复用代码。可以将一些通用的功能(如日志记录、性能测量、权限验证等)封装在装饰器中,然后应用到多个不同的函数上,避免了在每个函数中重复编写相同的代码。
  2. 分离关注点:它有助于将业务逻辑和横切关注点(如日志、安全等)分离开来。业务函数可以专注于实现核心业务逻辑,而装饰器负责处理与业务逻辑不直接相关的辅助功能。这样可以使代码更加清晰、易于维护和扩展。
  3. 灵活性:装饰器可以在运行时动态地应用或移除,这为程序提供了很大的灵活性。可以根据不同的需求和场景,选择是否应用特定的装饰器,或者组合使用多个装饰器来实现更复杂的功能。

性能测量:装饰器可以用来测量函数的执行时间,以便进行性能优化。

权限验证:在 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