Reputation: 12858
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'
Upvotes: 0
Views: 2666
Reputation: 76793
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.
Upvotes: 1
Reputation: 77450
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'
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
.
Upvotes: 4
Reputation: 302
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.
Upvotes: 1