Is there "let binding" in Python?

Rob Hunter rhunter007 at yahoo.com
Mon Sep 15 12:52:25 EDT 2003


Thanks for the reply.  Some comments below:

> Tutorial
> 9.2 Python Scopes and Name Spaces
>
http://www.python.org/doc/tut/node11.html#SECTION0011200000000000000000

I actually find this reference, at least the
section on Python Scopes and Namespaces, quite
complicated, and pretty much unreadable.  But I
think I understand what's going on despite it.

>... but you can't write
> 
>      def outer():
>          x = 3
>          def f(arg): return arg * x
>          x = 4
>          return f(5)
> 
> to have outer() return 15, because 'f' will
> take the value of x from the
> enclosing scope, and that value is 4 at the
> time of the call.
> 

I think it's actually that this will return 20
because the x = 4 "mutates" (rather than
introducing a new binding) the value of x from 3
to 4.  Thus, with mutation, we are able to pierce
the function.  (Reason #1 in this email why
mutation is often a bad thing.)

What you can do, though, is this: which shows
that Python has (correct) static scoping:

def create():
  x = 3
  def f(arg):
    return arg*x
  return f

def use():
  x = 2
  someF = test()
  x = 4
  return someF(1)

Now use() returns 3, not 4 or 5.  And the reason,
I think, is that the x in use() is different from
the x in create().

> If you want to have f capture the value of x at
> that moment, you must
> manually introduce another layer of nesting:
> 
>      def outer():
>          x = 3
>          def inner(x):
>              def f(arg): return arg*x
>              return f
>          f = inner(x)
>          x = 4
>          return f(5)

well no, you can just do what I did above.  But
yes, if you want to use the same name x in the
same function it seems that, in Python, you are
out of luck.  But I can deal with that.

> 
> or you can try to structure your code so that
> you can avoid re-using the
> name 'x'.  One common example of a situation
> where you can't avoid it
> is something like this:
> 
>      from Tkinter import *
> 
>      def button_func(i):
>          print "pressed button", i
> 
>      def make_stupid_gui():
>          t = Tk()
>          for i in range(10):
>              b = Button(t, text="Button #%d" %
> i,
>                  command=lambda:
> button_func(i))
>              b.pack()
>      make_stupid_gui()
> 
> 

I got mutation red-handed here!  The *only*
reason there is a problem here is because of
mutation (Reason #2).  The FOR statement is
mutating the variable i.  Avoid mutation, and you
avoid this problem.  This, by the way, really
illustrates the insidiousness of mutation.  IMO,
you can look at the code above and think it's
perfectly correct...after all, you do a loop on
i, creating one button for each of the 10 i's. 
What could go wrong?

In scheme, you could write something like this:

(let loop ((i 1))
  (if (> i 10)
      empty
      (cons (make-button i)
            (loop (+ 1 i)))))

This program returns a list of 10 buttons
(assuming "make-button" creates numbered buttons)
from 1 to 10.

IMO, this looks a lot like your button code,
except there's no mutation and thus no problem of
making every button be button 10.

Here's another Scheme program:

(map make-button (enumerate 1 10))

This is the way I would write the program in
Scheme *or* in Python.  It's beautiful, and it
does exactly what you'd think it would. 


> 
> Not an answer, but advice: when writing
> programs in Python, write Python
> programs.  If it's your task to translate some
> Scheme (or any other
> language) into Python as literally as possible,
> my heart goes out to 
> you.

I'm simply trying to learn to write in Python in
a way that I will find tolerable.  I really like
a lot of it, so far, and I find it fun to write
it, and very pretty and easy to read.  

In general, in most cases, I find it
*unacceptable* to use mutation.  And, since
Python is very much a Scheme-like language, and
has functional components (like map, and
uh...functions :) with proper scoping) I think
that this is quite a reasonable thing to want. 
The trick is knowing when mutation happens, so
that the programmer is aware when s/he is
entering a scary land where things may not be
what they seem.  And it seems from your
references and my tests that the "=" operator is
mutative.  In Scheme there's no issue..you
introduce a binding with LET and you mutate a
binding with SET!.  In Python, the way I see it,
you introduce a binding with "=" and you mutate a
binding with "=".

There seems to be some "functional" module (which
isn't installed on my system for some reason)
which I should look in to.  From what I can tell,
it prevents you from mutating...it only allows
you to introduce bindings.

Rob

__________________________________
Do you Yahoo!?
Yahoo! SiteBuilder - Free, easy-to-use web site design software
http://sitebuilder.yahoo.com





More information about the Python-list mailing list