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
_______________________________________________
Python-ideas mailing list
Python...@python.org
http://mail.python.org/mailman/listinfo/python-ideas
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?
_______________________________________________
Python-ideas mailing list
Python...@python.org
http://mail.python.org/mailman/listinfo/python-ideas
http://www.voidspace.org.uk/
May you do good and not evil
May you find forgiveness for yourself and forgive others
May you share freely, never taking more than you give.
-- the sqlite blessing http://www.sqlite.org/different.html
On Sat, 10 Dec 2011 14:16:21 +0000
Richard Prosser <richard...@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.
--Ned.
Message: 2
Date: Sat, 10 Dec 2011 13:34:15 -0500
From: Ned Batchelder <n...@nedbatchelder.com>
To: Richard Prosser <richard...@mail.com>
Cc: python...@python.org
Subject: Re: [Python-ideas] Python Isn't Perfect: adding a 'gotchas'
section to the tutorial
Message-ID: <4EE3A627...@nedbatchelder.com>
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
> Although that way may not be obvious at first unless you're Dutch.
> It seems to me
> that the essential problem is that of assignment in general, which (I
> believe) creates a reference on the LHS to the object on the RHS, rather
> than having a copy operation to make the two objects completely separate.
I don't see this as a problem, it's the normal semantics of languages using
reference-values for object types.
> A similar comment applies to the lack of type declarations.
>
> So if you or anyone else can explain exactly why such odditties are
> implemented I would be grateful.
I don't understand, why do you consider dynamic typing to be "an
oddity"? There is nothing odd about it, and it's in fact older than
computer science itself.
The default arguments issue is an unfortunate interaction of Python's
core reference-value semantics and default arguments being implemented
as attributes to the function object, evaluated when said function
object is created (the feature itself is as old as Python, according to
the logs it was added for the 1.0.2 release back in 1994, the changelog
seems to peg it to April 14, so your only chance for an explanation of
why it was implemented with these semantics is hoping Guido has a very,
very good memory. Numerous post-hoc rationalization exist, but only him
may hold the true reason).
> Unfortunately it is almost certainly too late to propose fixes (if
> appropriate) for such quirks in Python 3
Most of these have been core semantic attributes of the language for
almost two decades now, so even if you had proposed these changes
during the Python 3 design cycle I think it's very unlikely they'd
have passed: they don't just change Python, they create a very
different language with a syntax similar to Python's.
> However I still feel that there are some aspects of the language which
> are not in the true spirit of Python (i.e. 'intuitive').
While 'intuitive' may be part of the 'true spirit of Python', it is not
included in the Zen of Python, perhaps because it it so slippery and
person dependent. Python is more in tune with everyday life and
therefore with naive intuition than some other languages.
> The discussion about default mutable types is one of these. It seems to
> me that the essential problem is that of assignment in general, which (I
> believe) creates a reference on the LHS to the object on the RHS, rather
> than having a copy operation to make the two objects completely
> separate. That can be confusing in other contexts, not just with default
> parameters.
When an organization reassign the role 'president' to a new person, they
do not copy the person. Neither does Python. We use aliases (multiple
ways to refer to the same entity) all the time in real life.
Python is quite consistent. Expressions evaluate to objects, either
pre-existing or new. "Target = expression" binds the target to the
object resulting from evaluating the expression. "f(expression)" binds
the first parameter name of f to the expression object.
> If I am to write a 'gotchas' FAQ or whatever then I would like to
> understand the reasoning behind such design decisions but I can't find
> any 'deep' explanations at present - just several posts about people
> being puzzled! A similar comment applies to the lack of type declarations.
Both behaviors reflect the fact that Python is a named object language,
rather than a named memory block language, and that in Python types are
a property of objects rather than of names. This is similar to at least
some uses of names in everyday life. For instance, the name Buddy could
be bound by a particular person to a person, dog, other pet, story,
boat, or even a rifle.
--
Terry Jan Reedy
> Richard, I don't think I can provide you with a "why" for dynamic
> typing.
And this is the wrong place to ask. Dynamic typing and naming objects
dates back to the precursors to LISP in the mid 50s. You should be
asking the people who made that decision.
By the same token, have you asked anyone why C/Java/etc. have static
typing and name locations? It's an equally valid question.
<mike
--
Mike Meyer <m...@mired.org> http://www.mired.org/
Independent Software developer/SCM consultant, email for more information.
O< ascii ribbon campaign - stop html mail - www.asciiribbon.org
Python's assignment semantics are only an "oddity" to people
whose prior exposure to programming languages is very limited.
To anyone familiar with almost any other dynamic language --
such as Lisp, Scheme, Smalltalk, or Javascript -- it's not
only unsurprising, it's the *obvious* thing to do. So I wouldn't
class it as a "gotcha" in the same sense as truly Python-specific
features like default argument evaluation and list comprehension
variable scope.
As for rationale, it comes down to something like this: Copying
large chunks of data is expensive, so it makes sense to do it
only when you really need to. And experience shows that most of
the time you *don't* need to copy things.
Furthermore, copying some kinds of things automatically and not
others (as some other languages such as VB and Java do) makes the
rules needlessly complicated and difficult to remember.
So Python does the simplest possible thing and doesn't copy
anything by default. If you want a copy, you need to do something
explicit to make it happen.
> Unfortunately it is almost certainly too late to propose fixes
> (if appropriate) for such quirks in Python 3
Python's assignment behaviour is most definitely *not* something
that needs "fixing"!
--
Greg
List was designed for writing algorithms.
> By the same token, have you asked anyone why C/Java/etc. have static
> typing and name locations? It's an equally valid question.
C was designed for writing a computing machine operating system with
mutable sequential memory slots numbered from 0 to 2**n - 1.
In this respect, Python is much more like List than C, even though its
syntax is more like C.
--
Terry Jan Reedy
The only people I'd see confused by this are those with significant
C++ experience, where assignment of references does indeed go through
a copy of the object itself.
> As for rationale, it comes down to something like this: Copying
> large chunks of data is expensive, so it makes sense to do it
> only when you really need to. And experience shows that most of
> the time you *don't* need to copy things.
Of course technically that copy could very well be performed
"on write". This would significantly complexify the runtime as well.
>> By the same token, have you asked anyone why C/Java/etc. have static
>> typing and name locations? It's an equally valid question.
> C was designed for writing a computing machine operating system with mutable sequential memory slots numbered from 0 to 2**n - 1.
On the other hand, there are very few typed assemblies.
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.
--
Oleg Broytman http://phdru.name/ p...@phdru.name
Programmers don't die, they just GOSUB without RETURN.
On 2011-12-10, at 15:42 , Oleg Broytman wrote:It's not used by the runtime though (I think IDEA/PyCharm uses it, but
> You can use annotations. See PEP 3107. Twas implemented in Python 3.0.
I wouldn't bet on it), so it's mostly documentary at this point.
--
ಠ_ಠ
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.
>>> 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.
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
FWIW, Haskell does not have a literal singleton (the standard defines "unit" `()` and 2-tuple through 15-tuple)
I welcome Richard's help in explaining this issue to beginners.
--Ned.
--
ಠ_ಠ
ಠ_ಠ
--
ಠ_ಠ
> 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.
--
Terry Jan Reedy
Definitely.
>>> By the same token, have you asked anyone why C/Java/etc. have static
>>> typing and name locations? It's an equally valid question.
>> C was designed for writing a computing machine operating system with mutable sequential memory slots numbered from 0 to 2**n - 1.
> On the other hand, there are very few typed assemblies.
The question of typing is somewhat orthogonal to that of naming value
objects versus storage locations. There are strong stactic typed named
value languages like ML. They even require different operators for int
and float arithmetic, just like assembler.
Assemblers also type data versus address registers and may have signed
versus unsigned int operations. But most typing has to be done by the
programmer, just as C requires the programmer to do garbage collection.
--
Terry Jan Reedy
> They even require different operators for int and float arithmetic, just like assembler.
That's got nothing to do with the typing or binding disciplines though, it's an issue of ML's type system. Haskell does not have that issue and is arguably more statically typed than MLs.
> Assemblers also type data versus address registers and may have signed versus unsigned int operations. But most typing has to be done by the programmer
Which is the point I was trying to make, a point which kind-of goes against the justification used for C being statically typed, as assemblies are also designed for driving systems with mutable sequential memory slots.
> Which is the point I was trying to make, a point which kind-of goes
> against the justification used for C being statically typed, as
> assemblies are also designed for driving systems with mutable
> sequential memory slots.
C was designed to remove some, but just some, of the burden of managing
blocks of memory. One of the unfortunate ironies of C is that C
programmers tend to forget that they *are* working with contiguous
mutable blocks and not with isolated Python-like objects. Hence
buffer-run exploits by malware writers who *do* remember and study the
exact order of memory blocks in particular binaries.
--
Terry Jan Reedy
Yup. You can also find dynamically typed named locations languages,
like BCPL (though "untyped" might be more descriptive). It also has
different operators for int and float arithmetic, because the
locations don't have type information, so it has to come from the
operator.
<mike
> Of course technically that copy could very well be performed
> "on write". This would significantly complexify the runtime as well.
It could also lead to a lot of gratuitous inefficiency
in programs, as people got into the habit of modifying
anything passed to them in the knowledge that it
wouldn't do any harm, but without considering the cost
of all the implicit copying that it caused.
--
Greg
> 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
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.
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
--Ned.
Yep. To be consistent, we'd need an "immutable list" type...
another thing that Haskell has no need for :)
How is it inconsistent? Function signatures generally have a fixed (or
mostly fixed) number of heterogenous arguments.
Regards
Antoine.
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.
It's either that or a "rest of that crap" catchall used to forward arguments without caring much for what they are.
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: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):
"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 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.Tuples are for heterogeneous data, list are for homogeneous data. Tuples are *not* read-only lists.
~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.
Tim Delaney
--Ned.
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.
Python is a dynamic language -- why can't it have dynamic structs?
~Ethan~
--Ned.
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.
--
Nick Coghlan | ncog...@gmail.com | Brisbane, Australia
> 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.
--
Terry Jan Reedy
On 12/15/2011 3:42 PM, Ned Batchelder wrote: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.
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."
> 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.
--
\ “I am the product of millions of generations of individuals who |
`\ each fought against a hostile universe and won, and I aim to |
_o__) maintain the tradition.” —Paul Z. Myers, 2009-09-12 |
Ben Finney
On 12/15/2011 9:23 PM, Ben Finney wrote:
> Ned Batchelder<n...@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.
--
--Guido van Rossum (python.org/~guido)
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".
Could you explain why the foo(*args) syntax creates args as a tuple rather than a list?On 12/15/2011 8:39 PM, Guido van Rossum wrote:On Thu, Dec 15, 2011 at 5:16 PM, Terry Reedy <tjr...@udel.edu> wrote:
On 12/15/2011 3:42 PM, Ned Batchelder wrote: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.
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 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.
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
--
Mike Meyer <m...@mired.org> http://www.mired.org/
Independent Software developer/SCM consultant, email for more information.
O< ascii ribbon campaign - stop html mail - www.asciiribbon.org
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.
--
Greg
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
Thanks, Greg -- this is pretty much what I meant about dynamic structs. :)
~Ethan~
def foo(x, y, z=None):
if z is None:
fizzle2d(x, y)
else:
fizzle3d(x, y, z)
?
> 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).
--
\ “If you define cowardice as running away at the first sign of |
`\ danger, screaming and tripping and begging for mercy, then yes, |
_o__) Mr. Brave man, I guess I'm a coward.” —Jack Handey |
Ben Finney