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

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

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

它抱怨自我不存在是为了传递自我。指向装饰器的Url。有办法解决这个问题吗?


当前回答

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

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

其他回答

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

拥有一个通用实用程序将非常有用,它可以将函数的任何装饰器转换为方法的装饰器。我想了一个小时,终于想出了一个:

from typing import Callable
Decorator = Callable[[Callable], Callable]

def decorate_method(dec_for_function: Decorator) -> Decorator:

    def dec_for_method(unbounded_method) -> Callable:
        # here, `unbounded_method` will be a unbounded function, whose
        # invokation must have its first arg as a valid `self`. When it 
        # return, it also must return an unbounded method.
        def decorated_unbounded_method(self, *args, **kwargs):
            @dec_for_function
            def bounded_method(*args, **kwargs):
                return unbounded_method(self, *args, **kwargs)
            return bounded_method(*args, **kwargs)

        return decorated_unbounded_method

    return dec_for_method

用法是:

# for any decorator (with or without arguments)
@some_decorator_with_arguments(1, 2, 3)
def xyz(...): ...

# use it on a method:
class ABC:
  @decorate_method(some_decorator_with_arguments(1, 2, 3))
  def xyz(self, ...): ...

测试:

def dec_for_add(fn):
    """This decorator expects a function: (x,y) -> int.

    If you use it on a method (self, x, y) -> int, it will fail at runtime.
    """
    print(f"decorating: {fn}")
    def add_fn(x,y):
        print(f"Adding {x} + {y} by using {fn}")
        return fn(x,y)
    return add_fn


@dec_for_add
def add(x,y):
    return x+y

add(1,2)  # OK!


class A:
    @dec_for_add
    def f(self, x, y):
        # ensure `self` is still a valid instance
        assert isinstance(self, A)
        return x+y

# TypeError: add_fn() takes 2 positional arguments but 3 were given
# A().f(1,2)
    

class A:
    @decorate_method(dec_for_add)
    def f(self, x, y):
        # ensure `self` is still a valid instance
        assert isinstance(self, A)
        return x+y

# Now works!!
A().f(1,2)

我知道这个问题已经很老了,但是下面的解决方法以前没有人提过。这里的问题是你不能在类块中访问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()

我知道这是一个老问题,但这个解决方案还没有被提及,希望它可以帮助到今天的人,8年后。

那么,如何包装包装?让我们假设不能改变装饰器,也不能装饰init中的这些方法(它们可能是@property decoration或其他)。总是有可能创建自定义的、特定于类的装饰器,该装饰器将捕获self并随后调用原始的装饰器,并将运行时属性传递给它。

下面是一个工作示例(f-string需要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...

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

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