Декоратор метода класса с собственными аргументами?

Как передать поле класса декоратору метода класса в качестве аргумента? Я хочу сделать что-то вроде:

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

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

Он жалуется, что self не существует для передачи self.url декоратору. Это можно обойти?


person Mark    schedule 30.07.2012    source источник
comment
Это настраиваемый декоратор, который вы контролируете, или тот, который вы не можете изменить?   -  person Joel Cornett    schedule 31.07.2012
comment
Это мой декоратор, поэтому я полностью контролирую его   -  person Mark    schedule 31.07.2012
comment
Он вызывается до инициализации, я думаю, что проблема ...   -  person Joran Beasley    schedule 31.07.2012
comment
Проблема в том, что self не существует во время определения функции. Вам нужно превратить его в частичную функцию.   -  person Antimony    schedule 31.07.2012


Ответы (7)


да. Вместо того, чтобы передавать атрибут экземпляра во время определения класса, проверьте его во время выполнения:

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
person li.davidm    schedule 30.07.2012

Более краткий пример может быть следующим:

#/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!
person maxywb    schedule 29.04.2016
comment
IMO, это превосходит stackoverflow.com/a/11731208/257924. Он демонстрирует, как внутренняя функция _impl может обращаться к self, чтобы манипулировать этим self для любых целей. Мне нужно было создать простой декоратор методов, который увеличивал бы self.id в подмножестве методов в классе, и только тех методов в классе, к которым был применен синтаксис @ decoration. Этот синтаксический сахар приносит деньги моему будущему «Я» по сравнению с stackoverflow.com/a/56322968/257924, который отказался от этого сахар и требует, чтобы я глубоко заглянул внутрь метода __init__. - person bgoodr; 21.08.2020
comment
Это было мне очень полезно. Спасибо. - person Yaakov Bressler; 02.02.2021

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: ок

person Raphaël    schedule 03.05.2013
comment
@raphael В этой настройке я не могу получить доступ к _lambda или шаблону. Как я могу это исправить. - person Jonathan; 18.01.2018
comment
@Raphael: Как я могу сделать то же самое для метода класса, поскольку здесь все методы являются методами экземпляра. - person Apurva Kunkulol; 12.03.2018

Другой вариант - отказаться от синтаксического сахара и украсить __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!
person Arwalk    schedule 27.05.2019
comment
Это намного практичнее. Например. в примере, получившем наибольшее количество голосов, атрибут url жестко вставляется в определение декоратора. - person nwly; 06.01.2021

Вы не можете. В теле класса нет self, потому что не существует экземпляра. Вам нужно будет передать ему, скажем, str, содержащий имя атрибута, для поиска в экземпляре, что затем может выполнить возвращенная функция, или использовать другой метод полностью.

person Julian    schedule 30.07.2012

Я знаю, что это старый вопрос, но это решение еще не упоминалось, надеюсь, оно может помочь кому-то даже сегодня, через 8 лет.

Итак, как насчет упаковки обертки? Предположим, нельзя ни изменить декоратор, ни украсить эти методы в init (они могут быть украшены @property или чем-то еще). Всегда есть возможность создать собственный декоратор для конкретного класса, который будет захватывать себя и впоследствии вызывать исходный декоратор, передавая ему атрибут времени выполнения.

Вот рабочий пример (для f-строк требуется 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...
person Michal    schedule 04.02.2021

Я знаю, что эта проблема довольно старая, но решение, описанное ниже, ранее не предлагалось. Проблема здесь в том, что вы не можете получить доступ к 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()
person dguerrero    schedule 29.04.2021