代码之家  ›  专栏  ›  技术社区  ›  Gonçalo

如何装饰实例方法并避免实例之间共享闭包环境

  •  2
  • Gonçalo  · 技术社区  · 1 年前

    我很难找到解决这个问题的办法。每当我们装饰类的方法时,该方法还没有绑定到任何实例,所以说我们有:

    from functools import wraps
    
    def decorator(f):
        closure_variable = 0
    
        @wraps(f)
        def wrapper(*args, **kwargs):
            nonlocal closure_variable
            closure_variable += 1
            print(closure_variable)
            f(*args, **kwargs)
            return
    
        return wrapper
    
    class ClassA:
        @decorator
        def decorated_method(self):
            pass
    
    

    这导致了一些有趣的事情,这些都是 ClassA 绑定到相同的闭包环境。

    inst1 = ClassA()
    inst2 = ClassA()
    inst3 = ClassA()
    
    inst1.decorated_method()
    inst2.decorated_method()
    inst3.decorated_method()
    
    

    以上行将输出:

    1
    2
    3
    

    现在到了我手头的问题,我已经创建了一个decorator,它缓存一个令牌,并且只在令牌过期后请求一个新的令牌。这个decorator被应用于一个名为的类的方法 TokenSupplier . 我意识到了这种行为,我显然不希望这种情况发生,我能解决这个问题并将装饰器设计模式保留在这里吗?

    我曾想过在闭包环境中存储一个字典,并使用实例哈希来索引所需的数据,但我认为我可能只是缺少了一些更基本的东西。 我的目标是让每个实例都有自己的闭包环境,但仍然能够使用decorator模式来装饰不同的未来 代币供应商 实现。

    提前谢谢!

    1 回复  |  直到 1 年前
        1
  •  2
  •   user24714692    1 年前

    为了避免在所有实例之间共享缓存(这可能不是必需的或所需的),最好为每个实例都有一个带有到期时间等的缓存。换句话说,我们不需要为所有实例都有“单一源缓存”。

    在下面的实现中,类的每个实例都初始化自己的缓存 dict() 以存储令牌、其到期时间和其他相关信息,这将赋予您完全的控制权。

    from functools import wraps
    import time
     
     
    class TokenCacheDecorator:
        def __init__(self, get_token_func):
            self.get_token_func = get_token_func
     
        def __get__(self, inst, owner):
            if inst is None:
                return self
     
            @wraps(self.get_token_func)
            def wrapper(*args, **kwargs):
                if not hasattr(inst, '_token_cache') or inst._token_cache['expiration_time'] < time.time():
                    print(f"[{id(inst)}] Cache miss")
                    token, expires_in = self.get_token_func(inst, *args, **kwargs)
                    inst._token_cache = {
                        'token': token,
                        'expiration_time': time.time() + expires_in
                    }
                    print(f"[{id(inst)}] New token - {token} expiration time: {inst._token_cache['expiration_time']}")
     
                return inst._token_cache['token']
     
            return wrapper
     
     
    class ClassA:
        def __init__(self, token, expires_in):
            self.token = token
            self.expires_in = expires_in
            self._token_cache = {'token': None, 'expiration_time': 0}
     
        @TokenCacheDecorator
        def get_token(self):
            return self.token, self.expires_in
     
     
    inst1 = ClassA("token1", 2)
    inst2 = ClassA("token2", 2)
    inst3 = ClassA("token3", 2)
     
    print(inst1.get_token())
    print(inst2.get_token())
    print(inst3.get_token())
     
    time.sleep(3)
     
    print(inst1.get_token())
    print(inst2.get_token())
    print(inst3.get_token())
    
    

    打印

    [4439687776] Cache miss
    [4439687776] New token - token1 expiration time: 1716693215.503801
    token1
    [4440899024] Cache miss
    [4440899024] New token - token2 expiration time: 1716693215.503846
    token2
    [4440899360] Cache miss
    [4440899360] New token - token3 expiration time: 1716693215.503862
    token3
    [4439687776] Cache miss
    [4439687776] New token - token1 expiration time: 1716693218.5076532
    token1
    [4440899024] Cache miss
    [4440899024] New token - token2 expiration time: 1716693218.50767
    token2
    [4440899360] Cache miss
    [4440899360] New token - token3 expiration time: 1716693218.507679
    token3
    
    推荐文章