tuna
tuna

Reputation: 6351

Python Decorators - <Classname> object has no attribute '__name__'

I have a tornado method like below and I tried to decorate method to cache stuff. I have the following setup

def request_cacher(x):
    def wrapper(funca):
        @functools.wraps(funca)
        @asynchronous
        @coroutine
        def wrapped_f(self, *args, **kwargs):
            pass

        return wrapped_f
    return wrapper

class PhotoListHandler(BaseHandler):
    @request_cacher
    @auth_required
    @asynchronous
    @coroutine
    def get(self):
        pass

I am receiving the error, AttributeError: 'PhotoListHandler' object has no attribute '__name__' Any ideas?

Upvotes: 0

Views: 3115

Answers (3)

Bakuriu
Bakuriu

Reputation: 102029

The issue is that you defined your request_cacher decorator as a decorator with arguments but you forgot to pass the argument!

Consider this code:

import functools


def my_decorator_with_argument(useless_and_wrong):
    def wrapper(func):
        @functools.wraps(func)
        def wrapped(self):
            print('wrapped!')
        return wrapped
    return wrapper


class MyClass(object):
    @my_decorator_with_argument
    def method(self):
        print('method')


    @my_decorator_with_argument(None)
    def method2(self):
       print('method2')

When you try to use method in an instance you get:

>>> inst = MyClass()
>>> inst.method    # should be the wrapped function, not wrapper!
<bound method MyClass.wrapper of <bad_decorator.MyClass object at 0x7fed32dc6f50>>
>>> inst.method()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "bad_decorator.py", line 6, in wrapper
    @functools.wraps(func)
  File "/usr/lib/python2.7/functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'MyClass' object has no attribute '__name__'

With the correct usage of the decorator:

>>> inst.method2()
wrapped!

Alternative fix is remove one layer from the decorator:

def my_simpler_decorator(func):
    @functools.wraps(func)
    def wrapped(self):
        print('wrapped!')
    return wrapped


class MyClass(object):

    @my_simpler_decorator
    def method3(self):
        print('method3')

And you can see that it does not raise the error:

>>> inst = MyClass()
>>> inst.method3()
wrapped!

Upvotes: 5

A. Jesse Jiryu Davis
A. Jesse Jiryu Davis

Reputation: 24007

Your code throws from functools.wraps(funca), so funca must be a PhotoListHandler instance instead of a wrapped get method as you intend. I believe this means that the next decorator down the stack, auth_required, is written incorrectly: auth_required is returning self instead of returning a function.

While I'm here: stacking a cache on top of an authenticated function looks wrong to me. Won't the first authenticated user's photo list be cached and then shown to all subsequent users?

Upvotes: 0

ybrs
ybrs

Reputation: 24

i think this might work for you,

import tornado.ioloop
import tornado.web
from tornado.gen import coroutine
from functools import wraps


cache = {}

class cached(object):
    def __init__ (self, rule, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
        self.rule = rule
        cache[rule] = 'xxx'

    def __call__(self, fn):
        def newf(*args, **kwargs):
            slf = args[0]
            if cache.get(self.rule):
                slf.write(cache.get(self.rule))
                return
            return fn(*args, **kwargs)
        return newf 


class MainHandler(tornado.web.RequestHandler):

    @coroutine
    @cached('/foo')
    def get(self):
        print "helloo"
        self.write("Hello, world")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

Upvotes: 0

Related Questions