简体   繁体   中英

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?

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

"self" is the first parameter of do_something function.

So you can explicitly identify it inside your decorator. wrapped will be the decorated class instance, and *args will contain all other positional arguments.

from functools import wraps
import types
import time

class TimeDecorator():

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

    def __call__(self, wrapped, *args, **kwargs):
        tic = time.time()
        func_return = self.__wrapped__(wrapped, *args, **kwargs)
        tac = time.time()
        total_time = round((tac - tic) / 60, 2)
        print(f'Operation Time: {total_time} min.')
        print(wrapped.test_attr)
        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...



a = A()
a.do_something()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM