Q: Python 2.0 preliminary features?

Greg Ewing greg.ewing at compaq.com
Tue Oct 12 04:31:51 EDT 1999


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

On reflection, you're quite right -- some knowledge of how my
patch works is needed in order to inerpret it properly, so
some elaboration is in order.

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

Under my scheme, that piece of code in itself does not create
a cycle. This is because a nested def does not immediately create
a closure; instead it creates a special "local function object"
which simply wraps the function. A closure is created "on
demand" whenever a local function object is fetched out of a
local variable. This is analogous to the way a bound method
object is created whenever a function is fetched out of a
class during an attribute lookup.

Whether a cycle gets created or not depends on what you do with
the closure once you've got it. Many common uses will not
create any cycles, e.g.

  def add_to_each(items, x):
    def adder(y):
      return x+y
    return map(adder, items)

or any other use which only involves passing closures "inwards".

There are other common uses that will indeed create cycles.
I'm not saying that you shouldn't do those things -- only
that you need to be aware of the cycles you're creating and
take steps to break them later. Just like you have to do with
any other cyclic data structure in Python.

Many of these uses would have created cycles anyway if done
using one of the alternatives to closures. For example,
consider an object with an attached UI widget which makes
a callback to a method of the object:

  class Furble:

    def __init__(self, ...):
      ...
      self.button = Button(..., 
        command=lambda self=self: self.wibble(42))

This creates a cycle, because the instance has a reference
to the button whose callback has a default argument referring to
the instance. This isn't a problem in practice because usually
you will destroy() the instance's attached widgets when
you're finished with it.

Since we're doomed to have a cycle anyway, we might as
well use a lexical closure and do away with the
ugly default argument hack:

      self.button = Button(..., 
        command=lambda: self.wibble(42))

Hope that makes things less waffly,
Greg




More information about the Python-list mailing list