[...]
Tuples and lists really are intended to serve two fundamentally
different purposes. We might guess that just from the fact that
both are included in Python, in fact we hear it from Guido van
Rossum, and one might add that other languages also make this
distinction (more clearly than Python.)
As I'm sure everyone still reading has already heard, the
natural usage of a tuple is as a heterogenous sequence. I would
like to explain this using the concept of an "application type",
by which I mean the set of values that would be valid when
applied to a particular context. For example, os.spawnv() takes
as one of its arguments a list of command arguments,
time.mktime() takes a tuple of time values. A homogeneous
sequence is one where a and a[x:y] (where x:y is not 0:-1)
have the same application type. A list of command arguments is
clearly homogeneous in this sense - any sequence of strings is a
valid input, so any slice of this sequence must also be valid.
(Valid in the type sense, obviously the value and thus the
result must change.) A tuple of time values, though, must have
exactly 9 elements, so it's heterogeneous in this sense, even
though all the values are integer.
One doesn't count elements in this kind of a tuple, because it's
presumed to have a natural predefined number of elements. One
doesn't search for values in this kind of a tuple, because the
occurrence of a value has meaning only in conjunction with its
location, e.g., t[4] is how many minutes past the hour, but t[5]
is how many seconds, etc.
I have to confess that this wasn't obvious to me, either, at
first, and in fact probably about half of my extant code is
burdened with the idea that a tuple is a smart way to economize
on the overhead of a list. Somewhere along the line, I guess
about 5 years ago? maybe from reading about it here, I saw the
light on this, and since then my code has gotten easier to read
and more robust. Lists really are better for all the kinds of
things that lists are for -- just for example, [1] reads a lot
better than (1,) -- and the savings on overhead is not worth the
cost to exploit it. My tendency to seize on this foolish
optimization is however pretty natural, as is the human tendency
to try to make two similar things interchangeable. So we're
happy to see that tuple does not have the features it doesn't
need, because it helps in a small way to make Python code
better. If only by giving us a chance to have this little chat
once in a while.
Donn, this is a reasonable argument, and in general I don't have a
problem with the distinction between tuples and lists. I have heard
and understand the argument that the intended purpose of tuple
creation is to mimic C structs, so it seems reasonable to suppose
that one knows what was placed in them. Lists are dynamic by
nature, so you need a little more help getting information about
their current state.
However, there is at least one area where this distinction is
bogus. Lists cannot be used as dictionary keys (as it now stands).
But in practice, it is often useful to create a list of values,
cast the list to a tuple, and use that as a dictionary key. It
makes little sense to keep a list of that same information around,
so in practice, the tuple/key is the container that retains the
original information. But that tuple was dynamically created, and
it isn't always true that items were placed in it deliberately.
In other words, the fact that the key is now a tuple is unrelated
to the essential nature of tuples. Not all of the tools used in
examining lists are available to the key as a tuple, though it is
really nothing more than a frozen list.
Sure, you can cast it to a list to use the list methods, but that
requires creating objects just to throw away, which seems a little
wasteful, especially since that's what you had to do to create the
key to begin with.
I'm sure Antoon wouldn't object if lists were to be allowed as
dictionary keys, which would eliminate the multiple castings for
that situation. I wouldn't, either.
I'd extend this a little to say that tuples are (at least
potentially) created dynamically quite often in other contexts as
well, so that despite their designed intent, in practice they are
used a little differently a good bit of the time. So why not adjust
the available features to the practice?