TestController.index
завершает экземпляр Test
без доступа к объекту TestController
. Кроме того, только пользовательские методы (которые должны быть функциями, а не объектами) имеют атрибут im_func
. Вам нужно будет создать экземпляр Test
и заставить его метод __call__
вернуть функцию, чтобы ему можно было передать экземпляр TestController
.
class Test(object):
def __call__( self, f):
def wrapper(self, *args, **kwargs):
# anything in the old Test.__call__ goes here.
return f(self, *args, **kwargs)
return wrapper
class TestController(BaseController):
@Test()
def index(self):
return 'hello world'
Что происходит
Декоратор:
@decorator
def foo(...):
эквивалентно:
def foo(...):
...
foo = decorator(foo)
В вашем исходном коде,
@Test
def index(self):
создает экземпляр Test
и передает index
в конструктор. Полученный объект присваивается свойству index
TestController
.
class TestController(BaseController)
def index(self):
...
index = Test(index)
Test.__call__
не вызывается, пока вы не попытаетесь позвонить TestController.index
. С tc
экземпляр TestController
, tc.index()
эквивалентен tc.index.__call__()
или Test.__call__(tc.index)
.
Проблема в том, что при вызове Test.__call__
мы потеряли ссылку на tc
. Его не существовало, когда было определено Test.index
, поэтому нет способа его сохранить. Более того, похоже, что Pylons выполняет какое-то волшебство с методами, и ожидает, что tc.index
будет пользовательским методом (который имеет свойство im_func
), а не объектом (который не имеет).
Подход, который я показываю вам, меняется, когда вызывается Test.__call__
и тип TestController.index
.
class Test(object):
def __call__( self, f):
# if done properly, __call__ will get invoked when the decorated method
# is defined, not when it's invoked
print 'Test.__call__'
def wrapper(self, *args, **kwargs):
# wrapper will get invoked instead of the decorated method
print 'wrapper in Test.__call__'
return f(self, *args, **kwargs)
return wrapper
Определение TestController.index
эквивалентно:
class TestController(BaseController):
def index(self):
...
index = Test()(index) # note: Test.__call__ is invoked here.
# 'index' is now 'wrapper' from Test.__call__
tc = TestController
tc.index() # wrapper from Test.__call__ is invoked here
Поскольку TestController.index
является функцией, а не объектом, tc.index()
эквивалентно TestController.index(tc)
, и мы не теряем ссылку на tc
.