Декоратор метода класса Python с собственными аргументами?
Как передать поле класса декоратору метода класса в качестве аргумента? Что я хочу сделать, это что-то вроде:
class Client(object):
def __init__(self, url):
self.url = url
@check_authorization("some_attr", self.url)
def get(self):
do_work()
Он жалуется, что self не существует для передачи self.url декоратору. Это можно обойти?
8 ответов
Да. Вместо передачи атрибута экземпляра во время определения класса, проверьте его во время выполнения:
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
Более краткий пример может быть следующим:
#/usr/bin/env python3
from functools import wraps
def wrapper(method):
@wraps(method)
def _impl(self, *method_args, **method_kwargs):
return method(self, *method_args, **method_kwargs) + "!"
return _impl
class Foo:
@wrapper
def bar(self, word):
return word
f = Foo()
result = f.bar("kitty")
print(result)
Который напечатает:
kitty!
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: хорошо
Другим вариантом было бы отказаться от синтаксического сахара и украсить в __init__
класса.
def countdown(number):
def countdown_decorator(func):
def func_wrapper():
for index in reversed(range(1, number+1)):
print("{}".format(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!
Я знаю, что эта проблема довольно старая, но решение, описанное ниже, ранее не предлагалось. Проблема в том, что вы не можете получить доступ
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 или что-то еще). Всегда есть возможность создать собственный декоратор для конкретного класса, который будет захватывать себя и впоследствии вызывать исходный декоратор, передавая ему атрибут времени выполнения.
Вот рабочий пример (для 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...
Ты не можешь Нет никаких 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)