Ошибка с Python Decorator - PullRequest
       17

Ошибка с Python Decorator

0 голосов
/ 16 апреля 2010

Я получаю эту ошибку

object has no attribute 'im_func'

с этим

class Test(object):
    def __init__(self, f):
        self.func = f

    def __call__( self, *args ):
        return self.func(*args)

код пилонов:

class TestController(BaseController):

    @Test
    def index(self):
        return 'hello world'

полная ошибка:

File '/env/lib/python2.5/site-packages/WebError-0.10.2-py2.5.egg/weberror/evalexception.py', line 431 in respond
  app_iter = self.application(environ, detect_start_response)
File '/env/lib/python2.5/site-packages/repoze.who-1.0.18-py2.5.egg/repoze/who/middleware.py', line 107 in __call__
  app_iter = app(environ, wrapper.wrap_start_response)
File '/env/lib/python2.5/site-packages/Beaker-1.5.3-py2.5.egg/beaker/middleware.py', line 73 in __call__
  return self.app(environ, start_response)
File '/env/lib/python2.5/site-packages/Beaker-1.5.3-py2.5.egg/beaker/middleware.py', line 152 in __call__
  return self.wrap_app(environ, session_start_response)
File '/env/lib/python2.5/site-packages/Routes-1.10.3-py2.5.egg/routes/middleware.py', line 130 in __call__
  response = self.app(environ, start_response)
File '/env/lib/python2.5/site-packages/Pylons-0.9.7-py2.5.egg/pylons/wsgiapp.py', line 125 in __call__
  response = self.dispatch(controller, environ, start_response)
File '/env/lib/python2.5/site-packages/Pylons-0.9.7-py2.5.egg/pylons/wsgiapp.py', line 324 in dispatch
  return controller(environ, start_response)
File '/project/lib/base.py', line 18 in __call__
  return WSGIController.__call__(self, environ, start_response)
File '/env/lib/python2.5/site-packages/Pylons-0.9.7-py2.5.egg/pylons/controllers/core.py', line 221 in __call__
  response = self._dispatch_call()
File '/env/lib/python2.5/site-packages/Pylons-0.9.7-py2.5.egg/pylons/controllers/core.py', line 172 in _dispatch_call
  response = self._inspect_call(func)
File '/env/lib/python2.5/site-packages/Pylons-0.9.7-py2.5.egg/pylons/controllers/core.py', line 80 in _inspect_call
  argspec = cached_argspecs[func.im_func]
AttributeError: 'Test' object has no attribute 'im_func'

Ответы [ 3 ]

4 голосов
/ 16 апреля 2010

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.

1 голос
/ 17 апреля 2010

Чтобы понять, почему это не работает так, как вы ожидаете, вы должны понять, как методы работают в Python. При поиске атрибута вызывается его метод __get__ (если он существует), и вместо самого атрибута используется то, что возвращается. Основное использование для этого - реализация методов, специальных видов методов (таких как методы классов), свойств и тому подобное. Аналогичным образом есть хуки для установки и удаления атрибутов, и все это объяснено на http://www.python.org/download/releases/2.2.3/descrintro/

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

from functools import partial
class Test(object):
    def __init__(self, f):
        self.func = f

    def __call__(self, *args):
        return self.func(*args)

    def __get__(self, obj, objtype=None):
        if obj is not None:
            # Then the method was called on an instance, not the class itself
            return partial(self, obj)
            # Some people might find it easier to phrase this 
            # partial(self.func, obj) in this case, which would be equivalent. I 
            # prefer doing partial(self, obj) since then I can place all the 
            # logic for calling in one place.
        else:
            # The method was called on the class, not a particular instance, 
            # so we're not going to do anything special. Functions return 
            # unbound methods (which typecheck their first arguments) in this 
            # case, which I've always thought was an iffy approach.
            return self

class Foo(object):
    @Test
    def bar(self):
        return "hello world"

f = Foo()
print f.bar()

Что касается фактической ошибки 1010 *, которую вы получаете, я не уверен на 100%, почему вы это делаете. Интересно, не та ли это странность Пилона, о которой я не знаю. Совокупность соответствующих файлов и полная трассировка могут значительно помочь людям в диагностике проблем.

1 голос
/ 16 апреля 2010

См. http://pylonshq.com/project/pylonshq/ticket/589?

Когда вы называете это, происходит ли какая-нибудь мартышка или другая странность? Полный трекбэк и источник для вызывающего абонента действительно помогут здесь.

...