Search code examples
pythondecoratorpython-decorators

How to Access the Decorated Class' Attributes from a Class Decorator through `functools.wraps`


I'm following David Beazley's Python Cookbook's Class Decorator recipe, which seems more proper, since it uses functools.wraps to give the decorator better organization and properties.

However, it isn't very clear to me how (or if) to access the instance's attributes from within the decorator itself. Here is a snippet of the code:

import time
import types
from functools import wraps

class TimeDecorator():

    def __init__(self, func):
        wraps(func)(self)

    def __call__(self, *args, **kwargs):
        tic = time.time()
        func_return = self.__wrapped__(*args, **kwargs)
        tac = time.time()
        total_time = round((tac - tic) / 60, 2)
        print(f'Operation Time: {total_time} min.')
        # print(self.__wrapped__.test_attr) # going to give an error...
        return func_return

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)


class A():

    def __init__(self):
        self.test_attr = 0

    @TimeDecorator
    def do_something(self):
        time.sleep(1) # example of stuff only...

I've tried to access the instance from the decorator's __call__ through self.__wrapped__.test_attr or self.__wrapped__.__self__.test_attr, but all of them tell me:

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

So how would I be able to access the decorated class' attribute in this case? Or will I have to use another way of building my decorators?


Solution

  • You're applying the decorator on an instance method, so the instance that this decorated method is bound to would be passed in as the first parameter, self, which is where you can find the instance attribute you're looking for, so you simply have to extract self from the arguments, named wrapped_self in the example below:

    def __call__(self, wrapped_self, *args, **kwargs):
        tic = time.time()
        func_return = self.__wrapped__(wrapped_self, *args, **kwargs)
        tac = time.time()
        total_time = round((tac - tic) / 60, 2)
        print(f'Operation Time: {total_time} min.')
        print(wrapped_self.test_attr)
        return func_return