shackra
shackra

Reputation: 366

Decorating methods: AttributeError: 'function' object has no attribute '__self__'?

I'm working with asyncio for scheduling methods to be called at certain relative times intervals. I decided to centralize the scheduling into one method of the class I wrote to reduce the chance of getting errors with the logic of my project.

Such method should be called every time a scheduled method finished. I though on adding loop.call_soon at the end of each method, but I decided to give a shot to decorators.

I wrote a class decorator and then applied it to some methods of my main class, wrote the rest of the logic and all that. But when trying to test my changes on my project I get an exception:

AttributeError: 'function' object has no attribute '__self__'

Somehow, decorating my method made it a function. This is something I cannot understand, why does this happened? how can I work around this without giving up decorators?

Here is a minimal, complete, and verifiable example of what I'm trying to do:

import asyncio
from datetime import datetime


class thinkagain:
    loop = asyncio.get_event_loop()

    def __init__(self, f):
        self.fun = f
        self.class_ = f.__self__

    def __call__(self):
        self.fun(*args, **kwords)
        # everything in Python is an object
        setattr(self.fun, "called", datetime.utcnow())
        self.loop.call_later(self.class_.think, 5 * 60)


class DoSomething:
    loop = asyncio.get_event_loop()

    @thinkagain
    def think(self):
        attr = getattr(self.dosomething, "called")
        if attr:
            elapsed = attr - datetime.utcnow()
            seconds = elapsed.seconds
        else:
            seconds = 99999

        if seconds >= 20 * 60:
            self.loop.call_soon(self.dosomething)

    @thinkagain
    def dosomething(self):
        print("I did something awesome!")

loop = asyncio.get_event_loop()
something = DoSomething()
loop.call_soon(something.think)
loop.run_forever()

and here is the exception I get:

Python 3.5.1 (default, Dec  7 2015, 13:41:59) 
[GCC 5.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/mcve.py", line 19, in <module>
    class DoSomething:
  File "/tmp/mcve.py", line 22, in DoSomething
    @thinkagain
  File "/tmp/mcve.py", line 10, in __init__
    self.class_ = f.__self__
AttributeError: 'function' object has no attribute '__self__'
>>> 

Upvotes: 5

Views: 10923

Answers (3)

Aman Goel
Aman Goel

Reputation: 55

You ight need to wrap the inner function again to get the reference to your self object. Here's a sample code that worked for me. And you need to use @time() when calling the decorator.

def timer():
    def inner(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            ts = time.time()
            result = func(self, *args, **kwargs)
            elapsed_time = time.time() - ts
            write_ot = f"Elapsed time for {self.__class__.__name__} is :{elapsed_time: 0.4f}\n"
            with open("profiler_timing.txt", 'a+') as f:
                f.write(write_ot)
            return result
        return wrapper
    return inner

Upvotes: -1

Vlad M
Vlad M

Reputation: 477

Regarding decorators, Graham Dumpleton gave excellent talk Advanced methods for creating decorators, discussing internal implementations of various decoration flavours and techniques. Highly recommended.

Relevant module he introduced at the end: https://github.com/GrahamDumpleton/wrapt

Never the less, i modified your example with two versions. Version below stores attributes directly in methods as you intended.

from datetime import datetime

class thinkagain:

    def __init__(self, f):
        # Plain function as argument to be decorated
        self.func = f

    def __get__(self, instance, owner):
        self.instance_ = instance
        return self.__call__

    def __call__(self, *args, **kwargs):
        """Invoked on every call of any decorated method"""

        # set attribute directly within bound method
        bound_method = getattr(self.instance_, self.func.__name__)
        bound_method.__dict__['called'] = datetime.utcnow()

        # returning original function with class' instance as self
        return self.func(self.instance_, *args, **kwargs)


class DoSomething_A:

    @thinkagain
    def think(self, *args, **kwargs):
        print('\n%s' % locals())
        print(self.think.called, args, kwargs)
        self.dosomething()

    @thinkagain
    def dosomething(self):
        print('%s\n' % ('-'*30), locals())
        print("%s I did something awful" % self.dosomething.called)

Second version looks cleaner and it skips storing attributes inside methods and assigns them directly within the instance.

from datetime import datetime

class thinkagain:

    def __init__(self, f):
        # Plain function as argument to be decorated
        self.func = f

    def __get__(self, instance, owner):
        self.instance_ = instance
        return self.__call__

    def __call__(self, *args, **kwargs):
        """Invoked on every call of decorated method"""

        # set attribute on instance
        name = '%s_called' % self.func.__name__
        setattr(self.instance_, name, datetime.utcnow())

        # returning original function with class' instance as self
        return self.func(self.instance_, *args, **kwargs)


class DoSomething_B:

    @thinkagain
    def think(self, *args, **kwargs):
        print('\n%s' % locals())
        print(self.think_called)
        self.dosomething()

    @thinkagain
    def dosomething(self):
        print('%s\n' % ('-'*30), locals())
        print(self.dosomething_called)

Both produce same desired behaviour:

>>> something = DoSomething_A().think(1, 2)
{'args': (1, 2), 'kwargs': {}, 'self': <__main__.DoSomething_A object at     0x10209f128>}
2015-12-26 04:13:25.629887 (1, 2) {}
------------------------------
{'self': <__main__.DoSomething_A object at 0x10209f128>}
2015-12-26 04:13:25.647476 I did something awful

and

>>> something = DoSomething_B().think('arg_a', 'arg_b')
{'args': ('arg_a', 'arg_b'), 'kwargs': {}, 'self': <__main__.DoSomething_B object at 0x10209f208>}
2015-12-26 04:13:25.648039
------------------------------
{'self': <__main__.DoSomething_B object at 0x10209f208>}
2015-12-26 04:13:25.648390

Upvotes: 7

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 798606

Somehow, decorating my method made it a function.

Incorrect. The function is created, then it is decorated, and then it becomes a method. You will need to write a wrapper that captures the self argument at runtime and then invokes the actual function.

Upvotes: 2

Related Questions