Найти - Пользователи
Полная версия: декоратор метода должен быть функцией или в Питоне баг?
Начало » Python для новичков » декоратор метода должен быть функцией или в Питоне баг?
1
dimabest
имеем 2 аналогичных декоратора - первый реализован функцией, второй - классом:

class Decorator:
def __init__(self, method):
self.method = method

def __call__(self, *args):
print 'decorator-class arguments: ', args
return self.method(*args)


def decorator(method):
def inner(*args):
print 'decorator-function arguments: ', args
return method(*args)
return inner
Создаем объект простого класса и вызываем единственный метод:

class My:
def test(self, i):
return i+1

obj = My()
print obj.test(100)
Если метод test декорировать функцией decorator, то результат будет такой:

decorator-function arguments:  (<__main__.My instance at 0x01D1D878>, 100)
101
тоесть вложенная функция inner получает 2 параметра - ссылка на экземпляр класса My и число 100.

Если метод test декорировать классом Decorator, то результат будет такой:

decorator-class arguments:  (100,)
Traceback (most recent call last):
File "C:\Users\dima\Documents\NetBeansProjects\db\src\decorators.py", line 42, in <module>
print m.test(100)
File "C:\Users\dima\Documents\NetBeansProjects\db\src\decorators.py", line 9, in __call__
return self.method(*args)
TypeError: test() takes exactly 2 arguments (1 given)
тоесть метод декоратора __call__, который является аналогом вложенной функции inner, получает только 1 параметр.

Почему? это баг?
Александр Кошелев
dimabest
это баг?
Нет.
dimabest
Почему?
В первом случае после декорирования функция inner из декоратора становиться unbound методом test класса My. После инстанцирования этого класса, метод становиться bound, т.е. по сути появляется его карринг с объектом My в виде параметра self.

Во втором случае, атрибут test у класса main становится объектом класса Decorator и соответственно, как и любой другой атрибут данных, живет своей жизнь и при его “вызове” он получается через self себя и плюс параметр 100.

Ничего необычного.
dimabest
Daevaorn
Точно.

А как обойти такое поведение?

Я придумал решение - если декоратор принимает параметр - то имеем не 2, а 3 уровня вложенности и метод __call__ принимает такой же вид как функция decorator и первого поста:
class Decorator:
def __init__(self, param=None):
pass

def __call__(self, method):
def inner(*args):
print 'decorator-class arguments: ', args
return method(*args)
return inner
результат:
decorator-class arguments:  (<__main__.My instance at 0x01E1D940>, 100)
101
но при декорировании теперь нужно ставить скобки…
class My:
@Decorator()
def test(self, i):
return i+1
легко запутатся…

Есть ли более изящное решение?
Андрей Светлов
class Parametrized(object):
"""
A baseclass that allows to defer instance creation until
it gets a non-keyword argument (specifying the rule).

For example here's what it does to `compute`. A trivial case:

>>> compute(lambda:None)
compute(None)

`compute` can also take additional arguments:

>>> compute(lambda:None, resetting_to=0)
compute(None)

But we want to be able to use it as a decorator and still pass
additional arguments, so the following should work:

>>> compute(resetting_to=0)(lambda:None)
compute(None)

It worked because the first call was intercepted in
Parametrized.__new__ and returned the lambda that acts as a
real decorator:

>>> compute(resetting_to=0)
<function <lambda> at 0x00C343B0>

"""
def __new__(cls, *args, **kw):
if not args:
return lambda _: cls(_, **kw)
return object.__new__(cls)
Унаследуйтесь в своем декораторе от него. Но доп параметры нужно указывать по имени.
Или придумайте свой способ различения варианта “без параметров” от “с параметрами”.
Например, смотреть на callable первого аргумента, если это вам больше по душе.
dimabest
Спасибо, работает. Правда, я пока не понял как :)
Андрей Светлов
Подумай несколько раз, перечитай док-стринг…
Я тоже сразу не понял, и специально для меня-балбеса автор его такой длинный написал :)
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB