How to map func_closure entries to variable names?

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>}