【问题标题】:Python class decorator argumentsPython 类装饰器参数
【发布时间】:2011-11-21 11:12:35
【问题描述】:

我正在尝试将可选参数传递给我在 python 中的类装饰器。 在我目前拥有的代码下方:

class Cache(object):
    def __init__(self, function, max_hits=10, timeout=5):
        self.function = function
        self.max_hits = max_hits
        self.timeout = timeout
        self.cache = {}

    def __call__(self, *args):
        # Here the code returning the correct thing.


@Cache
def double(x):
    return x * 2

@Cache(max_hits=100, timeout=50)
def double(x):
    return x * 2

第二个带参数的装饰器覆盖默认装饰器(max_hits=10, timeout=5 在我的__init__ 函数中)不起作用,我得到了异常TypeError: __init__() takes at least 2 arguments (3 given)。我尝试了许多解决方案并阅读了有关它的文章,但在这里我仍然无法使其工作。

有解决这个问题的办法吗?谢谢!

【问题讨论】:

    标签: python arguments decorator


    【解决方案1】:

    @Cache(max_hits=100, timeout=50) 调用 __init__(max_hits=100, timeout=50),所以你不满足 function 参数。

    您可以通过检测函数是否存在的包装方法来实现您的装饰器。如果它找到一个函数,它可以返回 Cache 对象。否则,它可以返回一个将用作装饰器的包装函数。

    class _Cache(object):
        def __init__(self, function, max_hits=10, timeout=5):
            self.function = function
            self.max_hits = max_hits
            self.timeout = timeout
            self.cache = {}
    
        def __call__(self, *args):
            # Here the code returning the correct thing.
    
    # wrap _Cache to allow for deferred calling
    def Cache(function=None, max_hits=10, timeout=5):
        if function:
            return _Cache(function)
        else:
            def wrapper(function):
                return _Cache(function, max_hits, timeout)
    
            return wrapper
    
    @Cache
    def double(x):
        return x * 2
    
    @Cache(max_hits=100, timeout=50)
    def double(x):
        return x * 2
    

    【讨论】:

    • 感谢大家和@lunixbochs 的解决方案!像魅力一样工作:)
    • 如果开发者使用位置参数而不是关键字参数调用Cache(例如@Cache(100,50)),那么function 将被赋值为100,max_hits 50。不会出现错误直到函数被调用。这可能被认为是令人惊讶的行为,因为大多数人都期望统一的位置和关键字语义。
    • 如果我在对象实例方法上使用@Cache 装饰器,那么_Cache__call__ 方法不会接收到装饰对象的自引用。在这种情况下不起作用。
    • 哇。这适用于常规功能
    【解决方案2】:
    @Cache
    def double(...): 
       ...
    

    等价于

    def double(...):
       ...
    double=Cache(double)
    

    虽然

    @Cache(max_hits=100, timeout=50)
    def double(...):
       ...
    

    等价于

    def double(...):
        ...
    double = Cache(max_hits=100, timeout=50)(double)
    

    Cache(max_hits=100, timeout=50)(double) 的语义与Cache(double) 截然不同。

    试图让Cache 处理这两个用例是不明智的。

    您可以改用装饰器工厂,它可以采用可选的max_hitstimeout 参数,并返回一个装饰器:

    class Cache(object):
        def __init__(self, function, max_hits=10, timeout=5):
            self.function = function
            self.max_hits = max_hits
            self.timeout = timeout
            self.cache = {}
    
        def __call__(self, *args):
            # Here the code returning the correct thing.
    
    def cache_hits(max_hits=10, timeout=5):
        def _cache(function):
            return Cache(function,max_hits,timeout)
        return _cache
    
    @cache_hits()
    def double(x):
        return x * 2
    
    @cache_hits(max_hits=100, timeout=50)
    def double(x):
        return x * 2
    

    PS。如果Cache类除了__init____call__之外没有其他方法,你可以把_cache函数里面的代码全部移走,把Cache全部去掉。

    【讨论】:

    • 不明智与否...如果开发人员确实不小心使用了@cache 而不是cache(),当他们尝试调用生成的函数时会出现奇怪的错误。另一个实现实际上同时用作缓存和缓存()
    • @lunixbochs:将cache_hits (nee cache) 与cache_hits() 混淆的开发人员很可能将任何函数对象与函数调用混淆,或者将生成器与迭代器混淆。即使是经验中等的 Python 程序员也应该习惯于注意差异。
    【解决方案3】:

    我从这个问题中学到了很多东西,谢谢大家。答案不就是把空括号放在第一个@Cache 上吗?然后可以将function参数移动到__call__

    class Cache(object):
        def __init__(self, max_hits=10, timeout=5):
            self.max_hits = max_hits
            self.timeout = timeout
            self.cache = {}
    
        def __call__(self, function, *args):
            # Here the code returning the correct thing.
    
    @Cache()
    def double(x):
        return x * 2
    
    @Cache(max_hits=100, timeout=50)
    def double(x):
        return x * 2
    

    虽然我觉得这种方式更简单更简洁:

    def cache(max_hits=10, timeout=5):
        def caching_decorator(fn):
            def decorated_fn(*args ,**kwargs):
                # Here the code returning the correct thing.
            return decorated_fn
        return decorator
    

    如果您在使用装饰器时忘记了括号,不幸的是直到运行时您仍然不会收到错误,因为外部装饰器参数传递给您尝试装饰的函数。然后在运行时内部装饰器抱怨:

    TypeError:caching_decorator() 只接受 1 个参数(给定 0)。

    但是,如果您知道装饰器的参数永远不会是可调用的,那么您可以抓住这一点:

    def cache(max_hits=10, timeout=5):
        assert not callable(max_hits), "@cache passed a callable - did you forget to parenthesize?"
        def caching_decorator(fn):
            def decorated_fn(*args ,**kwargs):
                # Here the code returning the correct thing.
            return decorated_fn
        return decorator
    

    如果你现在尝试:

    @cache
    def some_method()
        pass
    

    您会在声明时收到AssertionError

    总的来说,我发现这篇文章是在寻找装饰类的装饰器,而不是装饰类。如果其他人也这样做,this question 很有用。

    【讨论】:

      【解决方案4】:

      我宁愿将包装器包含在类的 __call__ 方法中:

      更新: 这个方法已经在python 3.6中测试过了,所以我不确定是更高版本还是更低版本。

      class Cache:
          def __init__(self, max_hits=10, timeout=5):
              # Remove function from here and add it to the __call__
              self.max_hits = max_hits
              self.timeout = timeout
              self.cache = {}
      
          def __call__(self, function):
              def wrapper(*args):
                  value = function(*args)
                  # saving to cache codes
                  return value
              return wrapper
      
      @Cache()
      def double(x):
          return x * 2
      
      @Cache(max_hits=100, timeout=50)
      def double(x):
          return x * 2
      

      【讨论】:

      • 你有没有尝试过装饰后调用函数?我觉得这个方法行不通。
      • @AK12 你试过了吗,或者你只是认为它不起作用? Cz 我正在使用这种方法并且效果很好。
      • 我已经尝试过并且遇到了错误。当我尝试调用 double 方法时发生错误。
      • @AK12 如果你复制了整个类并且问题仍然存在,那么我怀疑这个问题可能是我们的 Python 版本不同的原因。
      • 在这个线程的所有提案中,这种方式是最简单和最容易使用的。谢谢@AlexJolig
      【解决方案5】:

      定义带有可选参数的装饰器:

      from functools import wraps, partial             
      def _cache(func=None, *, instance=None):         
          if func is None:                             
              return partial(_cache, instance=instance)
          @wraps(func)                                 
          def wrapper(*ar, **kw):                      
              print(instance)                          
              return func(*ar, **kw)                   
          return wrapper         
      

      并将instance 对象传递给__call__ 中的装饰器,或者使用在每个__call__ 上实例化的其他帮助类。这样你就可以使用没有括号的装饰器,带参数,甚至在代理缓存类中定义一个__getattr__ 来应用一些参数。

      class Cache:                                   
          def __call__(self, *ar, **kw):             
              return _cache(*ar, instance=self, **kw)
                                                     
      cache = Cache()                                
                                                     
      @cache                                         
      def f(): pass                                  
      f() # prints <__main__.Cache object at 0x7f5c1bde4880>
      
                                             
      
                        
      

      【讨论】:

        【解决方案6】:

        您可以将类方法用作工厂方法,这应该可以处理所有用例(带或不带括号)。

        import functools
        class Cache():
            def __init__(self, function):
                functools.update_wrapper(self, function)
                self.function = function
                self.max_hits = self.__class__.max_hits
                self.timeout = self.__class__.timeout
                self.cache = {}
        
            def __call__(self, *args):
                # Here the code returning the correct thing.
            
            @classmethod
            def Cache_dec(cls, _func = None, *, max_hits=10, timeout=5):
                cls.max_hits = max_hits
                cls.timeout = timeout
                if _func is not None: #when decorator is passed parenthesis
                    return cls(_func)
                else:
                    return cls    #when decorator is passed without parenthesis
               
        
        @Cache.Cache_dec
        def double(x):
            return x * 2
        
        @Cache.Cache_dec()
        def double(x):
            return x * 2
        
        @Cache.Cache_dec(timeout=50)
        def double(x):
            return x * 2
        
        @Cache.Cache_dec(max_hits=100)
        def double(x):
            return x * 2
        
        @Cache.Cache_dec(max_hits=100, timeout=50)
        def double(x):
            return x * 2
        

        【讨论】:

        • 但是对于装饰器的每个应用程序,您最终都会得到相同的类实例。这是一个问题,因为每个装饰函数都使用相同的参数集。
        猜你喜欢
        • 2014-03-24
        • 1970-01-01
        • 2014-07-21
        • 2023-03-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-25
        • 2011-04-28
        相关资源
        最近更新 更多