Q: Python 2.0 preliminary features?

Tim Peters tim_one at email.msn.com
Mon Oct 11 14:16:09 EDT 1999


[Greg Ewing]
> Also, to clarify Tim's rather waffly characterisation of when my
> closures do or don't cause cycles, it's quite simple: they never
> cause cycles unless you write code which makes them do so.

[Jeremy Hylton]
> I think this description is at least as waffly as Tim's :-).

I went into some detail on when Greg's patch would and wouldn't create
cycles in an earlier (but still very recent) msg, and didn't feel like
repeating it all.  That was certainly less waffleworthy than Greg's
content-free tautology above <wink>.

> It seems to me that most of the interesting uses of lexical scoping
> necessarily create cycles.  Is there a large class of uses that will
> not create cycles?

In Greg's approach, yes -- although at a cost.

> One of the prototypical uses of lexical scoping is functions like
> these:
>
> def make_adder(num):
>     def adder(x):
>             global num
>             return x + num
>     return adder
>
> In this case, adder maintains a reference to make_adder's
> environment, and that environment maintains a reference to adder,
> because adder was defined there.  So we have a cycle...

In Greg's scheme (which you may be confusing with John Skaller's), you write
that in the more Scheme-like way:

def make_adder(num):
    def adder(x):
        return x + num
    return adder

That is, Greg doesn't change the meaning of Python's "global", and you don't
want "global" here.  Non-local references are searched for up a chain of
static (lexical) links, in the usual way.

The "def adder" does *not* create a closure (and hence neither a cycle)
under Greg's scheme.  It binds a new kind of object to "adder", a trivial
wrapper around the function object currently stored.  No info about bindings
is contained in this new object.  The sole purpose of the new object is to
force a *later* operation to create a closure.

The magic all happens in LOAD_FAST, which is (for speed), alas, the worst
possible opcode to pick on.  When LOAD_FAST retrieves an object of this new
type, *then* it creates a closure, out of the function wrapped by the new
object and the current frame.  The beauty of it is that this closure (the
object returned by "return") is anonymous:  it refers to make_adder's
locals, but make_adder's locals do not refer to *it*.  make_adder's locals
refer only to the new wrapper object bound to "adder", which in turn refers
only to the (raw) function object.

So there is no cycle here, and everything works fine.  It's very clever.
It's also hard to know when you're creating a cycle, unless you're Greg <0.1
wink>; e.g., this variant does create a cycle:

def make_adder(num):
    adder = lambda x: x + num  # lambda is now very different from def
    return adder

and so does:

def make_adder(num):
    def adder(x):
        return x + num
    def suber(x):
        return x - num
    methods = [adder, suber]
    return methods

while:

def make_adder(num):
    return lambda x: x + num

does not; and so on (see my earlier msg).

no-msg-really-gets-sent-until-it's-typed-twice<wink>-ly y'rs  - tim






More information about the Python-list mailing list