views:

127

answers:

3

I get this error

object has no attribute 'im_func'

with this

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

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

pylons code:

class TestController(BaseController):

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

full error:

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'
+1  A: 

See http://pylonshq.com/project/pylonshq/ticket/589 ?

Is there any monkeypatching or other weirdness going on when you call it? Full traceback and source for the caller would really help here.

Enki
using the standard pylons template
Timmy
+3  A: 

TestController.index ends up an instance of Test, with no access to the TestController object. Also, only user defined methods (which must be functions, not objects) have an im_func attribute. You'll need to instantiate Test and have its __call__ method return a function so that it can be passed the TestController instance.

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'

What's happening

A decorator:

@decorator
def foo(...):

is equivalent to:

def foo(...):
    ...
foo = decorator(foo)

In your original code,

    @Test
    def index(self):

creates an instance of Test and passes index to the constructor. The resulting object is assigned to the index property of TestController.

class TestController(BaseController)
    def index(self):
        ...
    index = Test(index)

Test.__call__ doesn't get invoked until you try to call TestController.index. With tc an instance of TestController, tc.index() is equivalent to tc.index.__call__() or Test.__call__(tc.index).

The problem is that within the call to Test.__call__, we've lost the reference to tc. It didn't exist when Test.index was defined, so there's no way of saving it. Moreover, it looks like Pylons performs some magic on the methods and it expects tc.index to be a user defined method (which has an im_func property), not an object (which doesn`t).

The approach I show you changes when Test.__call__ is invoked and the type of 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

The definition of TestController.index is equivalent to:

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

Because TestController.index is a function rather than an object, tc.index() is equivalent to TestController.index(tc), and we don't lose the reference to tc.

outis
thanks, this fixes the error, but how does it work? im a python beginner
Timmy
+1  A: 

To understand why this does not work as you expect, you have to understand how methods work in Python. When an attribute is looked up, its __get__ method is called (if it exists) and what that returns is used instead of the attribute itself. The main use for this is implementing methods, special sorts of methods (like classmethods), properties and the like. There are similarly hooks for setting and deleting attributes, and it's all explained on http://www.python.org/download/releases/2.2.3/descrintro/

Functions already have __get__ magic built in, so they work as methods automatically, making a bound method passing the current instance when they are looked up. A class you defines does not automatically have this, so you have to define it manually, as such:

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()

As far as the actual error you are getting, I'm not 100% sure why you do. I wonder if it is not Pylons weirdness I don't know about. The entirety of the relevant file(s) and the full traceback can go a long way to helping people diagnose problems.

Mike Graham