【问题标题】:Class method decorator with self arguments?带有自参数的类方法装饰器?
【发布时间】:2023-03-29 19:44:02
【问题描述】:

如何将类字段作为参数传递给类方法上的装饰器?我想做的是:

class Client(object):
    def __init__(self, url):
        self.url = url

    @check_authorization("some_attr", self.url)
    def get(self):
        do_work()

它抱怨 self 不存在将self.url 传递给装饰器。有没有办法解决这个问题?

【问题讨论】:

  • 这是您可以控制的自定义装饰器,还是您无法更改的自定义装饰器?
  • 它是我的装饰器,所以我可以完全控制它
  • 它在初始化之前被调用我认为是问题......
  • 问题是 self 在函数定义时不存在。你需要把它变成一个偏函数。

标签: python python-decorators


【解决方案1】:

你不能。类主体中没有self,因为不存在实例。您需要传递它,例如,包含要在实例上查找的属性名称的 str,然后返回的函数可以执行此操作,或者完全使用不同的方法。

【讨论】:

    【解决方案2】:

    是的。不要在类定义时传入实例属性,而是在运行时检查它:

    def check_authorization(f):
        def wrapper(*args):
            print args[0].url
            return f(*args)
        return wrapper
    
    class Client(object):
        def __init__(self, url):
            self.url = url
    
        @check_authorization
        def get(self):
            print 'get'
    
    >>> Client('http://www.google.com').get()
    http://www.google.com
    get
    

    装饰器拦截方法参数;第一个参数是实例,因此它从中读取属性。如果您不想硬编码属性名称,您可以将属性名称作为字符串传递给装饰器并使用getattr

    def check_authorization(attribute):
        def _check_authorization(f):
            def wrapper(self, *args):
                print getattr(self, attribute)
                return f(self, *args)
            return wrapper
        return _check_authorization
    

    【讨论】:

    • 有没有办法直接在装饰器中传递@staticmethod? (一般来说)。我发现我们不能在装饰器中引用 Even 类。
    • @ShivKrishnaJaiswal 直接在装饰器中传递@staticmethod 到底是什么意思?您可以通过使用 @staticmethod 装饰器来摆脱对象引用要求,但是,它不会解决 OP 的问题......当然,您可以在装饰器中将包装器装饰为 @staticmethod 并且如果使用它应该可以工作正确(在 python 3.9 上测试),但我认为没有理由这样做。这样的装饰器将无法在没有类的函数上使用。此外,如果需要,您甚至可以在已经修饰的方法上使用@staticmethod...
    【解决方案3】:
    from re import search
    from functools import wraps
    
    def is_match(_lambda, pattern):
        def wrapper(f):
            @wraps(f)
            def wrapped(self, *f_args, **f_kwargs):
                if callable(_lambda) and search(pattern, (_lambda(self) or '')): 
                    f(self, *f_args, **f_kwargs)
            return wrapped
        return wrapper
    
    class MyTest(object):
    
        def __init__(self):
            self.name = 'foo'
            self.surname = 'bar'
    
        @is_match(lambda x: x.name, 'foo')
        @is_match(lambda x: x.surname, 'foo')
        def my_rule(self):
            print 'my_rule : ok'
    
        @is_match(lambda x: x.name, 'foo')
        @is_match(lambda x: x.surname, 'bar')
        def my_rule2(self):
            print 'my_rule2 : ok'
    
    
    
    test = MyTest()
    test.my_rule()
    test.my_rule2()
    

    输出: my_rule2:好的

    【讨论】:

    • @raphael 在此设置中,我似乎无法访问 _lambda 或模式。我该如何补救。
    • @Raphael:我怎样才能对类方法做同样的事情,因为这里所有的方法都是实例方法。
    【解决方案4】:

    一个更简洁的例子可能如下:

    #/usr/bin/env python3
    from functools import wraps
    
    def wrapper(method):
        @wraps(method)
        def _impl(self, *method_args, **method_kwargs):
            method_output = method(self, *method_args, **method_kwargs)
            return method_output + "!"
        return _impl
    
    class Foo:
        @wrapper
        def bar(self, word):
            return word
    
    f = Foo()
    result = f.bar("kitty")
    print(result)
    

    将打印的内容:

    kitty!
    

    【讨论】:

    • IMO,这优于stackoverflow.com/a/11731208/257924。它演示了内部函数_impl 如何访问self 以出于任何目的操纵self。我需要构建一个简单的方法装饰器,它在类中方法的子集 上增加self.id,并且只在类中应用了“@”装饰语法的那些方法。与stackoverflow.com/a/56322968/257924 相比,语法糖将其支付给我的未来自我,后者放弃了糖并要求我深入了解__init__ 方法。
    • 这对我很有帮助。谢谢。
    【解决方案5】:

    另一种选择是放弃语法糖并在类的__init__ 中进行装饰。

    def countdown(number):
        def countdown_decorator(func):
            def func_wrapper():
                for index in reversed(range(1, number+1)):
                    print(index)
                func()
            return func_wrapper
        return countdown_decorator
    
    class MySuperClass():
        def __init__(self, number):
            self.number = number
            self.do_thing = countdown(number)(self.do_thing)
        
        def do_thing(self):
            print('im doing stuff!')
    
    
    myclass = MySuperClass(3)
    
    myclass.do_thing()
    

    会打印出来

    3
    2
    1
    im doing stuff!
    

    【讨论】:

    • 这个实用多了。例如。投票最多的示例将“url”属性硬编码到装饰器定义中。
    【解决方案6】:

    我知道这是一个老问题,但尚未提及此解决方案,希望即使在 8 年后的今天,它也能对某人有所帮助。

    那么,包装一个包装器怎么样?让我们假设一个人不能change the decorator 也不能decorate those methods in init (他们可能是@property 装饰或其他)。总是有可能创建自定义的、特定于类的装饰器,该装饰器将捕获自身并随后调用原始装饰器,将运行时属性传递给它。

    这是一个工作示例 (f-strings require python 3.6):

    import functools
    
    # imagine this is at some different place and cannot be changed
    def check_authorization(some_attr, url):
            def decorator(func):
                    @functools.wraps(func)
                    def wrapper(*args, **kwargs):
                            print(f"checking authorization for '{url}'...")
                            return func(*args, **kwargs)
                    return wrapper
            return decorator
    
    # another dummy function to make the example work
    def do_work():
            print("work is done...")
    
    ###################
    # wrapped wrapper #
    ###################
    def custom_check_authorization(some_attr):
            def decorator(func):
                    # assuming this will be used only on this particular class
                    @functools.wraps(func)
                    def wrapper(self, *args, **kwargs):
                            # get url
                            url = self.url
                            # decorate function with original decorator, pass url
                            return check_authorization(some_attr, url)(func)(self, *args, **kwargs)
                    return wrapper
            return decorator
            
    #############################
    # original example, updated #
    #############################
    class Client(object):
            def __init__(self, url):
                    self.url = url
        
            @custom_check_authorization("some_attr")
            def get(self):
                    do_work()
    
    # create object
    client = Client(r"https://stackoverflow.com/questions/11731136/class-method-decorator-with-self-arguments")
    
    # call decorated function
    client.get()
    

    输出:

    checking authorisation for 'https://stackoverflow.com/questions/11731136/class-method-decorator-with-self-arguments'...
    work is done...
    

    【讨论】:

      【解决方案7】:

      我知道这个问题已经很老了,但是之前没有提出过以下解决方法。这里的问题是你不能在类块中访问self,但你可以在类方法中。

      让我们创建一个虚拟装饰器来重复一个函数。

      import functools
      def repeat(num_rep):
          def decorator_repeat(func):
              @functools.wraps(func)
              def wrapper_repeat(*args, **kwargs):
                  for _ in range(num_rep):
                      value = func(*args, **kwargs)
                  return 
              return wrapper_repeat
          return decorator_repeat
      
      class A:
          def __init__(self, times, name):
              self.times = times
              self.name = name
          
          def get_name(self):
              @repeat(num_rep=self.times)
              def _get_name():
                  print(f'Hi {self.name}')
              _get_name()
      

      【讨论】:

        猜你喜欢
        • 2014-10-08
        • 1970-01-01
        • 2012-03-14
        • 2014-07-21
        • 2014-03-24
        • 2023-03-17
        相关资源
        最近更新 更多