why cannot assign to function call

Mark Wooding mdw at distorted.org.uk
Tue Jan 6 15:03:16 CET 2009


Steven D'Aprano <steve at REMOVE-THIS-cybersource.com.au> wrote:

> (3) Those who come from an entirely different programming model, say, 
> Forth or Haskell. For them, Python's assignment model is going to be the 
> least of their worries.

Actually, Haskell's assignment model (you have to grubbing about for
IORefs or STRefs to find it but it's there) is exactly the same as Lisp,
Scheme, Python, Dylan, Perl, Lua, Icon, Ruby, Erlang, ML, ...

The variables-and-containers-hold-references-to-objects idea is common
to a very large number (probably the majority? depends how you count) of
high-level languages.

Java is in the same category.  C# is definitely not, since `struct's are
composite, mutable value types clearly distinguishable from anything
Python can do.

The main dissent is probably from Tcl, which pretends to copy values
-- even complex objects such as lists -- wholesale (though actually does
fancy copy-on-write things under the covers).

> Your first post in this topic made the extraordinarily arrogant and 
> incorrect claim that:
> 
> [quote]
> What the Python community often overlooks, when this discussion again
> rears its ugly head (as it seems to every other hour or so), is that
> its assignment model is BIZARRE, as in it's conceptually different
> from virtually all other languages substantially taught in
> undergraduate computer science programs.
> [end quote]
> 
> We can see some pretty poor assumptions right there:
> 
> * That the Python community is capable of uniformly overlooking Python's 
> differences from other languages.
> 
> * That only languages "substantially taught" in undergraduate CS courses 
> matter.

And a massive assumption about the languages taught to CS undergrads
too.  In particular, Scheme is a fairly popular language to teach to
undergrads, often as a first language.  See, for example, `The Structure
and Interpretation of Computer Programs' by Abelson and Sussman, and the
(much more recent) `How to Design Programs' by Felleisen, Findler, Flatt
and Krishnamurthi.

> It seems that you don't include in the Python community all those who
> use the term "name binding" instead of variable assignment
> specifically because it gives new users a clue that Python is not the
> same as C.

Unfortunately, this practice causes other confusion, since `binding' is
often used (in other language communities, notably Lisp and the
functional languages) to describe the association between names and
slots that hold references.

Let me explain.  I'll try to be clear, though I know that Stephen
already understands this stuff well.

At run-time, each named variable denotes a storage area (I'll call it a
slot[2]) which holds a reference to some object[1].

              +-----+
   name ----> | ref -----> object   
              +-----+

If we `assign' a different object to the variable, what really happens
is that the slot is modified to refer to the other object; but the name
continues to denote the same slot.

Usually `binding', or `rebinding', a name describes a different process,
whereby the name (for a while) denotes a different slot.  This name-to-
slot mapping is important because it's the mapping which is inherited by
nested functions.

This is most clearly shown by example.  Consider this Python
interaction.

In [1]: l = []

In [2]: for i in [1, 2, 3]:
   ...:   l.append(lambda: i)
   ...:

In [3]: [f() for f in l]
Out[3]: [3, 3, 3]

This prints [3, 3, 3].  What's happened is that the lambdas have closed
over the prevailing /binding/ of i -- i.e., which reference slot it
denotes.  Since the for loop operates by assignment, and /not/ by
rebinding i, the three lambdas all closed over the same environment, and
return the same value.

Scheme's DO loop, by contrast, really does operate by binding.  Consider
this example of a GNU Guile interaction.

guile> (define l '())
guile> (do ((i (list 1 2 3) (cdr i)))
...        ((null? i))
...      (set! l (append l (list (lambda () (car i))))))
guile> (map (lambda (f) (f)) l)
$1 = (1 2 3)

That is, each time through the loop, the variable I denotes a
/different/ reference slot; the LAMBDAs close over different
environments, and the functions return different values.  (This example
illustrates both binding and assignment, since the SET! changes the
value referred to in the slot denoted by L, without rebinding L.)

A very approximate translation of the above into Python looks like this.

In [1]: l = []

In [2]: items = [1, 2, 3]

In [3]: def _loop(ix):
   ...:   if ix >= len(items): return
   ...:   l.append(lambda: items[ix])
   ...:   _loop(ix + 1)
   ...:

In [4]: _loop(0)

In [5]: [f() for f in l]
Out[5]: [1, 2, 3]

(I've cheated and used indices because Python lists don't have the
head/tail structure of Lisp lists.)

None of this is a criticism of Python's behaviour.  (Sometimes it'd be
nice if `for' worked by rebinding, but sometimes it's better that it
doesn't.)  But it is a criticism of the Python community's confusing use
of the word `binding' to mean something different from what fairly
closely-related communities use it to mean.

[1] Lisp has a notion of `unbound variables', which is another confusing
    use of the terminology.  What this usually means is that the
    variable really refers to a special value, and the run-time signals
    an error if it retrieves this value from a variable.

[2] `Slot' gets used a lot too.  Sorry.  This usage has nothing to do
    with Lisp's structure or instance slots, and nothing to do with
    Python's __slots__.

> You've also missed out on probably twenty years of CS where Java (using 
> the same assignment model as Python!) has been *the* language of choice 
> for undergrad CS, not to mention those introductory courses which use 
> Python.

There's no way that Java has been taught anywhere for 20 years.  It just
isn't old enough.  Wikipedia claims that Java appeared in 1995, which
looks right to me.  Python, released in 1991, is therefore older.

> 
> And then you're shocked, SHOCKED!!! that people respond sharply.
> 
> How different do you think the response would have been if you had said:
> 
> "What we in the Python community should try to remember is that for those 
> new to the language, there can sometimes be preconceived ideas about how 
> assignment works that clashes with Python's assignment model. From my own 
> perspective, I found the differences between Python's name binding model 
> and the named-bins model of C/Pascal/Fortran confusing at first, and to 
> be honest, I still get caught by those differences. Naturally this is a 
> general problem for every language, Python is hardly unique, but please 
> remember not to bite the newbies just because they are still thinking in 
> some other language."
> 
> 


-- [mdw]



More information about the Python-list mailing list