tough-to-explain Python

K

kj

I'm having a hard time coming up with a reasonable way to explain
certain things to programming novices.

Consider the following interaction sequence:
.... some_int += 2
.... some_list += [2]
.... some_tuple += (2,)
....
x = 42
y = (42,)
z = [42]
eggs(x, y, z)
x 42
y (42,)
z [42, 2]

How do I explain to rank beginners (no programming experience at
all) why x and y remain unchanged above, but not z?

Or consider this one:
ham = [1, 2, 3, 4]
spam = (ham,)
spam ([1, 2, 3, 4],)
spam[0] is ham True
spam[0] += [5]
Traceback (most recent call last):
File said:
ham += [5]
spam ([1, 2, 3, 4, 5, 5],)

What do you say to that?

I can come up with much mumbling about pointers and stacks and
heaps and much hand-waving about the underlying this-and-that, but
nothing that sounds even remotely illuminating.

Your suggestions would be much appreciated!

TIA!

kj
 
C

Chris Rebert

I'm having a hard time coming up with a reasonable way to explain
certain things to programming novices.

Consider the following interaction sequence:
...     some_int += 2
...     some_list += [2]
...     some_tuple += (2,)
...
x = 42
y = (42,)
z = [42]
eggs(x, y, z)
x 42
y (42,)
z [42, 2]

How do I explain to rank beginners (no programming experience at
all) why x and y remain unchanged above, but not z?

Or consider this one:
ham = [1, 2, 3, 4]
spam = (ham,)
spam ([1, 2, 3, 4],)
spam[0] is ham True
spam[0] += [5]
Traceback (most recent call last):
 File said:
ham += [5]
spam ([1, 2, 3, 4, 5, 5],)

What do you say to that?

I can come up with much mumbling about pointers and stacks and
heaps and much hand-waving about the underlying this-and-that, but
nothing that sounds even remotely illuminating.

Your suggestions would be much appreciated!

You might find the following helpful (partially):
http://effbot.org/zone/call-by-object.htm

Cheers,
Chris
 
P

Piet van Oostrum

kj said:
k> I'm having a hard time coming up with a reasonable way to explain
k> certain things to programming novices.
k> Consider the following interaction sequence: k> ... some_int += 2
k> ... some_list += [2]
k> ... some_tuple += (2,)
k> ...
x = 42
y = (42,)
z = [42]
eggs(x, y, z)
x k> 42
y k> (42,)
z k> [42, 2]
k> How do I explain to rank beginners (no programming experience at
k> all) why x and y remain unchanged above, but not z?

You shouldn't. That's not for beginners. Leave it waiing until you get
to the advanced level.
k> Or consider this one:
ham = [1, 2, 3, 4]
spam = (ham,)
spam k> ([1, 2, 3, 4],)
spam[0] is ham k> True
spam[0] += [5]
k> Traceback (most recent call last):
k> File said:
ham += [5]
spam k> ([1, 2, 3, 4, 5, 5],)
k> What do you say to that?

Mutable and immutable. But use different examples. Like

ham = [1, 2, 3, 4]
spam = (1, 2, 3, 4)

spam[0] += 1 will give the same error message. You can't change the
components of a tuple.

Your example above is similar. The spam[0] += [5] appends the 5 to the
list in spam[0] (so it appends to ham), and then tries to assign the
result of it to spam[0], which is not allowed. That the item it tries to
assign is the same as the item that was already there doesn't matter.

So dont't forget += is a real assignment, even when it is an in-place
modification. Your example just proves that. The language ref manual
says:

With the exception of assigning to tuples and multiple targets in a
single statement, the assignment done by augmented assignment
statements is handled the same way as normal assignments.

But I think that your example isn't for beginners either.
 
K

kj

In said:
k> I'm having a hard time coming up with a reasonable way to explain
k> certain things to programming novices.
k> Consider the following interaction sequence:
def eggs(some_int, some_list, some_tuple):
k> ... some_int += 2
k> ... some_list += [2]
k> ... some_tuple += (2,)
k> ...
x = 42
y = (42,)
z = [42]
eggs(x, y, z)
x k> 42
y k> (42,)
z k> [42, 2]
k> How do I explain to rank beginners (no programming experience at
k> all) why x and y remain unchanged above, but not z?
You shouldn't. That's not for beginners.

No, of course not. And I don't plan to present these examples to
them. But beginners have a way of bumping into such conundrums
all on their own, and, as a former beginner myself, I can tell you
that they find them, at best, extremely frustrating, and at worst,
extremely discouraging. I doubt that an answer of the form "don't
worry your pretty little head over this now; wait until your second
course" will do the trick.

Thanks for your comments!

kj
 
K

kj

In said:
You might find the following helpful (partially):
http://effbot.org/zone/call-by-object.htm


Extremely helpful. Thanks! (I learned more from it than someone
who will teach the stuff would care to admit...)

I had not realized how *profoundly* different the meaning of the
"=" in Python's

spam = ham

is from the "=" in its

spam[3] = ham[3]

So much for "explicit is better than implicit"...


And it confirmed Paul Graham's often-made assertion that all of
programming language design is still catching up to Lisp...

kj
 
B

Bearophile

kj, as Piet van Oostrum as said, that's the difference between mutable
an immutable. It comes from the procedural nature of Python, and
probably an explanation of such topic can't be avoided if you want to
learn/teach Python.

Lot of people think that a language where everything is immutable is
simpler for newbies to understand (even if they have to learn what
higher-order functions are). That's why some people think Scheme or
other languages (even Clojure, I guess) are better for novices (Scheme
has mutability, but you can avoid showing it to newbies).

Some people say that languages that mostly encourage the use of
immutable data structures (like F#, Clojure, Scala and many other less
modern ones) help avoid bugs, maybe for novices too.

On the other hand, it's hard or impossible to actually remove
complexity from a system, and you usually just move it elsewhere. So
other things will become harder to do for those novices. I have no
idea if for the average novice it's simpler to learn to use a
immutables-based language instead of a mutables-based one (it can also
be possible that some novices prefer the first group, and other
novices prefer the second group).

From what I have seen lot of students seem able to learn Python, so
it's not a bad choice.

Python isn't perfect, and in *many* situations it is pragmatic, for
example to increase its performance. Generally for a novice programmer
running speed is not important, but it's important to have a really
coherent and clean language. I've personally seen that for such people
even Python looks very "dirty" (even if it's one of the less dirty
ones).

For example a novice wants to see 124 / 38 to return the 62/19
fraction and not 3 or 3.263157894736842 :)

People may be able to invent a clean and orthogonal language that's
easy to use and has very few compromises, fit for newbies. But this
language may be very slow, not much useful in practice (who knows?
Maybe there's a practical niche even for such very high level
language), and it doesn't teach how to use lower level languages like
C :)

Today I think there are no languages really fit for teaching. Python
is one of the few fit ones, but it's getting more and more complex as
time passes because it's getting used in more and more complex real
world situations (a language fit for newbies must not have abstract
base classes, decorators, etc). D language is too much complex for a
newbie. Java is not too much bad, but it's requires to write too much
code, it's too much fussy (semicolons at the end of lines? Newbies say
that the computer is an idiot when it refuses code just because
there's a missing useless semicolon!), and it misses some necessary
things (first class functions! Damn). A nice language like Boo running
on the Mono VM seems another option :)

In the past Pascal was good enough, but I think it's not good enough
anymore. The problem is that teaching is a niche activity (even if a
very important one). PLT Scheme is one of the few environments (beside
Squeak, Python itself, and little more) that look refined and
implemented well enough for such purpose.

See you later,
bearophile
 
N

norseman

Bearophile said:
kj, as Piet van Oostrum as said, that's the difference between mutable
an immutable. It comes from the procedural nature of Python, and
probably an explanation of such topic can't be avoided if you want to
learn/teach Python. ....(snip)

See you later,
bearophile

==========================Perhaps because it is the way I learned or the
way I "naturally" approach programming, or maybe the way one influences
the other, but at any rate I think "newbies" to programming should first
learn a few basic concepts (if/then, for, while, do and other loops and
other control constructs) and then be forced to deal with the computer
on it's own terms. Assembly. Once the student learns what the computer
already knows, that the whole thing is just bits and the combination of
them determines it's responses, it then becomes easier to present
'idealistic' concepts and their implementations. With the knowledge and
'mental picture' of the computer's nature the rest of the ideas for
programming have a tendency to drift in the direction of reality and the
underlying needs to fulfill the 'better' and/or 'easier' languages.
Having both the knowledge of the 'full capabilities' of a computer and
the experience of a formalized language the student can, or should,
learn/compare the trade offs of each. By understanding the computer (I
at least) grasp the basics of a new language quite quickly. No, I don't
become a guru overnight, if at all, but I do have the advantage in
deciding if a given language is appropriate for a given job with very
little research.

The more I delve into OOP the more I liken an 'object' to a box. A box
with a shipping manifest.

There are big boxes,
little boxes,
squat boxes and so on.

A box can contain corn flakes,
bullets, raisins, rice, burlap, silk, motorcycle(s), soap and more.

The manifest describes contents.
The manifest is there but the description(s) change with content (type).
The descriptions always use one or more of the basics like: color,
count, dimension and so forth.

Just like an OOP object.

A box can contain things of all sorts, including references to the
contents of other box(es). A box can even be a virtual of another (the
global concept). The return statement, in this context, means hauling
the contents of the box (and/or its manifest) back to (wherever) and
disposing of the current box (a local).

Just like an OOP object.


It is easier to visualize a box and it's use than a non described blob.
Abstracts are never precise - hence the evolution of the word.


The one thing a teacher will always fail is the same as anyone else who
tries to adequately describe a pretty sunset to a person born totally
blind. No point(s) of reference.



Time for me to sign off. To all those that helped me when I needed it -

I thank you very much.

Food for thought: your watch (clock) does not tell time.
The watch (clock) only mimics one movement of the earth.
ie... 3 dimensions are still static, the 4th is movement.


Steve
 
J

John Yeung

I don't plan to present these examples to them.
But beginners have a way of bumping into such
conundrums all on their own [...].  I doubt that
an answer of the form "don't worry your pretty
little head over this now; wait until your second
course" will do the trick.

I agree that beginners are bound to come across difficult issues on
their own, and when they do, you have to at least try to explain,
rather than dismiss them. I believe that the beginners which are most
curious, and thus most likely to run into things you didn't plan for
them, are also most likely to be ready and able to receive your
explanation.

That said, I think it's worth planning a fairly controlled flow of
information. For example, you can go a LONG way without touching
tuples or the augmented assignment operators.

For your function example, I suppose the key ideas to understand are
binding and containers (which may or may not be mutable). The
oversimplified version I think I would attempt to explain is that, as
far as the "outside world" is concerned, a function cannot rebind the
arguments passed to it. However, the contents of a mutable container
may be altered without rebinding. That is, you can hold a bucket,
pass that bucket to a function, and the function can put stuff in the
bucket or take stuff out without you ever letting go of the bucket.
When the function returns, you are still holding the bucket.

Nested containers, especially with "outside" bindings to inner
containers, may be tougher to visualize with real-world objects.
Hopefully by then the students can grasp a more abstract (pointerlike)
notion of binding! Good luck!

John
 
J

John Yeung

I have got very good results from teaching using
the analogy of “paper tags tied to physical objects”
to describe Python's references to values.

Ah, I like that! I think it's better than what I used in my post
(which I composed and submitted before yours showed up in my reader).

I am not formally a teacher, but I do try to help "nonprogrammers"
learn Python from time to time, and this will be a good one to
remember.

John
 
S

Steven D'Aprano

I had not realized how *profoundly* different the meaning of the "=" in
Python's

spam = ham

is from the "=" in its

spam[3] = ham[3]

So much for "explicit is better than implicit"...

I'm sorry, I don't get it. Can you explain please? I don't see why it's
so "profoundly" different. Apart from spam[3] = x not being permitted if
spam is an immutable type.

I suppose though they are *fundamentally* different, in that spam=ham is
dealt with by the compiler while spam[3]=ham is handled by the object
spam using the __setitem__ method. Is that the difference you're talking
about?
 
P

python

Ben,
I have got very good results from teaching using the analogy of "paper tags tied to physical objects" to describe Python's references to values.

Great analogy!! And an excellent analogy for newcomers to Python. (this
would have saved me some personal pain in my early days).

Regards,
Malcolm
 
G

Gabriel Genellina

I'm having a hard time coming up with a reasonable way to explain
certain things to programming novices.
ham = [1, 2, 3, 4]
spam = (ham,)
spam ([1, 2, 3, 4],)
spam[0] is ham True
spam[0] += [5]
Traceback (most recent call last):
File said:
([1, 2, 3, 4, 5, 5],)
What do you say to that?

I can come up with much mumbling about pointers and stacks and
heaps and much hand-waving about the underlying this-and-that, but
nothing that sounds even remotely illuminating.

This article is based on an old thread in this list that is very
enlightening:

How To Think Like A Pythonista
http://python.net/crew/mwh/hacks/objectthink.html

and I'll quote just a paragraph from Alex Martelli:

«There is [...] a huge difference
between changing an object, and changing (mutating) some
OTHER object to which the first refers.

In Bologna over 100 years ago we had a statue of a local hero
depicted pointing forwards with his finger -- presumably to
the future, but given where exactly it was placed, the locals
soon identified it as "the statue that points to Hotel
Belfiore". Then one day some enterprising developer bought
the hotel's building and restructured it -- in particular,
where the hotel used to be was now a restaurant, Da Carlo.

So, "the statue that points to Hotel Belfiore" had suddenly
become "the statue that points to Da Carlo"...! Amazing
isn't it? Considering that marble isn't very fluid and the
statue had not been moved or disturbed in any way...?

This is a real anecdote, by the way (except that I'm not
sure of the names of the hotel and restaurant involved --
I could be wrong on those), but I think it can still help
here. The dictionary, or statue, has not changed at all,
even though the objects it refers/points to may have been
mutated beyond recognition, and the name people know it
by (the dictionary's string-representation) may therefore
change. That name or representation was and is referring
to a non-intrinsic, non-persistent, "happenstance"
characteristic of the statue, or dictionary...»
 
S

Simon Forman

I'm having a hard time coming up with a reasonable way to explain
certain things to programming novices.

Consider the following interaction sequence:

... some_int += 2
... some_list += [2]
... some_tuple += (2,)
...
x = 42
y = (42,)
z = [42]
eggs(x, y, z)
x 42
y (42,)
z
[42, 2]

You have transposed some_list and some some_tuple. I.e. you should be
calling eggs(x, z, y).
How do I explain to rank beginners (no programming experience at
all) why x and y remain unchanged above, but not z?

You don't. Rank beginners don't have enough background knowledge to
grok that code.

Why would you even tell the poor bastards about "+=" before they were
comfortable with (python's style of) function calls, immutable
integers, mutable lists and immutable tuples?

Let them use "x = x + y" until they have enough knowledge to
understand "augmented" assignment.

Syntax matters (I mean general order of things, not linguistic
syntax.) Making an omelette requires putting eggs in a pan and
cracking them, but not in that order.
Or consider this one:
ham = [1, 2, 3, 4]
spam = (ham,)
spam ([1, 2, 3, 4],)
spam[0] is ham True
spam[0] += [5]

Traceback (most recent call last):
File said:

([1, 2, 3, 4, 5, 5],)



What do you say to that?

I say, "Don't use augmented assignment with indexed tuples."

Seriously, python's augmented assignment is almost magical. I think
you're just making trouble for yourself and your students if you
introduce it too early.

I get python pretty well (if I say so myself) but I wouldn't know how
to explain:

In [1]: def foo(a_list):
...: a_list = a_list + [5]
...:
...:

In [2]: n = []

In [3]: foo(n)

In [4]: n
Out[4]: []

In [5]: def bar(a_list):
...: a_list += [5]
...:
...:

In [6]: bar(n)

In [7]: n
Out[7]: [5]


It's "Just The Way It Is". Freakin' magic, man.
I can come up with much mumbling about pointers and stacks and
heaps and much hand-waving about the underlying this-and-that, but
nothing that sounds even remotely illuminating.

Your suggestions would be much appreciated!

Frankly, I'm of the impression that it's a mistake not to start
teaching programming with /the bit/ and work your way up from there.
I'm not kidding. I wrote a (draft) article about this: "Computer
Curriculum" http://docs.google.com/View?id=dgwr777r_31g4572gp4

I really think the only good way to teach computers and programming is
to start with a bit, and build up from there. "Ontology recapitulates
phylogeny"

I realize that doesn't help you teach python, but I wanted to put it
out there.
 
S

Steven D'Aprano

I'm having a hard time coming up with a reasonable way to explain
certain things to programming novices.
[...]


Or consider this one:
ham = [1, 2, 3, 4]
spam = (ham,)
spam ([1, 2, 3, 4],)
spam[0] is ham True
spam[0] += [5]
Traceback (most recent call last):
File said:
ham += [5]
spam ([1, 2, 3, 4, 5, 5],)
What do you say to that?


That one surely is very straight forward. Just like the exception says,
tuples don't support item assignment, so spam[0] += [5] is not allowed.

But just because you have put a list inside a tuple doesn't mean the list
stops being a list -- you can still append to the list, which is what
ham += [5] does. So spam is unchanged: it is still the one-item tuple
containing a list. It is just that the list has now been modified.

This is only troublesome (in my opinion) if you imagine that tuples are
somehow magical "frozen-lists", where the contents can't be modified once
created. That's not the case -- the tuple itself can't be modified, but
the objects inside it remain ordinary objects, and the mutable ones can
be modified.

The thing to remember is that the tuple spam doesn't know anything about
the *name* ham -- it knows the object referred to by the name ham. You
can modify the name, and nothing happens to the tuple:
spam ([1, 2, 3, 4, 5],)
ham = [5]
spam
([1, 2, 3, 4, 5],)

Or if you prefer:
ham = spam[0] # label the list inside spam as 'ham'
ham += [6] # modify the list labelled as 'ham'
spam ([1, 2, 3, 4, 5, 6],)
pork = ham # create a new label, 'pork', and bind it to the same list
del ham # throw away the label 'ham'
pork += [7] # modify the list labelled as 'pork'
spam
([1, 2, 3, 4, 5, 6, 7],)


It's all about the objects, not the names.
 
D

Dennis Lee Bieber

Frankly, I'm of the impression that it's a mistake not to start
teaching programming with /the bit/ and work your way up from there.
I'm not kidding. I wrote a (draft) article about this: "Computer
Curriculum" http://docs.google.com/View?id=dgwr777r_31g4572gp4

I really think the only good way to teach computers and programming is
to start with a bit, and build up from there. "Ontology recapitulates
phylogeny"
Sounds like one of the books I have in a box in a storage
facility... I'd try to find it but my DSL connection if flaky, and as
of this week, I can't even retrieve Amazon.com pages! Best I find via
Google is "Surreal Numbers" by Knuth (which description sounds about
right)


--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 
F

Francesco Bochicchio

I'm having a hard time coming up with a reasonable way to explain
certain things to programming novices.

Consider the following interaction sequence:

...     some_int += 2
...     some_list += [2]
...     some_tuple += (2,)
...
x = 42
y = (42,)
z = [42]
eggs(x, y, z)
x 42
y (42,)
z
[42, 2]

How do I explain to rank beginners (no programming experience at
all) why x and y remain unchanged above, but not z?

Or consider this one:
ham = [1, 2, 3, 4]
spam = (ham,)
spam ([1, 2, 3, 4],)
spam[0] is ham True
spam[0] += [5]

Traceback (most recent call last):

([1, 2, 3, 4, 5, 5],)



What do you say to that?

I can come up with much mumbling about pointers and stacks and
heaps and much hand-waving about the underlying this-and-that, but
nothing that sounds even remotely illuminating.

Your suggestions would be much appreciated!

TIA!

kj

I would go with something like this:

"""
In object oriented programming, the same function or operator can be
used to represent
different things. This is called overloading. To understand what the
operator/function do, we have to look at
the kind of object it is applied to.
In this case, the operator "+=" means two different things:
- for strings and numbers it means : "create a new object by merging
the two operands". This is why the original object is left the same.
- for lists, it means : "increase the left operand with the contents
of the right operand". This is why the original object is changed
"""

You couuld also add:
"""
You see, in python everithing is an object. Some object can be changed
(mutable objects), others cannot.
"""
but this is another story.


P:S : Sometime I think they should not have allowed += on immutables
and forced everybody to write s = s + "some more".

Ciao
 
P

Piet van Oostrum

Simon Forman said:
SF> Why would you even tell the poor bastards about "+=" before they were
SF> comfortable with (python's style of) function calls, immutable
SF> integers, mutable lists and immutable tuples?
SF> Let them use "x = x + y" until they have enough knowledge to
SF> understand "augmented" assignment.

And *then* you can tell them that "x += y" can be subtly different from
"x = x + y", which is what happened in the example that the OP gave.
 
G

Gabriel Genellina

En Wed, 08 Jul 2009 04:32:07 -0300, Francesco Bochicchio
I would go with something like this:

"""
In object oriented programming, the same function or operator can be
used to represent
different things. This is called overloading. To understand what the
operator/function do, we have to look at
the kind of object it is applied to.
In this case, the operator "+=" means two different things:
- for strings and numbers it means : "create a new object by merging
the two operands". This is why the original object is left the same.
- for lists, it means : "increase the left operand with the contents
of the right operand". This is why the original object is changed
"""

Mmm, but this isn't quite exact. += isn't an operator, but a statement
(augmented assignment). a += b translates to: [*]

a = a.__iadd__(b) [*]

It's up to the __iadd__ implementation to return the same object or a new
one, and the assignment occurs even if the same object is returned.

If you think of an assignments as binding names to objects (well, that's
the documented behavior, btw) things become more clear.
P:S : Sometime I think they should not have allowed += on immutables
and forced everybody to write s = s + "some more".

I think not everyone agreed when += was introduced.
But now, having s += "some more" allows the operation to be optimized.

[*] except 'a' is evaluated only once
 
U

Ulrich Eckhardt

Bearophile said:
For example a novice wants to see 124 / 38 to return the 62/19
fraction and not 3 or 3.263157894736842 :)

Python has adopted the latter of the three for operator / and the the second
one for operator //. I wonder if it was considered to just return a
fraction from that operation.

x = 3/5
-> x = fraction(3, 5)
x = x*7
-> x = fraction(21, 5)
x = x/2
-> x = fraction(21, 10)
range(x)
-> error, x not an integer
range(int(x))
-> [0, 1,]
y = float(x)
-> y = 2.1

This would have allowed keeping the operator what people are used to. On the
other hand, implicit conversion to either of float or int would have been
avoided, which is usually the #1 cause for subtle problems.


Uli
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,149
Latest member
Vinay Kumar Nevatia0
Top