使用三个参数构建装饰器 - 在Python 2.x中无法使用闭包

时间:2011-12-15 10:42:10

标签: python

def ensure_type(parameter_name, the_type, type_converter_fn=None):
   def decorator(fn):
      def wrapped(self, *args, **kwargs):

         if not type_converter_fn: # fails here
            type_converter_fn = the_type

         return fn(self, *args, **kwargs)
      return wrapped
   return decorator

当单步执行wrapped函数时,parameter_namethe_type闭包变量被正确绑定,但type_converter_fn不正确。无论是否使用该可选参数调用ensure_type,都会发生这种情况,如果我强制使用该参数,也会发生这种情况。

为什么前两个参数有效,而第三个从未被分配?

作为参考,我在这一行上得到一个例外 - if not type_converter_fn说它在分配之前已被引用。

2 个答案:

答案 0 :(得分:3)

Python中的范围确定是静态的。函数内部的赋值使该函数的局部变量成为可能。在第一次分配之前,您无法访问本地变量。

中的第二行
if not type_converter_fn:
    type_converter_fn = the_type

type_converter_fn设为wrapped()本地,因此此代码段第一行的访问权限为UnboundLocalError。 (顺便说一句,如果你告诉我们你得到了什么错误信息以及在哪一行,这将会变得如此简单。总是将错误信息的完整追溯复制到你的问题中 - 这会为回答者节省很多时间。)< / p>

答案 1 :(得分:2)

你的“行动水平”太复杂了。只要你有权访问它就可以处理它们。

作为

if not isinstance(parameter_name, str):
    raise Exception("parameter_name must be a string")

if not type_converter_fn:
    type_converter_fn = the_type

只处理“外部”参数,你应该在那里处理。

同样适用于

arg_position = list(fn.func_code.co_varnames).index(parameter_name) - 1

更深层次。

一般情况下,我会这样做(未经测试!):

def ensure_type(parameter_name, the_type, type_converter_fn=None):
    if not isinstance(parameter_name, str):
        raise Exception("parameter_name must be a string")

    if not type_converter_fn:
        type_converter_fn = the_type

    def decorator(fn):
        arg_position = list(fn.func_code.co_varnames).index(parameter_name) - 1 #minus once because of self
        def wrapped(self, *args, **kwargs):
            if arg_position > -1:
                the_arg = args[arg_position]
                if the_arg is not None and not isinstance(the_arg, the_type):
                    all_the_args = list(args)
                    all_the_args[arg_position] = type_converter_fn(the_arg)
                    args = tuple(all_the_args)
            return fn(self, *args, **kwargs)
        return wrapped
    return decorator
编辑:我刚才看到:它仍然太复杂了。

只有在需要时才会进行换行。 if arg_position < 0,您可以提出异常(因为整个内容毫无意义,因此请使用.find()代替.index()),或者您可以返回原始fn。< / p>

def ensure_type(parameter_name, the_type, type_converter_fn=None):
    if not isinstance(parameter_name, str):
        raise Exception("parameter_name must be a string")
    if not type_converter_fn:
        type_converter_fn = the_type

    def decorator(fn):
        arg_position = list(fn.func_code.co_varnames).index(parameter_name) - 1 #minus once because of self
        # Either use .find() here, or live with -1 and return the original function then:
        if arg_position < 0: # only needed with .index()
            return fn
        def wrapped(self, *args, **kwargs):
            the_arg = args[arg_position]
            if the_arg is not None and not isinstance(the_arg, the_type):
                all_the_args = list(args)
                all_the_args[arg_position] = type_converter_fn(the_arg)
                args = tuple(all_the_args)
            return fn(self, *args, **kwargs)
        return wrapped
    return decorator