1

I'm learning about decorators and what I'm trying to do is to apply a simple decorator class to methods of another class for understanding and educational purposes.

class decorators():
    def __init__(self, func):
        self.func = func
    
    def __call__(self, *args, **kwargs):
        print(f'this method has been decorated')
        return self.func(self,*args, **kwargs)

class Users():
    def __init__(self,name,password,age,email):
        self.name = name
        self.password = password
        self.age = age
        self.email = email

    @decorators
    def login(self):
        name = input('Enter your name')
        password = input('Enter your password')
        if name == self.name and password == self.password:
            print("user logged in")
        else:
            print("wrong credentials")

    @decorators
    def show(self):
        print(f'my name is {self.name} and my age is {self.age}')

user1 = Users('John', 'pass', 20, 'john@doe.com')
user1.login()

As you can see, I just want to test whether I can build this decorator class that calls any methods in the Users class such as login() and show(). When calling user1.login() as above, I get this error:

AttributeError                            Traceback (most recent call last)
<ipython-input-98-ed78c0a45454> in <module>
----> 1 user1.login()

<ipython-input-96-759e600a717b> in __call__(self, *args, **kwargs)
      5     def __call__(self, *args, **kwargs):
      6         print(f'this method has been decorated')
----> 7         return self.func(self,*args, **kwargs)
      8 
      9 class Users():

<ipython-input-96-759e600a717b> in login(self)
     18         name = input('Enter your name')
     19         password = input('Enter your password')
---> 20         if name == self.name and password == self.password:
     21             print("user logged in")
     22         else:

AttributeError: 'decorators' object has no attribute 'name'

My thinking is that the User instance is not passed to the decorator and thus the decorator object doesn't have the User attribute 'name'. Is there a way to accomplish this?

1 Answers1

1

You are passing self to the first argument of self.func: return self.func(self,*args, **kwargs) but self is a decorator instance, not an instance of Users, hence the error.

There are various ways to make this work. Essentially, the instance is passed as the first argument to a function object when it is called on that instance through the descriptor protocol

That is, function objects are descriptors, and their __get__ method partially applies the instance to itself. So the cleanest way to make your custom class act like a function object is to make your decorator class a descriptor, replicating what a function object does:

from types import MethodType
class decorators:
    def __init__(self, func):
        self.func = func
    def __call__(self, *args, **kwargs):
        print(f'this method has been decorated')
        return self.func(*args, **kwargs)
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return MethodType(self, obj)

So, as an example:

>>> from types import MethodType
>>> class decorators:
...     def __init__(self, func):
...         self.func = func
...     def __call__(self, *args, **kwargs):
...         print(f'this method has been decorated')
...         return self.func(*args, **kwargs)
...     def __get__(self, obj, objtype=None):
...         if obj is None:
...             return self
...         return MethodType(self, obj)
...
>>> class Foo:
...     def __init__(self):
...         self.bar = 42
...     @decorators
...     def frobnicate(self):
...         return self.bar + 1
...
>>> Foo().frobnicate()
this method has been decorated
43
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172