Counterintuitive Python behavior

Michael Hudson mwh at python.net
Wed Apr 17 09:41:15 EDT 2002


dominikush at yahoo.com writes:

> one thing I like very much about Python is that statements
> work like you would expect them to work.

Well, Python works very much as I expect it, but it's not clear if
this says more about me or Python <wink>.

At the end of your email, you say:

> Who is wrong here: my intuition or Python (2.2)? If it's
> my intuition, how can I train my thinking about Python's
> execution model, so that my intuition get's better ;-)

It's you :) As I can't read my email at the moment[1], I have no
better way of wasting my time to hand than drawing you some ascii art.

First, some terminology.  Actually, the very first thing is some
anti-terminology; I find the word "variable" to be particularly
uphelpful in a Python context.  I prefer "names", "bindings" and
"objects".

Names look like this:

    ,-----.
    | foo |
    `-----'

Names live in namespaces, but that's not really important for the
matter at hand as the only namespace in play is the one associated
with the read-eval-print loop of the interpreter.  In fact names are
only minor players in the current drama; bindings and objects are the
real stars.

Bindings look like this:

    ------------>

Bindings' left ends can be attached to names, or other "places" such
as attributes of objects and entries in lists or dictionaries.  Their
right hand ends are always attached to objects[2].

Objects look like this:

    +-------+
    | "bar" |
    +-------+

This is meant to be the string "bar".  Other types of object will be
drawn differently, but I hope you'll work out what I'm up to.

> Take for example the use of dict.values() for dictionaries: If you
> store the result of dict.values(), and change the dictionary after-
> wards, the previously stored result remains untouched.
> 
> >>> dict = {'a':1,'b':2}

After this statement, it would seem appropriate to draw this picture:

    ,------.       +-------+
    | dict |------>|+-----+|     +---+
    `------'       || "a" |+---->| 1 |
                   |+-----+|     +---+
                   |+-----+|     +---+
                   || "b" |+---->| 2 |
                   |+-----+|     +---+
                   +-------+

> >>> list = dict.values()

Now this:

    ,------.       +-------+
    | dict |------>|+-----+|             +---+
    `------'       || "a" |+------------>| 1 |
                   |+-----+|             +---+
                   |+-----+|              /\
                   || "b" |+-----.    ,---'
                   |+-----+|     |    |
                   +-------+     `----+----.
                                      |    |
    ,------.       +-----+            |    \/
    | list |------>| [0]-+------------'   +---+
    `------'       | [1]-+--------------->| 2 |
                   +-----+                +---+

> >>> list
> [1, 2]

Which is of course, no surprise.

> >>> dict['a']=3

Now this:


    ,------.       +-------+
    | dict |------>|+-----+|             +---+
    `------'       || "a" |+-.           | 1 |
                   |+-----+| |           +---+
                   |+-----+| |            /\
                   || "b" |+-+---.    ,---'
                   |+-----+| |   |    |
                   +-------+ |   `----+----.
                             |        |    |
    ,------.       +-----+   |        |    \/
    | list |------>| [0]-+---+--------'   +---+
    `------'       | [1]-+---+----------->| 2 |
                   +-----+   |            +---+
                             |            +---+
                             `----------->| 3 |
                                          +---+


> >>> list
> [1, 2]
> >>> dict
> {'a': 3, 'b': 2}

These should also come as no surprise; just chase the arrows
(bindings) above.

> However, if a dictionary has lists as value entries, I get
> a counterintuitive behavior (which, just recently, broke my
> code): If you change the dict, the list you previously
> created via dict.values() gets automagically updated. A nice
> feature, but nothing I would have expected!

That's because you're not thinking in terms of Names, Objects and
Bindings.

> >>> dict = {'a':[1],'b':[2]}

    ,------.       +-------+
    | dict |------>|+-----+|     +-----+   +---+
    `------'       || "a" |+---->| [0]-+-->| 1 |
                   |+-----+|     +-----+   +---+
                   |+-----+|     +-----+   +---+
                   || "b" |+---->| [0]-+-->| 2 |
                   |+-----+|     +-----+   +---+
                   +-------+

> >>> list = dict.values()

    ,------.       +-------+
    | dict |------>|+-----+|             +-----+   +---+
    `------'       || "a" |+------------>| [0]-+-->| 1 |
                   |+-----+|             +-----+   +---+
                   |+-----+|               /\
                   || "b" |+-----.    ,----'
                   |+-----+|     |    |
                   +-------+     `----+-----.
                                      |     |
    ,------.       +-----+            |     \/
    | list |------>| [0]-+------------'   +-----+   +---+
    `------'       | [1]-+--------------->| [0]-+-->| 2 |
                   +-----+                +-----+   +---+

> >>> list
> [[1], [2]]

Again, no surprises here.

> >>> dict['a'].append(3)

                                                    +---+
    ,------.       +-------+                     ,->| 1 |
    | dict |------>|+-----+|             +-----+ |  +---+
    `------'       || "a" |+------------>| [0]-+-'
                   |+-----+|             | [1]-+-.
                   |+-----+|             +-----+ |  +---+
                   || "b" |+-----.         /\    `->| 3 |
                   |+-----+|     |    ,----'        +---+
                   +-------+     |    |
                                 `----+-----.
    ,------.       +-----+            |     \/
    | list |------>| [0]-+------------'   +-----+   +---+
    `------'       | [1]-+--------------->| [0]-+-->| 2 |
                   +-----+                +-----+   +---+

> >>> dict
> {'a': [1, 3], 'b': [2]}
> >>> list
> [[1, 3], [2]]

And now these should not be surprising either.

> Looks like that in the first case a copy is returned while
> in the latter case list references are returned. Ok, but
> according to Python's philosophy I shouldn't mind if I work
> with lists in the dictionary or anything else. If the
> behavior depends on the knowledge of the type of values I
> put into a dictionary, I find that somehow counterintuitive.

If you haven't realised where you're misconceptions come from from the
above pictures, I'm not sure more prose would help.

Cheers,
M.
[1] Does anyone know where the starship's gone?
[2] Anyone mentioning UnboundLocalError at this point will be shot.

-- 
  A.D. 1517: Martin Luther nails his 95 Theses to the church door and
             is promptly moderated down to (-1, Flamebait).
        -- http://slashdot.org/comments.pl?sid=01/02/09/1815221&cid=52
                                        (although I've seen it before)



More information about the Python-list mailing list