Modifying func_closure

Robert Brewer fumanchu at amor.org
Thu Jul 8 16:21:30 EDT 2004


Jacek Generowicz:
> Python's closures are read-only. But is there a way of 
> hacking one's
> way around this restriction?

Me:
> If I understand your requirements correctly, I would use 
> new.function(), passing it the closure you want.
>
> return new.function(co, f.func_globals, newname, f.func_defaults,
> f.func_closure)

Jacek:
> But this doesn't help me to modify the binding in an original closure 
> ... unless I'm missing your point.

Hmmm. perhaps I'm missing yours. It sounded to me like you had a
function with an associated closure, and wanted to modify that closure.
Given:

>>> def foo():
... 	a = 5
... 	def bar():
... 		return a
... 	return bar
... 
>>> g = foo()
>>> g.func_closure
(<cell at 0x0116B310: int object at 0x002F9AF0>,)
>>> foo.func_code.co_cellvars
('a',)

Are you trying to modify g.func_closure or foo.func_code.co_cellvars or
both?

Either way, the only way I know to even fake that (in pure Python) is to
create a new function by copying the old one, substituting a different
func_closure or co_cellvars (see example below). Perhaps you can share
more about what you're trying to accomplish?


import new

def safe_tuple(seq):
    """Force func_code attributes to tuples of strings.
    
    Many of the func_code attributes must take tuples, not lists,
    and *cannot* accept unicode items--they must be coerced to strings
    or the interpreter will crash.
    """
    seq = map(str, seq)
    return tuple(seq)

def new_closure(self, func, newname, cellvars=None, func_closure=None):
    fcode = func.func_code
    if cellvars is None:
        cellvars = fcode.co_cellvars
    if func_closure is None:
        func_closure = func.func_closure
    newco = new.code(fcode.co_argcount, fcode.co_nlocals,
fcode.co_stacksize,
                    # Notice co_consts should *not* be safe_tupled.
                    fcode.co_flags, codestr, tuple(fcode.co_consts),
                    safe_tuple(fcode.co_names),
safe_tuple(fcode.co_varnames),
                    fcode.co_filename, fcode.co_name,
fcode.co_firstlineno,
                    fcode.co_lnotab, safe_tuple(fcode.co_freevars),
                    safe_tuple(co_cellvars))
    return new.function(newco, func.func_globals, newname,
func.func_defaults, func_closure)


[...this was hastily cut-and-pasted, so there may be some errors I
missed, but the principle is sound.]

If you're trying to hack the func_closure tuple itself, here's a handy
trick to dereference the values:

_derefblock = new.code(0, 0, 1, 3, '\x88\x00\x00Sd\x00\x00S', (None,),
                       ('cell',), (), '', '', 2, '', ('cell',))
def deref_cell(cell):
    return new.function(_derefblock, {}, "", (), (cell,))()

...pass this an item from a func_closure tuple, and you should get back
the value of the original outer variable.


Robert Brewer
MIS
Amor Ministries
fumanchu at amor.org



More information about the Python-list mailing list