在修饰方法中赋值之前引用的局部变量

Mar*_*nen 2 python decorator python-3.x python-decorators

我正在创建一个装饰器,该装饰器将允许我执行以下操作:

@cooldownf(lambda self, **eargs: 30 - self.level)
def method(self, **eargs):
    ...
Run Code Online (Sandbox Code Playgroud)

这将简单地装饰方法,使其具有冷却时间。所有这些工作都很好,并且该方法现在可以每秒钟执行一次30 - self.level

但是,我想添加一条消息,如果该方法仍处于冷却状态,则会显示该消息。我messagecooldownf装饰器添加了一个参数,但是随后我的装饰器代码UnboundLocalError: local variable 'message' referenced before assignmentìf message:中出现错误:

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
Run Code Online (Sandbox Code Playgroud)

是什么原因造成的?我可以messagecooldownf或中method_decorator进行打印,但在中添加打印内容method_wrapper会引起错误。那是精确的代码,我无法使用函数将其复制到IDLE中,特别是使用方法与我有什么关系吗?

Mar*_*ers 5

您要分配method最里面的功能:

message = message.format(
    name=self.name,
    cd=method_wrapper.cooldown.remaining,
    max_cd=method_wrapper.cooldown.limit
)
Run Code Online (Sandbox Code Playgroud)

该分配使它成为局部变量,但是您必须先访问才能进行分配message。您无法通过本地方式做到这一点。

由于您不想修改closed-over参数,因此您想在此处使用新的本地名称:

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)
Run Code Online (Sandbox Code Playgroud)

  • @MarkusMeskanen:Python 通过 *binding* 定义名称所属的范围;如果您分配一个名称,或导入一个名称或创建一个具有给定名称的函数或类,那么您就是在该范围内绑定该名称。如果不赋值,则 Python 必须在父作用域中查找名称;全局或内置,或来自封闭范围。 (3认同)