Modifying func_closure

Jacek Generowicz jacek.generowicz at cern.ch
Fri Jul 9 10:47:09 CEST 2004


"Robert Brewer" <fumanchu at amor.org> writes:

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

I think that I am trying to modify g.func_closure[0]

I'm trying to do something like the following. (I'm no Schemer, so
there may well be a more concise way of putting it.)

  (define closures
    (let ((enclosed 1))
      (define (report) enclosed)
      (define (modify new) (set! enclosed new))
      (list report modify)))
  
  (define report (car closures))
  (define modify (cadr closures))
  
  (report)    ; -> 1
  (modify 2)
  (report)    ; -> 2

The mechanism isn't important, it's the effect: "enclosed" is rebound;
it's value changes in a way which all enclosing closures notice.

> Perhaps you can share more about what you're trying to accomplish?

I tried to formulate it... but I didn't find a way of decoupling the
essence of the problem from a whole bunch of horrible implementation
details of my application ... so I don't think I could say anything
interesting or enlightening about it.

> 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 could actually end up being useful ... but first I'll try to see
whether I can restructure my code to find some high level solution.

> 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,))()

Hmm, almost as clear as Perl :-)

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

... but it's still read-only.


Thanks for your code examples. They give me a good starting point, if
I need to pursue this approach.



More information about the Python-list mailing list