Why do I get "missing required argument: self" when using a decorator written as a class with a method?

Question:

I have written a decorator using a class. Below is the code

class to_uppercase:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        return self.func(*args, *kwargs).upper()

When I apply this decorator on a function, it works.

@to_uppercase
def format_string(string):
    return string
>>> format_string('wORD wORD')
'WORD WORD'

Now when I apply the same decorator on a method, I get an error.

class base_string:
    def __init__(self, base):
        self.base = base

    @to_uppercase
    def get_base_string(self):
        return self.base

s = base_string('word worD')
s.get_base_string()

TypeError: get_base_string() missing 1 required positional argument: ‘self’

What am I missing here?

Asked By: Usman Riaz

||

Answers:

Use a function instead of a class, but if you need a class, here what you can do.

Add a __get__ method in your class

When you use a class as decorator, get_base_string is now a instance of your class decorator:

print(type(s.get_base_string))  # <class '__main__.to_uppercase'>

So, when the function is called, self is a ref to to_uppercase, and you don’t have a ref to the s object.

Code from https://stackoverflow.com/a/3296318/6251742

    def __get__(self, obj, obj_type):
        """Support instance methods."""
        import functools
        return functools.partial(self.__call__, obj)

With this method, you interact with Python getting the decorator instance. Since you call it from another object, this object and his type is sent to the __get__ method in obj parameter, and his type in obj_type parameter.

s.get_base_string()
# def __get__(self, obj, obj_type)
# s go to obj, this is the self we need
# type(s) go to obj_type, we don't need that, consider renaming it "_"
# self is an instance of the decorator class

Without the __get__ method, the called function is the __call__ of decorator, where self refer to the decorator instance and no instance of the class from where we decorated the method is provided

So we need to return a callable where an instance of the class from where we decorated the method is provided as first positional argument, and that’s what you do with functools.partial and the obj (our self, the instance we need). This partial will then be called.

Answered By: Dorian Turba

Basically the decorator for outside function is working fine
decorator for normal function working fine

Decorator for a class method is not working fine
error for another class

Using the descriptor protocol in the decorator will allow us to access the method decorated with the correct instance as self
descriptor prptocol
This runs fine

import functools
#decorater class
class to_uppercase:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        return self.func(*args, *kwargs).upper()
        
    def __get__(self, obj, objtype):
        return functools.partial(self.__call__, obj)
        

#normal class
class base_string:
    def __init__(self, base):
        self.base = base

    @to_uppercase
    def get_base_string(self):
        return self.base

s = base_string('word worD')
print(s.get_base_string())

for more information or understanding please visit
https://docs.python.org/3/howto/descriptor.html

for more examples
Python decorator, self is mixed up

Decorating class methods – how to pass the instance to the decorator?

Answered By: Nagaajay k
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.