Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

First time using decorators, what am I missing?

Tags:

python

class User:
    def __init__(self):
       self.score = 0
       self.wins = 0
       self.losses = 0


    def tally(self, func):
        def wrapper_function(w):
            print("Wins: {}\nLosses: {}\nTotal Score: {}".format(self.wins, self.losses, self.score))
            return func(w)
        return wrapper_function()

    @tally
    def record_win(self, w=1):
        self.wins += w


user1 = User()
user1.record_win()

The error that I'm getting is: TypeError: tally() missing 1 required positional argument: 'func'

EDIT This post is different from the one here because in that post the decorator functions were not instance methods.. which I see now adds some peculiar requirements.

like image 783
Sergio Bost Avatar asked Apr 13 '26 11:04

Sergio Bost


1 Answers

Your problem is that you are defining tally as an instance method, but it's really just a decorator function (it can't be called on an instance in any reasonable way). You can still define it in the class if you insist (it's just useless for instances), you just need to make it accept a single argument (the function to wrap), without self, and make the wrapper accept self (while passing along any provided arguments to the wrapped function):

class User:
    # ... other methods unchanged ...
    def tally(func):
        def wrapper_function(self, *args, **kwargs):  # Accept self + arbitrary arguments to make decorator useable on more functions
            print("Wins: {}\nLosses: {}\nTotal Score: {}".format(self.wins, self.losses, self.score))
            return func(self, *args, **kwargs)  # Pass along self to wrapped function along with arbitrary arguments
        return wrapper_function                 # Don't call the wrapper function; you have to return it so it replaces the decorated function
    
    @tally
    def record_win(self, w=1):
        self.wins += w

    # Optionally, once tally is no longer needed for new methods, but before dedenting out of class definition, do:
    del tally
    # so it won't stick around to appear as a possible method to call on instances
    # It's done all the work it needs to do after all

Removing the w argument in favor of *args, **kwargs means you don't need to specialize to specific function prototypes, nor duplicate the defaults of the function you're wrapping (if they don't pass the argument, the default will get used automatically).

like image 175
ShadowRanger Avatar answered Apr 14 '26 23:04

ShadowRanger