【问题标题】:Read function arguments in decorator在装饰器中读取函数参数
【发布时间】:2017-06-05 19:21:40
【问题描述】:

我已经编写了这个装饰器,它将中间结果保存在一个 json 文件中

import json
import os

def json_file(fname):
    def decorator(function):
        def wrapper(*args, **kwargs):
            if os.path.isfile(fname):
                with open(fname, 'r') as f:
                    ret = json.load(f)
            else:
                with open(fname,'w') as f:
                    ret = function(*args, **kwargs)
                    json.dump(ret, f)
            return ret
        return wrapper
    return decorator

而用法是

@json_file("cached.json")
def some_calculation(n):
   return {"result": 2**n}

我想增加使用这样的函数参数:

@json_file("cached_{n}.json")
def calculation(n):
    return {"result": 2**n}

这样在调用函数时{n}n的值替换。

我尝试将fname 替换为fname.format(**kwargs),但没有成功。

如何做到这一点?

编辑:

根据@jonrshape 的评论,这是我在添加.format(**kwargs) 后遇到的错误

 <ipython-input-4-12b0c894b345> in wrapper(*args, **kwargs)
       5     def decorator(function):
       6         def wrapper(*args, **kwargs):
 ----> 7             fname = fname.format(**kwargs)
       8             if os.path.isfile(fname):
       9                 with open(fname, 'r') as f:

 UnboundLocalError: local variable 'fname' referenced before assignment

【问题讨论】:

  • n 可以作为位置参数传递,你为什么期望在kwargs 中?如果它必须始终是第一个参数,请更改为 def wrapper(n, *args, **kwargs):
  • calculation(n=5)调用函数也不起作用
  • 请给minimal reproducible example 提供回溯,然后。一个简单的例子对我来说就很好。
  • 为什么要投反对票?
  • 关于UnboundLocalError: local variable 'fname' referenced before assignment 还有很多其他问题,这不是一个独特的问题。如果您不尝试在 wrapper 中重用相同的名称作为 json_file 中的参数,那么您所拥有的将起作用。

标签: python python-decorators


【解决方案1】:

您可以使用inspect 模块。

首先我们提取包装函数的签名:

signature= inspect.signature(function)

然后我们bind *args 和 **kwargs:

bound_args= signature.bind(*args, **kwargs)

现在bound_args.parametersparameter_name:parameter_value 的字典,我们可以使用它来格式化我们的文件名:

file_name= fname.format(**bound_args.arguments)

所有东西放在一起:

import json
import os
import inspect

def json_file(fname):
    def decorator(function):
        signature= inspect.signature(function)

        def wrapper(*args, **kwargs):
            bound_args= signature.bind(*args, **kwargs)
            file_name= fname.format(**bound_args.arguments)

            #~ print(file_name)
            if os.path.isfile(file_name):
                with open(file_name, 'r') as f:
                    ret = json.load(f)
            else:
                with open(file_name,'w') as f:
                    ret = function(*args, **kwargs)
                    json.dump(ret, f)
            return ret
        return wrapper
    return decorator

【讨论】:

    【解决方案2】:

    如果n 有时是位置参数,有时是关键字参数,您可以先搜索关键字参数kwargs,如果失败,“回退”以从args 获取值:

    def json_file(fname):
        def decorator(function):
            def wrapper(*args, **kwargs):
                try:
                    value = kwargs['n']
                except KeyError:
                    value = args[0]
                fname.format(value)
                if os.path.isfile(fname):
                    with open(fname, 'r') as f:
                        ret = json.load(f)
                else:
                    with open(fname,'w') as f:
                        ret = function(*args, **kwargs)
                        json.dump(ret, f)
                return ret
            return wrapper
        return decorator
    

    为了完整起见,我会解决您的错误。

    您收到 UnboundLocalError 错误的原因是 Python 如何看待您的变量定义。

    如果变量已在当前范围内定义,将变量分配给新值将简单地将其重新绑定到新值。但是,如果变量尚未定义,Python 会将其视为变量定义,而不是重新绑定。

    这就是您的代码失败的原因。 Python 期望 fname 已在 current 范围内定义,而不是在父范围内。但由于从未定义过fname,因此引发了错误。

    您可以使用nonlocal 语句修复此错误。来自文档:

    nonlocal 语句使列出的标识符引用先前绑定的最近封闭范围内的变量,不包括全局变量。这很重要,因为绑定的默认行为是首先搜索本地命名空间。该语句允许封装代码重新绑定全局(模块)范围之外的局部范围之外的变量。

    这里是一个用法示例:

    >>> def foo(arg):
        def bar():
            arg += 1
            return arg
        return bar
    
    >>> 
    >>> foo(0)() # this will raise an error
    Traceback (most recent call last):
      File "<pyshell#76>", line 1, in <module>
        foo(0)() # this will raise an error
      File "<pyshell#74>", line 3, in bar
        arg += 1
    UnboundLocalError: local variable 'arg' referenced before assignment
    >>> 
    >>> def foo(arg):
        def bar():
            nonlocal arg
            arg += 1
            return arg
        return bar
    
    >>> foo(0)() # this will work
    1
    >>> 
    
    猜你喜欢
    • 2018-01-21
    • 1970-01-01
    • 2010-11-03
    • 2014-07-21
    • 2011-06-25
    • 1970-01-01
    • 1970-01-01
    • 2013-06-03
    • 2021-11-21
    相关资源
    最近更新 更多