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?
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.
Basically the decorator for outside function is working fine
Decorator for a class method is not working fine
Using the descriptor protocol in the decorator will allow us to access the method decorated with the correct instance as self
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?
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?
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.
Basically the decorator for outside function is working fine
Decorator for a class method is not working fine
Using the descriptor protocol in the decorator will allow us to access the method decorated with the correct instance as self
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?