scope hacks

Thomas Wouters thomas at xs4all.net
Wed Jun 21 03:12:57 EDT 2000


On Wed, Jun 21, 2000 at 05:37:05AM +0000, Andy Freeman wrote:

> I like local functions that can be recursive AND have access
> to the enclosing function's state. 

Why ? Isn't it easier to do something like this:

def _outers_inner(i):
    if i:
        return i + _outers_inner(i)
    else:
        return 1

def outer(j):
    _outers_inner(j)

Or do you need more state from the 'outer' function than the inner function
alone (if so, your example isn't good enough ;)

> I tried to use locals()
> in the following way and ran into a bug; is it me or Python 1.5?

Probably a little bit of both :)

> With the tagged line, everything works as expected.  Without
> the tagged line, inner's second print is {'j' : <value>};
> it doesn't include inner's definition.  (It behaves like a
> bogus optimization, namely, not adding local fns to the local
> scope unless there's some "not call" reference or an explicit
> locals(), the bogosity being that the default for v is an
> explicit use of locals().)

The bogosity is dual: at the time of the 'v = locals()', 'inner' is not
defined in locals, so you won't see it. The 'dictionary' representation
doesn't get updated, apparently, at each change in locals, so you have to
'force' it, by using locals() again. Python 1.6 doesn't (yet) fix this, by
the way.

The problem is that the local dictionary isn't really a dictionary (if
Python can help it, that is) but rather a vector, containing the values, and
the names refering to the 'keys' are translated to straight indexes into
that vector. So, yes, part of it is 'bogus' optimization :)

There are a couple of possible solutions. The easiest is "don't do that
then", as nested functions are not as useful or efficient as they might
seem. (They are still compiled at compile time, wether they'll get used at
runtime or not(*), and they'll get 'assembled' at runtime each time you
enter outer.)

Another solution is to do what you already did, work around the bug by using
the extra locals(). Not very pretty, but it works.

Or you can do something like this:

def outer(j):
     def inner(i, callerlocals=None, v={}):
        if not v and callerlocals:
            v.update(callerlocals)
        print "inner: " + str(i)
        print v
        if i:
            return i + v['inner'](i-1)
        else:
            return 1
 
    print "outer"
    inner(j, locals())

which utilizes the 'static' behaviour of mutable default arguments. You
shouldn't use this trick if you intend to call the inner function from
several different functions, though, you should probably delete v's keys
before you do that. Also note that this introduces a circular reference ;)
so you won't collect any of the objects local or global to outer() or
inner(), unless you fiddle with namespace dicts directly. 

-- 
Thomas Wouters <thomas at xs4all.net>

Hi! I'm a .signature virus! copy me into your .signature file to help me spread!




More information about the Python-list mailing list