Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Local variable referenced before assignment in decorated method

I'm creating a decorator that would allow me to do stuff like:

@cooldownf(lambda self, **eargs: 30 - self.level)
def method(self, **eargs):
    ...

Which would simply decorate the method so that it has a cooldown. This all worked just fine and the method can now be executed every 30 - self.level seconds.

However, I wanted to add a message that would be displayed if the method is still on cooldown. I added a message parameter for the cooldownf decorator, but then I got an error UnboundLocalError: local variable 'message' referenced before assignment from the line ìf message: in my decorator's code:

def cooldownf(fn, message=None):
    """Decorates a method to have a dynamic cooldown.

    Decorator function for easily adding cooldown as a dynamic time
    (function) into skill's methods. The function gets called when the
    cooldown is needed, and the skill is passed to the function.

    Args:
        fn: Function to determine the cooldown of the method
        message: Optional message sent if there's still cooldown left

    Returns:
        Decorated method with a dynamic cooldown
    """

    # Create a decorator using the function and message provided
    def method_decorator(method):

        # Create a wrapper method
        @wraps(method, assigned=WRAPPER_ASSIGNMENTS+('__dict__',), updated=())
        def method_wrapper(self, **eargs):

            # If the method's cooldown is over
            if method_wrapper.cooldown.remaining <= 0:

                # Restart the cooldown
                method_wrapper.cooldown.start(1, fn(self, **eargs))

                # And call the function
                return method(self, **eargs)

            # If there was cooldown remaining and a message is provided
            if message:

                # Format the provided message
                message = message.format(
                    name=self.name,
                    cd=method_wrapper.cooldown.remaining,
                    max_cd=method_wrapper.cooldown.limit
                )

                # Send it to the player
                SayText2(message=message).send(eargs['player'].index)

                # And exit with code 3
                return 3

        # Create the cooldown object for the wrapper
        method_wrapper.cooldown = TickRepeat(lambda: None)

        # And return the wrapper
        return method_wrapper

    # Return the decorator
    return method_decorator

What's causing this? I can print message just fine inside of cooldownf or method_decorator, but adding a print into the method_wrapper will cause the error to raise. That's exact code and I can't replicate it in IDLE with functions, does it have something to do with me using methods in particular?

like image 985
Markus Meskanen Avatar asked Dec 25 '22 22:12

Markus Meskanen


1 Answers

You are assigning to message in the innermost function:

message = message.format(
    name=self.name,
    cd=method_wrapper.cooldown.remaining,
    max_cd=method_wrapper.cooldown.limit
)

That assignment makes it a local variable, but you cannot make that assignment without first accessing message. You cannot do that with a local.

Since you don't want to modify the closed-over argument, you want to use a new local name here:

formatted_message = message.format(
    name=self.name,
    cd=method_wrapper.cooldown.remaining,
    max_cd=method_wrapper.cooldown.limit
)
SayText2(message=formatted_message).send(eargs['player'].index)
like image 174
Martijn Pieters Avatar answered May 10 '23 18:05

Martijn Pieters