I have a lambda object that is created in this function:
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))
Using func_closure
of the lambda function object I can access the closure scope of the lambda function:
(<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>)
A closer look (at the cell_contents
attribute of each cell
object) shows me this:
>>> [c.cell_contents for c in func.func_closure]
['categoryDisplay',
<function indico.web.flask.util.RHCategoryDisplay>,
'/<categId>/',
{}]
That lambda function was created by this call:
add_url_rule('/<categId>/', 'categoryDisplay', rh_as_view(RHCategoryDisplay))
As you can see, the order does not match the argument order of the function or the order in which the arguments are used inside the lambda. While I could easily find out which element is what based on its type/content, I'd like to do it in a cleaner way.
So my question is: How can I associate it with their original variable names (or at least positions in case of function arguments)?
The closures are created by the LOAD_CLOSURE
bytecode, in the same order as their bytecodes are ordered:
>>> 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
so the order is determined at compile time, by compiler_make_closure()
; this function uses the func.func_code.co_freevars
tuple as a guide, which lists the closures in the same order.
func.func_code.co_freevars
is set when creating a code object in makecode
, and the tuple is generated from the keys of a python dictionary, so the order is otherwise arbitrary, as common for dictionaries. If you are curious, the dict is built in compiler_enter_scope()
, using the dictbytype()
utility function from all the free variables named in the compiler symbol table, itself a python dictionary.
So, the order of the closures is indeed arbitrary (hash table ordered), and you'd use the func.func_code.co_freevars
tuple (which can be seen as a record of the order the compiler processed the dictionary keys) to attach names to the closures:
dict(zip(func.func_code.co_freevars, (c.cell_contents for c in func.func_closure)))
Thanks to YHg1s in #python on Freenode I figured it out: func_code.co_freevars
is a tuple containing the variable names of the elements in the closure.
>>> func.func_code.co_freevars
('endpoint', 'view_func', 'rule', 'options')
So creating a dict mapping names to closure values is easy:
>>> 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>}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With