Python Isn't Perfect: adding a 'gotchas' section to the tutorial

Although I love Python there are some aspects of the language design which are disappointing and which can even lead to problems in some cases.
A classic example is a mutable default argument having the potential to produce unexpected side-effects, as a consequence of the non-intuitive scoping rules.
Another awkward 'feature' is the requirement for a trailing comma in singleton tuples, due I believe to the use of expression parentheses rather than (say) the use of special brackets like chevrons.
Something that I personally wish for is the ability to declare variable types 'up front' but that facility is missing from Python.
This is an important issue, so I propose that the Python tutorial be updated to highlight such problems. I would be willing to write a draft section myself but obviously it would need to be reviewed.
I am not sure if this is the appropriate place to make such a comment but it seems to be a good starting point. Any advice on making a more formal proposal would be welcome.
Cheers,
Richard Prosser PS Is it too late to fix such warts in version 3?

On 2011-12-10, at 15:16 , Richard Prosser wrote:
A classic example is a mutable default argument having the potential to produce unexpected side-effects, as a consequence of the non-intuitive scoping rules.
As far as I know, mutable default arguments have nothing to do with scoping, they have to do with the "toplevel" being evaluated fully, so as the function declaration is evaluated (to create the function object) so are its default arguments. This is independent from Python's scoping issues unless I misunderstood what you meant by "scoping".
But this is definitely something which trips people. However, there is already a note (though a pretty low-key one, it should probably use an actual warning directive instead of just bolding it, you should submit a documentation patch) in the tutorial on that subject[0].
Another awkward 'feature' is the requirement for a trailing comma in singleton tuples, due I believe to the use of expression parentheses rather than (say) the use of special brackets like chevrons.
For tuples, there are no matching operators left, as literal sets have been added.
And technically, the irregularity with tuples is probably the empty tuple `()` as parens in other tuple arities are only necessary for disambiguation (much like parens around generator expressions): the "tuple constructor" is the comma, not the parens,
a = 1, b = 1, 2 c = 1, 2, 3
are all valid and generate respectively a singleton, a pair and a triple. In that context, the trailing comma for singletons makes sense. If you want regularity, you can even add a trailing comma to the pair and the triple (as you can in e.g. a list or a dict):
a = 1, b = 1, 2, c = 1, 2, 3,
I'd rather have a lone comma (with or without parens, depending on the context) create a null tuple.
Something that I personally wish for is the ability to declare variable types 'up front' but that facility is missing from Python.
I fail to see how this is a "gotcha": since Python is dynamically typed names don't have types (well technically Python 3 added documentary type specs to arguments, but they're not used by any implementation I know of though some third-party tools may already have started using them)
[0] http://docs.python.org/tutorial/controlflow.html#default-argument-values

On Sat, Dec 10, 2011 at 02:16:21PM +0000, Richard Prosser wrote:
Although I love Python there are some aspects of the language design which are disappointing and which can even lead to problems in some cases.
What really is disappointing is the number of people who criticize Python without knowing it.
Another awkward 'feature' is the requirement for a trailing comma in singleton tuples, due I believe to the use of expression parentheses rather than (say) the use of special brackets like chevrons.
You do not understand the syntax. Parens do not construct tuples - commas do. So for every tuple - even of length 1 - you must have a comma. The only exception is an empty tuple (of length 0).
Something that I personally wish for is the ability to declare variable types 'up front' but that facility is missing from Python.
You can use annotations. See PEP 3107. Twas implemented in Python 3.0.
Oleg.

On 10 December 2011 14:16, Richard Prosser richard.prosser@mail.com wrote:
Although I love Python there are some aspects of the language design which are disappointing and which can even lead to problems in some cases.
A classic example is a mutable default argument having the potential to produce unexpected side-effects, as a consequence of the non-intuitive scoping rules.
The default argument "problem" is not intuitive (and does need highlighting to people new to Python). But it is still better than the alternatives. It isn't to do with scoping but when the default arguments are evaluated. If instead the default values were evaluated at call time, what would the following code do:
a = 3 def function(arg=a): pass del a function()
In addition you have added extra overhead cost to each function call (argument evaluation). Plus Python exposes the default arguments to introspection. If the default arguments aren't evaluated until call time you lose that capability.
Another awkward 'feature' is the requirement for a trailing comma in singleton tuples, due I believe to the use of expression parentheses rather than (say) the use of special brackets like chevrons.
Well, parentheses are only part of the syntax for an empty tuple: ()
For non-empty tuple literals it is *only* the commas that are significant.
single = 1, double = 2, 3
So although the syntax for a single member tuple does feel a bit awkward I wonder what alternative you would suggest?
Something that I personally wish for is the ability to declare variable types 'up front' but that facility is missing from Python.
That runs counter to the basic principles of Python where types are determined at runtime. What effect would declaring types have and how would that interact with the rest of the language? (Do you want every assignment to do a runtime type check?)
This is an important issue, so I propose that the Python tutorial be updated to highlight such problems. I would be willing to write a draft section myself but obviously it would need to be reviewed.
This is a good idea however. :-) Just do it. Create a documentation patch (for Python 3) and attach it to an issue in the bug tracker:
I am not sure if this is the appropriate place to make such a comment but it seems to be a good starting point. Any advice on making a more formal proposal would be welcome.
Cheers,
Richard Prosser PS Is it too late to fix such warts in version 3?
For the issues you've raised, yes. Unless you have specific proposals that don't break backwards compatibility.
All the best,
Michael Foord
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas

Hello Richard,
On Sat, 10 Dec 2011 14:16:21 +0000 Richard Prosser richard.prosser@mail.com wrote:
This is an important issue, so I propose that the Python tutorial be updated to highlight such problems. I would be willing to write a draft section myself but obviously it would need to be reviewed.
I think documenting "gotchas" can be useful indeed. However, I'm not sure the tutorial is the right place: it should present an easy to grasp view of the language, not digress about edge cases.
So perhaps a FAQ, for example, would be more appropriate.
In any case, feel free to propose a draft on http://bugs.python.org You can take a look at http://docs.python.org/dev/documenting/ if you are not familiar with the process.
Regards
Antoine.

On 12/10/2011 9:16 AM, Richard Prosser wrote:
Although I love Python there are some aspects of the language design which are disappointing and which can even lead to problems in some cases.
A classic example is a mutable default argument having the potential to produce unexpected side-effects, as a consequence of the non-intuitive scoping rules.
Another awkward 'feature' is the requirement for a trailing comma in singleton tuples, due I believe to the use of expression parentheses rather than (say) the use of special brackets like chevrons.
Something that I personally wish for is the ability to declare variable types 'up front' but that facility is missing from Python.
One of these things is not like the others. Mutable default arguments, and singleton tuples are surprises that make sense once you understand things on a deeper level. It makes sense to call them out as a "gotcha": a common stumbling block for learners. But "no type declarations" is not a wart, it's a fundamental feature of the language that is immediately apparent from the first lesson.
--Ned.

On 2011-12-10, at 15:42 , Oleg Broytman wrote:
You can use annotations. See PEP 3107. Twas implemented in Python 3.0.
It's not used by the runtime though (I think IDEA/PyCharm uses it, but I wouldn't bet on it), so it's mostly documentary at this point.

On Mon, Dec 12, 2011 at 2:38 PM, Masklinn masklinn@masklinn.net wrote:
On 2011-12-10, at 15:42 , Oleg Broytman wrote:
You can use annotations. See PEP 3107. Twas implemented in Python 3.0.
It's not used by the runtime though (I think IDEA/PyCharm uses it, but I wouldn't bet on it), so it's mostly documentary at this point.
<shameless> pyopt uses annotations as types for parsing command line options if that counts:
http://code.google.com/p/pyopt/
</shameless>
--Yuval

On 12/10/2011 9:42 AM, Oleg Broytman wrote:
On Sat, Dec 10, 2011 at 02:16:21PM +0000, Richard Prosser wrote:
Although I love Python there are some aspects of the language design which are disappointing and which can even lead to problems in some cases.
What really is disappointing is the number of people who criticize
Python without knowing it.
Another awkward 'feature' is the requirement for a trailing comma in singleton tuples, due I believe to the use of expression parentheses rather than (say) the use of special brackets like chevrons.
You do not understand the syntax. Parens do not construct tuples -
commas do. So for every tuple - even of length 1 - you must have a comma. The only exception is an empty tuple (of length 0).
I don't think we have to go as far as blaming the user. Tuple syntax is a little tricky, people often trip up on (x,) as a single-item tuple. You and I understand why it is, and there isn't a better alternative, but that one-item syntax sticks out when compared to the others: (), (x, y), (x, y, z), etc. This is a true "gotcha" as Richard originally expressed it.
Oleg.
--Ned.

(a,) is the real gotcha, not ()
On Tue, Dec 13, 2011 at 12:00 AM, Ned Batchelder ned@nedbatchelder.com wrote:
On 12/10/2011 9:42 AM, Oleg Broytman wrote:
On Sat, Dec 10, 2011 at 02:16:21PM +0000, Richard Prosser wrote:
Although I love Python there are some aspects of the language design which are disappointing and which can even lead to problems in some cases.
What really is disappointing is the number of people who criticize Python without knowing it.
Another awkward 'feature' is the requirement for a trailing comma in singleton tuples, due I believe to the use of expression parentheses rather than (say) the use of special brackets like chevrons.
You do not understand the syntax. Parens do not construct tuples - commas do. So for every tuple - even of length 1 - you must have a comma. The only exception is an empty tuple (of length 0).
I don't think we have to go as far as blaming the user. Tuple syntax is a little tricky, people often trip up on (x,) as a single-item tuple. You and I understand why it is, and there isn't a better alternative, but that one-item syntax sticks out when compared to the others: (), (x, y), (x, y, z), etc. This is a true "gotcha" as Richard originally expressed it.
Oleg.
--Ned.
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas

On Mon, 12 Dec 2011 08:00:28 -0500 Ned Batchelder ned@nedbatchelder.com wrote:
On 12/10/2011 9:42 AM, Oleg Broytman wrote:
On Sat, Dec 10, 2011 at 02:16:21PM +0000, Richard Prosser wrote:
Although I love Python there are some aspects of the language design which are disappointing and which can even lead to problems in some cases.
What really is disappointing is the number of people who criticize
Python without knowing it.
Another awkward 'feature' is the requirement for a trailing comma in singleton tuples, due I believe to the use of expression parentheses rather than (say) the use of special brackets like chevrons.
You do not understand the syntax. Parens do not construct tuples -
commas do. So for every tuple - even of length 1 - you must have a comma. The only exception is an empty tuple (of length 0).
I don't think we have to go as far as blaming the user. Tuple syntax is a little tricky, people often trip up on (x,) as a single-item tuple. You and I understand why it is, and there isn't a better alternative, but that one-item syntax sticks out when compared to the others: (), (x, y), (x, y, z), etc. This is a true "gotcha" as Richard originally expressed it.
I think it would be more of a gotcha if parentheses were enough to create a tuple, though. Parentheses are useful to group operations, either for stylistic / syntactic support (think multi-line statements), or to work around operator precedence. Creating a tuple by mistake because you put some parentheses where not necessary would be really annoying.
Regards
Antoine.

On 12/12/2011 8:07 AM, Antoine Pitrou wrote:
On Mon, 12 Dec 2011 08:00:28 -0500 Ned Batchelderned@nedbatchelder.com wrote:
On 12/10/2011 9:42 AM, Oleg Broytman wrote:
On Sat, Dec 10, 2011 at 02:16:21PM +0000, Richard Prosser wrote:
Although I love Python there are some aspects of the language design which are disappointing and which can even lead to problems in some cases.
What really is disappointing is the number of people who criticize
Python without knowing it.
Another awkward 'feature' is the requirement for a trailing comma in singleton tuples, due I believe to the use of expression parentheses rather than (say) the use of special brackets like chevrons.
You do not understand the syntax. Parens do not construct tuples -
commas do. So for every tuple - even of length 1 - you must have a comma. The only exception is an empty tuple (of length 0).
I don't think we have to go as far as blaming the user. Tuple syntax is a little tricky, people often trip up on (x,) as a single-item tuple. You and I understand why it is, and there isn't a better alternative, but that one-item syntax sticks out when compared to the others: (), (x, y), (x, y, z), etc. This is a true "gotcha" as Richard originally expressed it.
I think it would be more of a gotcha if parentheses were enough to create a tuple, though. Parentheses are useful to group operations, either for stylistic / syntactic support (think multi-line statements), or to work around operator precedence. Creating a tuple by mistake because you put some parentheses where not necessary would be really annoying.
Believe me, I understand the issues. It is true, though that the single-element tuple syntax is often a surprise to people, and often well into their Python learning experience. We often repeat, "it isn't parens that make a tuple, but a comma." Then why when displaying a tuple does Python insist on using parens around it?
1, 2, 3
(1, 2, 3)
I'm not saying it shouldn't, it's a rhetorical question. The repr of a tuple always includes parens, even though "parens don't make a tuple." It's the best of all the options, but let's face it: it's confusing.
--Ned.
Regards
Antoine.
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas

Le lundi 12 décembre 2011 à 08:15 -0500, Ned Batchelder a écrit :
Believe me, I understand the issues. It is true, though that the single-element tuple syntax is often a surprise to people, and often well into their Python learning experience. We often repeat, "it isn't parens that make a tuple, but a comma." Then why when displaying a tuple does Python insist on using parens around it?
1, 2, 3
(1, 2, 3)
I would say: - because it's easier to read (subjectively so, I guess) - because it's easier to copy/paste into an expression without running into precedence problems
Regards
Antoine.

On 2011-12-12, at 14:15 , Ned Batchelder wrote:
Believe me, I understand the issues. It is true, though that the single-element tuple syntax is often a surprise to people, and often well into their Python learning experience. We often repeat, "it isn't parens that make a tuple, but a comma." Then why when displaying a tuple does Python insist on using parens around it?
1, 2, 3
(1, 2, 3)
I'm not saying it shouldn't, it's a rhetorical question. The repr of a tuple always includes parens, even though "parens don't make a tuple." It's the best of all the options, but let's face it: it's confusing.
An alternative would be to just drop the literal unary tuple. I would be kind-of sad as I'd lose the ability to unpack singleton iterables, but it would "fix" the issue.
FWIW, Haskell does not have a literal singleton (the standard defines "unit" `()` and 2-tuple through 15-tuple)

On 12/12/2011 8:19 AM, Antoine Pitrou wrote:
Le lundi 12 décembre 2011 à 08:15 -0500, Ned Batchelder a écrit :
Believe me, I understand the issues. It is true, though that the single-element tuple syntax is often a surprise to people, and often well into their Python learning experience. We often repeat, "it isn't parens that make a tuple, but a comma." Then why when displaying a tuple does Python insist on using parens around it?
1, 2, 3
(1, 2, 3)
I would say:
- because it's easier to read (subjectively so, I guess)
- because it's easier to copy/paste into an expression without running
into precedence problems
Yes, those are good reasons. And you can see why Python's insistence on showing tuples with parens contributes to the gotcha that the parens are a red herring, and it's the commas that are important. Also, who hasn't said this to a beginner: "lists and tuples are very similar, lists use square brackets, tuples use parens"? Somehow, in a list, the commas don't make a tuple... It's complicated.
I welcome Richard's help in explaining this issue to beginners.
--Ned.
Regards
Antoine.
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas

Here's some crazy ideas: * Don't allow iteration on tuples. for i in tuple * Make the singleton tuple the default object representation: (), a, (a, b), (a, b, c), ... * Steal STM, Channels, and sparks from Haskell * Profit.
On Tue, Dec 13, 2011 at 12:26 AM, Ned Batchelder ned@nedbatchelder.com wrote:
On 12/12/2011 8:19 AM, Antoine Pitrou wrote:
Le lundi 12 décembre 2011 à 08:15 -0500, Ned Batchelder a écrit :
Believe me, I understand the issues. It is true, though that the single-element tuple syntax is often a surprise to people, and often well into their Python learning experience. We often repeat, "it isn't parens that make a tuple, but a comma." Then why when displaying a tuple does Python insist on using parens around it?
>>> 1, 2, 3 (1, 2, 3)
I would say:
- because it's easier to read (subjectively so, I guess)
- because it's easier to copy/paste into an expression without running
into precedence problems
Yes, those are good reasons. And you can see why Python's insistence on showing tuples with parens contributes to the gotcha that the parens are a red herring, and it's the commas that are important. Also, who hasn't said this to a beginner: "lists and tuples are very similar, lists use square brackets, tuples use parens"? Somehow, in a list, the commas don't make a tuple... It's complicated.
I welcome Richard's help in explaining this issue to beginners.
--Ned.
Regards
Antoine.
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas

On 12/12/2011 8:15 AM, Ned Batchelder wrote:
parens that make a tuple, but a comma." Then why when displaying a tuple does Python insist on using parens around it?
1, 2, 3
(1, 2, 3)
I'm not saying it shouldn't, it's a rhetorical question.
Actually, I would agree that it would be better to not mislead by not printing the parens unless necessary. (If done from the beginning.) For something like
t = 1,2 l = list(t) t,l
((1, 2), [1, 2])
the outer parens are also unnecessary (while the inner ones are needed) and make the result less easy to read. But the easiest way to not print them would probably be to test the output string after it is constructed for beginning with '(' and ending with ')' and strip them off if so.

Masklinn wrote:
FWIW, Haskell does not have a literal singleton (the standard defines "unit" `()` and 2-tuple through 15-tuple)
That's because, due to its static typing, there is no reason you would ever need to use a 1-tuple rather than a bare value. We're not that lucky in Python, though.

Greg Ewing writes:
Masklinn wrote:
FWIW, Haskell does not have a literal singleton (the standard defines "unit" `()` and 2-tuple through 15-tuple)
That's because, due to its static typing, there is no reason you would ever need to use a 1-tuple rather than a bare value. We're not that lucky in Python, though.
I think you have misstated your point? That's not due to static typing, that's because you may *always* identify 1-factor products with the only factor, and Haskell made a deliberate decision to consistently represent the isomorphism class by the factor rather than the product. Eg, Python uses the same strategy for characters and strings (characters as a category are isomorphic to one-element strings), but chose a different representative (the one-element string rather than the character).
As for Python's relative lack of luck, I think that's unlikely to be the reason why things are as they are. I'm pretty sure that Python's omission of character objects was deliberate<wink/>. I think it was the right choice, given that (1) despite category theory, I believe we think of tuples as composite objects (at least I do), but strings seem to be more "monolithic" in some sense, and (2) pretty much all languages have chosen to leave identifiers unmarked (I guess Perl and many template languages should be considered exceptions), and mark strings and characters (often both with quotation marks, sometimes characters with a prefix) -- may as well go with the familiar if there's no good reason otherwise.

On 12/13/2011 03:44 AM, Stephen J. Turnbull wrote:
Greg Ewing writes:
Masklinn wrote:
FWIW, Haskell does not have a literal singleton (the standard defines "unit" `()` and 2-tuple through 15-tuple)
That's because, due to its static typing, there is no reason you would ever need to use a 1-tuple rather than a bare value. We're not that lucky in Python, though.
I think you have misstated your point? That's not due to static typing, that's because you may *always* identify 1-factor products with the only factor, and Haskell made a deliberate decision to consistently represent the isomorphism class by the factor rather than the product.
Well, I would say the reason is that the type "tuple of any length" does not exist in Haskell. So there's no way you will have to pass a 1-tuple to a function that operates on tuples only.
But of course, if we all used tuples as tuples only, we wouldn't have to do that either. It's only because we use tuples as sequences every so often.
Georg

On 12/15/2011 3:24 PM, Georg Brandl wrote:
On 12/13/2011 03:44 AM, Stephen J. Turnbull wrote:
Greg Ewing writes:
Masklinn wrote:
FWIW, Haskell does not have a literal singleton (the standard defines "unit" `()` and 2-tuple through 15-tuple)
That's because, due to its static typing, there is no reason you would ever need to use a 1-tuple rather than a bare value. We're not that lucky in Python, though.
I think you have misstated your point? That's not due to static typing, that's because you may *always* identify 1-factor products with the only factor, and Haskell made a deliberate decision to consistently represent the isomorphism class by the factor rather than the product.
Well, I would say the reason is that the type "tuple of any length" does not exist in Haskell. So there's no way you will have to pass a 1-tuple to a function that operates on tuples only.
But of course, if we all used tuples as tuples only, we wouldn't have to do that either. It's only because we use tuples as sequences every so often.
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct." Then we define a function foo(*args), and Python gives us a tuple! :-(
--Ned.
Georg
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas

On 12/15/2011 09:42 PM, Ned Batchelder wrote:
On 12/15/2011 3:24 PM, Georg Brandl wrote:
On 12/13/2011 03:44 AM, Stephen J. Turnbull wrote:
Greg Ewing writes:
Masklinn wrote:
FWIW, Haskell does not have a literal singleton (the standard defines "unit" `()` and 2-tuple through 15-tuple)
That's because, due to its static typing, there is no reason you would ever need to use a 1-tuple rather than a bare value. We're not that lucky in Python, though.
I think you have misstated your point? That's not due to static typing, that's because you may *always* identify 1-factor products with the only factor, and Haskell made a deliberate decision to consistently represent the isomorphism class by the factor rather than the product.
Well, I would say the reason is that the type "tuple of any length" does not exist in Haskell. So there's no way you will have to pass a 1-tuple to a function that operates on tuples only.
But of course, if we all used tuples as tuples only, we wouldn't have to do that either. It's only because we use tuples as sequences every so often.
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct." Then we define a function foo(*args), and Python gives us a tuple! :-(
Yep. To be consistent, we'd need an "immutable list" type... another thing that Haskell has no need for :)
Georg

On Thu, 15 Dec 2011 15:42:42 -0500 Ned Batchelder ned@nedbatchelder.com wrote:
On 12/15/2011 3:24 PM, Georg Brandl wrote:
On 12/13/2011 03:44 AM, Stephen J. Turnbull wrote:
Greg Ewing writes:
Masklinn wrote:
FWIW, Haskell does not have a literal singleton (the standard defines "unit" `()` and 2-tuple through 15-tuple)
That's because, due to its static typing, there is no reason you would ever need to use a 1-tuple rather than a bare value. We're not that lucky in Python, though.
I think you have misstated your point? That's not due to static typing, that's because you may *always* identify 1-factor products with the only factor, and Haskell made a deliberate decision to consistently represent the isomorphism class by the factor rather than the product.
Well, I would say the reason is that the type "tuple of any length" does not exist in Haskell. So there's no way you will have to pass a 1-tuple to a function that operates on tuples only.
But of course, if we all used tuples as tuples only, we wouldn't have to do that either. It's only because we use tuples as sequences every so often.
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct." Then we define a function foo(*args), and Python gives us a tuple! :-(
How is it inconsistent? Function signatures generally have a fixed (or mostly fixed) number of heterogenous arguments.
Regards
Antoine.

On 16 December 2011 07:42, Ned Batchelder ned@nedbatchelder.com wrote:
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct." Then we define a function foo(*args), and Python gives us a tuple! :-(
How is that inconsistent? At the point where the tuple is constructed, it has a known length. And it's definitely a heterogenous aggregation.
I think where you're getting confused is that you're thinking of a *single* struct definition for every tuple. But the concept you should have is that each tuple has its own struct definition. And with functions, the structure is defined at function call time.
Tim Delaney

On 12/15/2011 3:51 PM, Antoine Pitrou wrote:
On Thu, 15 Dec 2011 15:42:42 -0500 Ned Batchelderned@nedbatchelder.com wrote:
On 12/15/2011 3:24 PM, Georg Brandl wrote:
On 12/13/2011 03:44 AM, Stephen J. Turnbull wrote:
Greg Ewing writes:
Masklinn wrote:
FWIW, Haskell does not have a literal singleton (the standard defines "unit" `()` and 2-tuple through 15-tuple)
That's because, due to its static typing, there is no reason you would ever need to use a 1-tuple rather than a bare value. We're not that lucky in Python, though.
I think you have misstated your point? That's not due to static typing, that's because you may *always* identify 1-factor products with the only factor, and Haskell made a deliberate decision to consistently represent the isomorphism class by the factor rather than the product.
Well, I would say the reason is that the type "tuple of any length" does not exist in Haskell. So there's no way you will have to pass a 1-tuple to a function that operates on tuples only.
But of course, if we all used tuples as tuples only, we wouldn't have to do that either. It's only because we use tuples as sequences every so often.
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct." Then we define a function foo(*args), and Python gives us a tuple! :-(
How is it inconsistent? Function signatures generally have a fixed (or mostly fixed) number of heterogenous arguments.
What? The whole point of the "def foo(*args)" syntax is so a function can take an unknown-length list of arguments, which will be treated uniformly.
--Ned.
Regards
Antoine.
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas

Ned Batchelder wrote:
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct." Then we define a function foo(*args), and Python gives us a tuple! :-(
Where is that in the docs? Sounds like a patch is needed:
"lists are for sequences where items need to be added/removed; tuples are for sequences/aggregations where items will not be added/removed once the tuple is created"
~Ethan~
PS As Antoine noted, a tuple for 'args' is appropriate, as once args is created at function call time, we won't be adding or removing from it.

On 2011-12-15, at 21:51 , Antoine Pitrou wrote:
On Thu, 15 Dec 2011 15:42:42 -0500 Ned Batchelder ned@nedbatchelder.com wrote:
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct." Then we define a function foo(*args), and Python gives us a tuple! :-(
How is it inconsistent? Function signatures generally have a fixed (or mostly fixed) number of heterogenous arguments.
But *args is mostly dedicated to an arbitrary (non-fixed) number of homogenous arguments. In statically typed languages with varargs, they're generally represented as an array-type collection.
It's either that or a "rest of that crap" catchall used to forward arguments without caring much for what they are.

On 2011-12-15, at 22:00 , Ned Batchelder wrote:
What? The whole point of the "def foo(*args)" syntax is so a function can take an unknown-length list of arguments, which will be treated uniformly.
That's not *entirely* true, it's also used for proxy functions, in order to just pass a bunch of arguments (positional or keywords) along to the next level without having to unpack and repack them one by one.

On 12/15/2011 4:00 PM, Ethan Furman wrote:
Ned Batchelder wrote:
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct." Then we define a function foo(*args), and Python gives us a tuple! :-(
Where is that in the docs? Sounds like a patch is needed:
"lists are for sequences where items need to be added/removed; tuples are for sequences/aggregations where items will not be added/removed once the tuple is created"
I don't know if it appears in the docs, but I hear it all the time, and Guido has said it (http://mail.python.org/pipermail/python-dev/2003-March/033964.html):
Tuples are for heterogeneous data, list are for homogeneous data. Tuples are *not* read-only lists.
I don't want to get too far off the original point, which was: Python isn't as simple as we'd like to thing, and even smart beginners can be tripped up by things we've provided to them.
--Ned.
~Ethan~
PS As Antoine noted, a tuple for 'args' is appropriate, as once args is created at function call time, we won't be adding or removing from it.

On 12/15/2011 3:59 PM, Tim Delaney wrote:
On 16 December 2011 07:42, Ned Batchelder <ned@nedbatchelder.com mailto:ned@nedbatchelder.com> wrote:
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct." Then we define a function foo(*args), and Python gives us a tuple! :-(
How is that inconsistent? At the point where the tuple is constructed, it has a known length. And it's definitely a heterogenous aggregation.
I think where you're getting confused is that you're thinking of a *single* struct definition for every tuple. But the concept you should have is that each tuple has its own struct definition. And with functions, the structure is defined at function call time.
Tim, this seems misguided to me. Finish that foo function definition: it will *have* to have "for a in args:" Since I don't know the length of args when I write the function, I have to treat it as an unknown length. What good is a "structure" that changes length and definition with every instance? I think you're trying too hard to fit the reality into the theory.
--Ned.
Tim Delaney
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas

On 12/15/2011 4:06 PM, Masklinn wrote:
On 2011-12-15, at 22:00 , Ned Batchelder wrote:
What? The whole point of the "def foo(*args)" syntax is so a function can take an unknown-length list of arguments, which will be treated uniformly.
That's not *entirely* true, it's also used for proxy functions, in order to just pass a bunch of arguments (positional or keywords) along to the next level without having to unpack and repack them one by one.
We are in agreement: I don't know the length, and I will treat them uniformly (pass them all through to the next function).
--Ned.

Ned Batchelder wrote:
On 12/15/2011 3:59 PM, Tim Delaney wrote:
On 16 December 2011 07:42, Ned Batchelder wrote:
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct." Then we define a function foo(*args), and Python gives us a tuple! :-(
How is that inconsistent? At the point where the tuple is constructed, it has a known length. And it's definitely a heterogenous aggregation.
I think where you're getting confused is that you're thinking of a *single* struct definition for every tuple. But the concept you should have is that each tuple has its own struct definition. And with functions, the structure is defined at function call time.
Tim, this seems misguided to me. Finish that foo function definition: it will *have* to have "for a in args:" Since I don't know the length of args when I write the function, I have to treat it as an unknown length. What good is a "structure" that changes length and definition with every instance? I think you're trying too hard to fit the reality into the theory.
Python is a dynamic language -- why can't it have dynamic structs?
~Ethan~

On 12/15/2011 4:24 PM, Ethan Furman wrote:
Ned Batchelder wrote:
On 12/15/2011 3:59 PM, Tim Delaney wrote:
On 16 December 2011 07:42, Ned Batchelder wrote:
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct." Then we define a function foo(*args), and Python gives us a tuple! :-(
How is that inconsistent? At the point where the tuple is constructed, it has a known length. And it's definitely a heterogenous aggregation.
I think where you're getting confused is that you're thinking of a *single* struct definition for every tuple. But the concept you should have is that each tuple has its own struct definition. And with functions, the structure is defined at function call time.
Tim, this seems misguided to me. Finish that foo function definition: it will *have* to have "for a in args:" Since I don't know the length of args when I write the function, I have to treat it as an unknown length. What good is a "structure" that changes length and definition with every instance? I think you're trying too hard to fit the reality into the theory.
Python is a dynamic language -- why can't it have dynamic structs?
I honestly don't know how to interpret this. In what way is it a "struct" if I have no idea how many fields it has, or how they might differ from each other? I think we've lost the utility of this discussion.
--Ned.
~Ethan~

On 15 déc. 2011, at 22:11, Ned Batchelder ned@nedbatchelder.com wrote:
On 12/15/2011 4:06 PM, Masklinn wrote:
On 2011-12-15, at 22:00 , Ned Batchelder wrote:
What? The whole point of the "def foo(*args)" syntax is so a function can take an unknown-length list of arguments, which will be treated uniformly.
That's not *entirely* true, it's also used for proxy functions, in order to just pass a bunch of arguments (positional or keywords) along to the next level without having to unpack and repack them one by one.
We are in agreement: I don't know the length, and I will treat them uniformly (pass them all through to the next function).
Ah yes, you're right, I had not considered this use case under that angle.

On Fri, Dec 16, 2011 at 7:06 AM, Ned Batchelder ned@nedbatchelder.com wrote:
Where is that in the docs? Sounds like a patch is needed:
"lists are for sequences where items need to be added/removed; tuples are for sequences/aggregations where items will not be added/removed once the tuple is created"
I don't know if it appears in the docs, but I hear it all the time, and Guido has said it (http://mail.python.org/pipermail/python-dev/2003-March/033964.html):
Tuples are for heterogeneous data, list are for homogeneous data. Tuples are *not* read-only lists.
I don't want to get too far off the original point, which was: Python isn't as simple as we'd like to thing, and even smart beginners can be tripped up by things we've provided to them.
Guido has softened his stance on that point over the years (IIRC, the question came up explicitly in the discussion over making tuple() fully conform to the Sequence ABC - you can guess the outcome from the fact that tuple these days in fact *does* fully conform to that ABC, including the previously missing index() and count() methods).
So tuples have two valid use cases: as read-only arbitrary-length sequences of homogeneous data and as fixed-length sequences of heterogeneous data.
These days, the latter use case is often better served by creating a collections.namedtuple() definition rather than using a bare tuple directly.
Cheers, Nick.

On 12/15/2011 3:42 PM, Ned Batchelder wrote:
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct."
I have not been told that for several years, and I am pretty sure you will not find any such thing in the current docs. I consider it pretty much obsolete, as the differences that flowed from that idea are gone. In Python 3, tuples have all the non-mutating sequence methods that list does. The situation was much different in 1.4.

On Thu, Dec 15, 2011 at 5:16 PM, Terry Reedy tjreedy@udel.edu wrote:
On 12/15/2011 3:42 PM, Ned Batchelder wrote:
This is another place where Python is inconsistent. We're told, "lists
are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct."
I have not been told that for several years, and I am pretty sure you will not find any such thing in the current docs. I consider it pretty much obsolete, as the differences that flowed from that idea are gone. In Python 3, tuples have all the non-mutating sequence methods that list does. The situation was much different in 1.4.
I strongly disagree. Being immutable sequences (i.e. homogeneous) is a minor secondary role for tuples. Their primary role remains to hold a small bunch of heterogeneous values -- like namedtuple, but without needing forethought. A good example are dictionary items -- these are (key, value) pairs where for a given dict, the keys are all of the same type (or of a small set of related types) and ditto for the values, but the key type and the value types are unrelated.

Ned Batchelder ned@nedbatchelder.com writes:
This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct."
I think that's a poor rendition of the distinction. Rather, what I advise is: if the *meaning* of an item depends on its position in the sequence (like with a C struct), use a tuple. If the meaning of an item is unaffected by its position in the sequence, use a list.
Then we define a function foo(*args), and Python gives us a tuple! :-(
Yes, exactly: the positional arguments to the function are *not* a homogeneous sequence, so a list doesn't connote the right thing. The position is important – we call them “positional arguments” – so a tuple makes sense.

On 12/15/2011 9:23 PM, Ben Finney wrote:
Ned Batchelderned@nedbatchelder.com writes:
Then we define a function foo(*args), and Python gives us a tuple! :-(
Yes, exactly: the positional arguments to the function are *not* a homogeneous sequence, so a list doesn't connote the right thing. The position is important – we call them “positional arguments” – so a tuple makes sense.
In general the positional arguments to a function are not homogenous, but the foo(*args) syntax is for precisely when you don't know how many args you will get, and you will of necessity treat them homogenously, no? We've been through this in another branch of this thread...
--Ned.

On 12/15/2011 8:39 PM, Guido van Rossum wrote:
On Thu, Dec 15, 2011 at 5:16 PM, Terry Reedy <tjreedy@udel.edu mailto:tjreedy@udel.edu> wrote:
On 12/15/2011 3:42 PM, Ned Batchelder wrote: This is another place where Python is inconsistent. We're told, "lists are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct." I have not been told that for several years, and I am pretty sure you will not find any such thing in the current docs. I consider it pretty much obsolete, as the differences that flowed from that idea are gone. In Python 3, tuples have all the non-mutating sequence methods that list does. The situation was much different in 1.4.
I strongly disagree. Being immutable sequences (i.e. homogeneous) is a minor secondary role for tuples. Their primary role remains to hold a small bunch of heterogeneous values -- like namedtuple, but without needing forethought. A good example are dictionary items -- these are (key, value) pairs where for a given dict, the keys are all of the same type (or of a small set of related types) and ditto for the values, but the key type and the value types are unrelated.
Could you explain why the foo(*args) syntax creates args as a tuple rather than a list?
--Ned.
-- --Guido van Rossum (python.org/~guido http://python.org/%7Eguido)
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas

Ned Batchelder writes:
On 12/15/2011 4:06 PM, Masklinn wrote:
On 2011-12-15, at 22:00 , Ned Batchelder wrote:
What? The whole point of the "def foo(*args)" syntax is so a function can take an unknown-length list of arguments, which will be treated uniformly.
That's not *entirely* true, it's also used for proxy functions, in order to just pass a bunch of arguments (positional or keywords) along to the next level without having to unpack and repack them one by one.
We are in agreement: I don't know the length, and I will treat them uniformly (pass them all through to the next function).
That's way too high a level of abstraction.
The discussion I found persuasive is the varargs analogy.
/* "Hello Ned" in C */ #include <stdio.h> char *s = "string"; int i = 1; double x = 3.1415926;
int main (int argc, char *argv[]) { printf ("string %s, int %d, double %f\nHello Ned! Are we homogenized yet?"); exit(-1); }
Of course it still demands "correct perception" on the part of the user, but I don't see this as "inconsistent".

On Thu, Dec 15, 2011 at 6:56 PM, Ned Batchelder ned@nedbatchelder.comwrote:
On 12/15/2011 8:39 PM, Guido van Rossum wrote:
On Thu, Dec 15, 2011 at 5:16 PM, Terry Reedy tjreedy@udel.edu wrote:
On 12/15/2011 3:42 PM, Ned Batchelder wrote:
This is another place where Python is inconsistent. We're told, "lists
are for homogenous sequences of varying length, like a C array; tuples are for heterogenous aggregations of known length, like a C struct."
I have not been told that for several years, and I am pretty sure you will not find any such thing in the current docs. I consider it pretty much obsolete, as the differences that flowed from that idea are gone. In Python 3, tuples have all the non-mutating sequence methods that list does. The situation was much different in 1.4.
I strongly disagree. Being immutable sequences (i.e. homogeneous) is a minor secondary role for tuples. Their primary role remains to hold a small bunch of heterogeneous values -- like namedtuple, but without needing forethought. A good example are dictionary items -- these are (key, value) pairs where for a given dict, the keys are all of the same type (or of a small set of related types) and ditto for the values, but the key type and the value types are unrelated.
Could you explain why the foo(*args) syntax creates args as a tuple rather than a list?
It's a historical accident. In ABC, functions didn't have multiple arguments -- instead there was a single argument that was a tuple. If you go back way, way in Python's early history (before 1.0 I think) you'll find that arguments kind of worked the same way. Then an idiom developed to accept a variable number of arguments, in order to support default argument values (because Python's tuples, unlike ABC's, were sequences even then). This turned out to be awkward if you wanted 1 or 2 arguments (the 1-arg case had to be dealt with special because you'd receive a plain value instead of a tuple) and then the *args and arg=value syntax was invented. But because previously varargs had been tuples, we kept them tuples. (I think this was also in the days that you couldn't unpack a list -- but I'm not actually sure of that. I do know that tuple unpacking was always in the language.)

On Thu, 15 Dec 2011 16:54:16 -0500 Ned Batchelder ned@nedbatchelder.com wrote:
On 12/15/2011 4:24 PM, Ethan Furman wrote:
Python is a dynamic language -- why can't it have dynamic structs?
I honestly don't know how to interpret this.
I do. Ethan is incorrectly trying to turn a similarity - that both Python tuples and C structs are collections of heterogeneous data - into an analogy. They aren't analogs, as that's pretty much the only similarity.
If you really want a dynamic tuple - a dynamic collection of heterogeneous data accessed by an index - use a list. That was standard practice in LISP-like languages for a long time. But Python (and most modern LISPs) has better tools for such.
For instance, the proper analog for a C struct in Python is the class. You provide definitions of them both along with names for their heterogeneous components. You create instances of them both that can assign different values to those components. The components are properly accessed by name. There are probably other similarities.
There are a number of differences as well. Most notably, instances of classes are dynamic. You can change, and even add and delete, components dynamically.
So Python already has a dynamic struct. You just have to get the analogies right.
<mike

Ned Batchelder wrote:
Finish that foo function definition: it will *have* to have "for a in args:"
Not necessarily. It might be something like this:
def foo(*args): if len(args) == 2: x, y = args fizzle2d(x, y) else: x, y, z = args fizzle3d(x, y, z)
In other words, it might be what is effectively an overloaded function with a number of different possible calling signatures. Once you've decided which signature is being invoked, each member of the argument tuple takes on a specific role.

Guido van Rossum wrote:
(I think this was also in the days that you couldn't unpack a list -- but I'm not actually sure of that. I do know that tuple unpacking was always in the language.)
I can definitely remember a time when you had to use (...) to unpack tuples and [...] to unpack lists. My Python memory doesn't go back further than that, though.

Greg Ewing wrote:
Ned Batchelder wrote:
Finish that foo function definition: it will *have* to have "for a in args:"
Not necessarily. It might be something like this:
def foo(*args): if len(args) == 2: x, y = args fizzle2d(x, y) else: x, y, z = args fizzle3d(x, y, z)
In other words, it might be what is effectively an overloaded function with a number of different possible calling signatures. Once you've decided which signature is being invoked, each member of the argument tuple takes on a specific role.
Thanks, Greg -- this is pretty much what I meant about dynamic structs. :)
~Ethan~

On 2011-12-16, at 06:50 , Greg Ewing wrote:
Ned Batchelder wrote:
Finish that foo function definition: it will *have* to have "for a in args:"
Not necessarily. It might be something like this:
def foo(*args): if len(args) == 2: x, y = args fizzle2d(x, y) else: x, y, z = args fizzle3d(x, y, z)
In other words, it might be what is effectively an overloaded function with a number of different possible calling signatures. Once you've decided which signature is being invoked, each member of the argument tuple takes on a specific role.
Is that really used instead of optional arguments with a placeholder value e.g.
def foo(x, y, z=None): if z is None: fizzle2d(x, y) else: fizzle3d(x, y, z)
?

Ned Batchelder ned@nedbatchelder.com writes:
On 12/15/2011 9:23 PM, Ben Finney wrote:
the positional arguments to the function are *not* a homogeneous sequence, so a list doesn't connote the right thing. The position is important – we call them “positional arguments” – so a tuple makes sense.
In general the positional arguments to a function are not homogenous, but the foo(*args) syntax is for precisely when you don't know how many args you will get
It's valuable for the case where the arguments need to be passed verbatim to a superclass. At some point, something in the call chain should know the meaning of each positional argument.
and you will of necessity treat them homogenously, no?
If the function consumes the positional arguments as a homogeneous list, I'd say that's a poor design (the function should instead specify a single argument which is a homogeneous sequence).
participants (18)
-
Antoine Pitrou
-
Ben Finney
-
Ethan Furman
-
Georg Brandl
-
Greg Ewing
-
Guido van Rossum
-
Masklinn
-
Matt Joiner
-
Michael Foord
-
Mike Meyer
-
Ned Batchelder
-
Nick Coghlan
-
Oleg Broytman
-
Richard Prosser
-
Stephen J. Turnbull
-
Terry Reedy
-
Tim Delaney
-
Yuval Greenfield