0

I am using a class decorator for a subclass TestClass which inherits SuperClass. I have a classmethod in SuperClass called what(cls) which takes in a class. I want to be able to decorate that class in my subclass TestClass, but it is not letting me as it is saying.

TypeError: unbound method wrapper() must be called with TestClass instance as first argument (got nothing instead)

I have tried to make an instantiation of my TestClass object and then use that to call method testclass.what(cls) and that works, but when I do TestClass.what(), it gives me the error above.

def class_decorator(cls):
    for attr_name in dir(cls):
        attr_value = getattr(cls, attr_name)
        if hasattr(attr_value, '__call__'):  # check if attr is a function
            # apply the function_decorator to your function
            # and replace the original one with your new one
            setattr(cls, attr_name, ball(attr_value))
    return cls


def ball(func):
    def wrapper(*args, **kwargs):
        print("hello")
        return func(*args, **kwargs)

    return wrapper



class SuperClass:
    def __init__(self):
        pass

    @classmethod
    def what(cls):
        print("testing")


@class_decorator
class TestClass(SuperClass):

    def what(cls):
        super().what()


TestClass.what()

Expected:

"hello"
"testing"
"hello"
"testing"

Actual: TypeError: unbound method wrapper() must be called with TestClass instance as first argument (got nothing instead)
arajshree
  • 626
  • 4
  • 13
  • It sounds like your local wrapper function needs to take self and pass it to func. – quamrana Jun 24 '19 at 21:58
  • Possible duplicate of [Attaching a decorator to all functions within a class](https://stackoverflow.com/questions/3467526/attaching-a-decorator-to-all-functions-within-a-class) – quamrana Jun 24 '19 at 22:01

1 Answers1

0

There are a few issues with your program; instead of going to all the details I'm gonna point out the way to get your desired output.

The error is being raised because you've overriden the classmethod what in TestClass, which now takes a single argument cls that cna be anything. In other words, you also need to decorate your classmethod in subclass by using the classmethod decorator.

If you want to keep your current code, you need to provide the cls argument expilictly, something like following should do:

from functools import partial

def class_decorator(cls): 
    for attr_name in vars(cls): 
        attr_value = getattr(cls, attr_name) 
        if callable(attr_value):  # check if attr is a function 
            if attr_name == 'what': 
                setattr(cls, attr_name, partial(ball(attr_value), cls=cls)) 
            else: 
                setattr(cls, attr_name, ball(attr_value)) 

using partial to pass in the class as first argument.

Also, I've used vars(cls) to get the cls.__dict__ as dir does recursion on bases (which you don't want here). Also, rather than checking for the __call__ attribute, use callable.

heemayl
  • 39,294
  • 7
  • 70
  • 76