renatodamas
renatodamas

Reputation: 19395

Applying class decorator to staticmethod: takes 1 positional argument but 2 were given

I am trying to apply a class decorator to each of its methods. But staticmethod and classmethod are not working for me. I got this:

import functools
import re


def decor(cls):
    def decorator(f):

        if isinstance(f, type):
            for attr in f.__dict__:
                if callable(getattr(f, attr)) and not re.match(r"__\w*__", attr):
                    setattr(f, attr, decorator(getattr(f, attr)))
            return f

        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            return f(*args, **kwargs)

        return wrapper

    return decorator(cls)


@decor
class MyClass:

    def printer1(self, string):
        print(string)

    @classmethod
    def printer2(cls, string):
        print(string)

    @staticmethod
    def printer3(string):
        print(string)


MyClass().printer1("foo")  # this works
MyClass().printer2("foo")  # this does not work
MyClass.printer2("foo")    # this works
MyClass().printer3("foo")  # this does not work
MyClass.printer3("foo")    # this works

# the error is the following:
TypeError: printer() takes 1 positional argument but 2 were given

Basically every time I instantiate the class, I get the error when calling a staticmethod and a classmethod.

Upvotes: 0

Views: 272

Answers (1)

Mechanic Pig
Mechanic Pig

Reputation: 7736

You don't seem to understand the working principle of staticmethod and classmethod. This is a link about the working principle of classmethod. The working principle of the staticmethod is similar to that of the classmethod. The difference is that when accessing the staticmethod, the class will not be bound to the function, but simply return the function.


There are two problems:

  1. The decorator also wraps the class and returns a function instead of a class, which prevents staticmethod and classmethod from working properly.
  2. For staticmethod and classmethod, getattr will get the result returned after the descriptor triggers the __get__ method, not necessarily the function itself. And here you only use wrapper to wrap, and do not restore them to the corresponding special methods.

Here is a working example:

def decorator(cls_or_func):
    if isinstance(cls_or_func, type):
        cls = cls_or_func
        for name, attr in cls.__dict__.items():
            if isinstance(attr, (staticmethod, classmethod)):
                setattr(cls, name, type(attr)(decorator(attr.__func__)))
            elif callable(attr) and not re.match(r"__\w*__", name):
                setattr(cls, name, decorator(attr))
        return cls

    func = cls_or_func

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper

Upvotes: 1

Related Questions