如何将func_closure条目映射到变量名?

7

我有一个Lambda对象,是在这个函数中创建的:

def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
    self.record(lambda s:
        s.add_url_rule(rule, endpoint, view_func, **options))

使用 lambda 函数对象的 func_closure,我可以访问到闭包作用域:

(<cell at 0x3eb89f0: str object at 0x2fb4378>,
 <cell at 0x3eb8a28: function object at 0x3cb3a28>,
 <cell at 0x3eb8a60: str object at 0x3ebd090>,
 <cell at 0x3eb8b08: dict object at 0x3016ec0>)

仔细观察(每个cell对象的cell_contents属性),我发现:

>>> [c.cell_contents for c in func.func_closure]
['categoryDisplay',
 <function indico.web.flask.util.RHCategoryDisplay>,
 '/<categId>/',
 {}]

那个 Lambda 函数是通过这个调用创建的:
add_url_rule('/<categId>/', 'categoryDisplay', rh_as_view(RHCategoryDisplay))

正如您所看到的,该顺序不匹配函数参数顺序或lambda内部使用参数的顺序。虽然我可以根据其类型/内容轻松找出哪个元素是什么,但我希望以更清晰的方式完成它。

因此,我的问题是:如何将其与它们的原始变量名称(或至少在函数参数的情况下是位置)关联起来?


似乎关键字参数首先被接受,然后是位置参数,最后是捕获所有参数。 - Martijn Pieters
这个是在Python 2还是3上测试过的? - Martijn Pieters
1
我正在使用Python 2.7,但需要支持2.6和2.7。不过我认为这两个版本之间没有任何相关的变化。 - ThiefMaster
使用2.7版本和您的简化函数,我得到了不同的顺序。有趣。 - Martijn Pieters
2个回答

13
闭包是由LOAD_CLOSURE字节码创建的,顺序与它们的字节码顺序相同:
>>> dis.dis(add_url_rule)
  2           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (record)
              6 LOAD_CLOSURE             0 (endpoint)
              9 LOAD_CLOSURE             1 (options)
             12 LOAD_CLOSURE             2 (rule)
             15 LOAD_CLOSURE             3 (view_func)
             18 BUILD_TUPLE              4
             21 LOAD_CONST               1 (<code object <lambda> at 0x10faec530, file "<stdin>", line 2>)
             24 MAKE_CLOSURE             0
             27 CALL_FUNCTION            1
             30 POP_TOP             
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE        

因此,闭包的顺序在编译时由 compiler_make_closure() 决定;该函数使用 func.func_code.co_freevars 元组作为指南,该元组按相同顺序列出了闭包。

func.func_code.co_freevars 在创建代码对象时设置,在makecode 中生成元组,该元组从 python 字典的键中生成,因此顺序是任意的,与字典的常见特性相同。如果你好奇,字典是在compiler_enter_scope() 中构建的,使用编译器符号表中所有自由变量的dictbytype() 实用函数,它本身是一个 python 字典。

因此,闭包的顺序实际上是任意的(哈希表排序),您可以使用 func.func_code.co_freevars 元组(可以视为编译器处理字典键的顺序记录)来将名称附加到闭包:

dict(zip(func.func_code.co_freevars, (c.cell_contents for c in func.func_closure)))

如果您能提供任何关于这个答案的见解,我们将不胜感激(相关问题,Python 3)- https://dev59.com/1WIk5IYBdhLWcg3wTcUr#54841357 - arcseldon
@arcseldon 现在正在使用移动设备,但我能做的就是检查我在答案中使用的参考资料是否仍适用于 Python 的主分支。尽管我不认为有所改变。 - Martijn Pieters
1
@arcseldon,我在回答中提到的compile.c中的相同函数进行了快速扫描,发现没有任何变化;co_freevars按照闭包单元格的顺序列出名称。 - Martijn Pieters
1
@arcseldon: 此外,inspect.getclosurevars()实现明确依赖于这个顺序。 - Martijn Pieters
非常感谢,真的很感激你的帮助/解释。 - arcseldon

5
感谢Freenode上 #python 频道的 YHg1s ,我找到了答案:func_code.co_freevars 是一个元组,其中包含闭包中元素的变量名。
>>> func.func_code.co_freevars
('endpoint', 'view_func', 'rule', 'options')

因此,创建一个将名称映射到闭包值的字典很容易:

>>> dict(zip(func.func_code.co_freevars,
             (c.cell_contents for c in func.func_closure)))
{'endpoint': 'categoryDisplay',
 'options': {},
 'rule': '/<categId>/',
 'view_func': <function indico.web.flask.util.RHCategoryDisplay>}

啊,我本来要回答为什么它们是按照它们的顺序排列的。 - Martijn Pieters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接