By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
446,265 Members | 1,690 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 446,265 IT Pros & Developers. It's quick & easy.

Death to tuples!

P: n/a
It seems that the distinction between tuples and lists has slowly been
fading away. What we call "tuple unpacking" works fine with lists on
either side of the assignment, and iterators on the values side. IIRC,
"apply" used to require that the second argument be a tuple; it now
accepts sequences, and has been depreciated in favor of *args, which
accepts not only sequences but iterators.

Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?

If not, then it's not clear that tuples as a distinct data type still
serves a purpose in the language. In which case, I think it's
appropriate to consider doing away with tuples. Well, not really: just
changing their intended use, changing the name to note that, and
tweaking the implementation to conform to this.

The new intended use is as an immutable sequence type, not a
"lightweight C struct". The new name to denote this new use -
following in the footsteps of the set type - is "frozenlist". The
changes to the implementation would be adding any non-mutating methods
of list to tuple, which appears to mean "index" and "count".

Removing the tuple type is clearly a Py3K action. Adding frozenlist
could be done now. Whehter or not we could make tuple an alias for
frozenlist before Py3K is an interesting question.

<mike
--
Mike Meyer <mw*@mired.org> http://www.mired.org/home/mwm/
Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.
Nov 28 '05 #1
Share this Question
Share on Google+
66 Replies


P: n/a
Mike Meyer <mw*@mired.org> writes:
The new intended use is as an immutable sequence type, not a
"lightweight C struct". The new name to denote this new use -
following in the footsteps of the set type - is "frozenlist". The
changes to the implementation would be adding any non-mutating methods
of list to tuple, which appears to mean "index" and "count".


I like this. I also note that tuples predated classes. The
appropriate way to do a C struct these days is often as a class
instance with __slots__.
Nov 28 '05 #2

P: n/a
Mike Meyer wrote:
It seems that the distinction between tuples and lists has slowly been
fading away. What we call "tuple unpacking" works fine with lists on
either side of the assignment, and iterators on the values side. IIRC,
"apply" used to require that the second argument be a tuple; it now
accepts sequences, and has been depreciated in favor of *args, which
accepts not only sequences but iterators.

Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?


The % operator for strings. And in argument lists.

def __setitem__(self, (row, column), value):
...

Nov 28 '05 #3

P: n/a
Mike Meyer enlightened us with:
Is there any place in the language that still requires tuples
instead of sequences, except for use as dictionary keys?
Anything that's an immutable sequence of numbers. For instance, a pair
of coordinates. Or a value and a weight for that value.
If not, then it's not clear that tuples as a distinct data type
still serves a purpose in the language. In which case, I think it's
appropriate to consider doing away with tuples.
I really disagree. There are countless examples where adding or
removing elements from a list just wouldn't be right.
The new intended use is as an immutable sequence type, not a
"lightweight C struct".


It's the same, really. A lightweight list of elements, where each
element has its own meaning, is both an immutable sequence as well as
a lightweight C struct.

Sybren
--
The problem with the world is stupidity. Not saying there should be a
capital punishment for stupidity, but why don't we just take the
safety labels off of everything and let the problem solve itself?
Frank Zappa
Nov 28 '05 #4

P: n/a
Mike Meyer wrote:
It seems that the distinction between tuples and lists has slowly been
fading away. What we call "tuple unpacking" works fine with lists on
either side of the assignment, and iterators on the values side. IIRC,
"apply" used to require that the second argument be a tuple; it now
accepts sequences, and has been depreciated in favor of *args, which
accepts not only sequences but iterators.

Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?


Would it be possible to optimize your "frozenlist" so that the objects
would be created during compilation time and rather than only during
runtime? If not then tuples() have a distinct performance advantage in
code like the following where they are used as local constants:
def func(x): .... if x in (1, 3, 5, 7, 8):
.... print 'x is really odd'
.... import dis
dis.dis(func) .....
3 20 LOAD_FAST 0 (x)
23 LOAD_CONST 8 ((1, 3, 5, 7, 8))
26 COMPARE_OP 6 (in)
def func(x): .... if x in [1,3,5,7,8]:
.... print 'x is really odd'
.... dis.dis(func)

....
3 20 LOAD_FAST 0 (x)
23 LOAD_CONST 2 (1)
26 LOAD_CONST 3 (3)
29 LOAD_CONST 4 (5)
32 LOAD_CONST 5 (7)
35 LOAD_CONST 6 (8)
38 BUILD_LIST 5
41 COMPARE_OP 6 (in)
-Peter

Nov 28 '05 #5

P: n/a
Op 2005-11-28, Peter Hansen schreef <pe***@engcorp.com>:
Mike Meyer wrote:
It seems that the distinction between tuples and lists has slowly been
fading away. What we call "tuple unpacking" works fine with lists on
either side of the assignment, and iterators on the values side. IIRC,
"apply" used to require that the second argument be a tuple; it now
accepts sequences, and has been depreciated in favor of *args, which
accepts not only sequences but iterators.

Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?


Would it be possible to optimize your "frozenlist" so that the objects
would be created during compilation time and rather than only during
runtime? If not then tuples() have a distinct performance advantage in
code like the following where they are used as local constants:
def func(x): ... if x in (1, 3, 5, 7, 8):
... print 'x is really odd'
... import dis
dis.dis(func) ....
3 20 LOAD_FAST 0 (x)
23 LOAD_CONST 8 ((1, 3, 5, 7, 8))
26 COMPARE_OP 6 (in)
def func(x): ... if x in [1,3,5,7,8]:
... print 'x is really odd'
... dis.dis(func)

...
3 20 LOAD_FAST 0 (x)
23 LOAD_CONST 2 (1)
26 LOAD_CONST 3 (3)
29 LOAD_CONST 4 (5)
32 LOAD_CONST 5 (7)
35 LOAD_CONST 6 (8)
38 BUILD_LIST 5
41 COMPARE_OP 6 (in)


I'm probably missing something, but what would be the problem if this
list was created during compile time?

--
Antoon Pardon
Nov 28 '05 #6

P: n/a
Antoon Pardon wrote:
>>> def func(x):

... if x in [1,3,5,7,8]:
... print 'x is really odd'
...
>>> dis.dis(func)

...
3 20 LOAD_FAST 0 (x)
23 LOAD_CONST 2 (1)
26 LOAD_CONST 3 (3)
29 LOAD_CONST 4 (5)
32 LOAD_CONST 5 (7)
35 LOAD_CONST 6 (8)
38 BUILD_LIST 5
41 COMPARE_OP 6 (in)


I'm probably missing something, but what would be the problem if this
list was created during compile time?


Not much in this particular instance. 'x in aList' is implemented as
aList.__contains__(x), so there isn't any easy way to get hold of the
list[*] and keep a reference to it. On the other hand:

def func(x):
return x + [1, 3, 5, 7, 8]

we could pass in an object x with an add operator which gets hold of its
right hand operand and mutates it.

So the problem is that we can't just turn any list used as a constant into
a constant list, we need to be absolutely sure that the list is used only
in a few very restricted circumstances, and since there isn't actually any
benefit to using a list here rather than a tuple it hardly seems
worthwhile.

There might be some mileage in compiling the list as a constant and copying
it before use, but you would have to do a lot of timing tests to be sure.
[*] except through f.func_code.co_consts, but that doesn't count.
Nov 28 '05 #7

P: n/a
Dan Bishop wrote:
Mike Meyer wrote:
Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?


The % operator for strings. And in argument lists.

def __setitem__(self, (row, column), value):
...


Interesting that both of these two things[1][2] have recently been
suggested as candidates for removal in Python 3.0.

[1]http://www.python.org/dev/summary/2005-09-01_2005-09-15.html#string-formatting-in-python-3-0
[2]http://www.python.org/dev/summary/2005-09-16_2005-09-30.html#removing-nested-function-parameters

STeVe
Nov 28 '05 #8

P: n/a
In article <86************@bhuda.mired.org>, Mike Meyer <mw*@mired.org> wrote:

Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?

If not, then it's not clear that tuples as a distinct data type
still serves a purpose in the language. In which case, I think it's
appropriate to consider doing away with tuples. Well, not really:
just changing their intended use, changing the name to note that, and
tweaking the implementation to conform to this.


There's still the obstacle known as Guido. I suggest you write a PEP so
that whatever decision gets made, there's a document.
--
Aahz (aa**@pythoncraft.com) <*> http://www.pythoncraft.com/

"If you think it's expensive to hire a professional to do the job, wait
until you hire an amateur." --Red Adair
Nov 28 '05 #9

P: n/a
Steven Bethard <st************@gmail.com> writes:
Dan Bishop wrote:
Mike Meyer wrote:
Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?

The % operator for strings. And in argument lists.
def __setitem__(self, (row, column), value):
...

Interesting that both of these two things[1][2] have recently been
suggested as candidates for removal in Python 3.0.
[1]http://www.python.org/dev/summary/2005-09-01_2005-09-15.html#string-formatting-in-python-3-0
[2]http://www.python.org/dev/summary/2005-09-16_2005-09-30.html#removing-nested-function-parameters


#2 I actually mentioned in passing, as it's part of the general
concept of tuple unpacking. When names are bound, you can use a
"tuple" for an lvalue, and the sequence on the rhs will be "unpacked"
into the various names in the lvalue:

for key, value = mydict.iteritems(): ...
a, (b, c) = (1, 2), (3, 4)

I think of the parameters of a function as just another case of
this; any solution that works for the above two should work for
function paremeters as well.

<mike
--
Mike Meyer <mw*@mired.org> http://www.mired.org/home/mwm/
Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.
Nov 28 '05 #10

P: n/a
Peter Hansen <pe***@engcorp.com> writes:
Mike Meyer wrote:
It seems that the distinction between tuples and lists has slowly been
fading away. What we call "tuple unpacking" works fine with lists on
either side of the assignment, and iterators on the values side. IIRC,
"apply" used to require that the second argument be a tuple; it now
accepts sequences, and has been depreciated in favor of *args, which
accepts not only sequences but iterators.
Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?

Would it be possible to optimize your "frozenlist" so that the objects
would be created during compilation time and rather than only during
runtime? If not then tuples() have a distinct performance advantage
in code like the following where they are used as local constants:


No, because I want to change the definition.

Tuples have the problem that they are immutable, except when they're
not (or for proper values of immutable, your choice). They're
hashable, except when they're not. Or equivalently, they can be used
as dictionary keys - or set elements - except when they can't.

I want frozenlists to *not* be that way. A frozenlist should *always*
be valid as a dictionary key. This changes the semantics significantly
- and means that creating a frozenlist from a list could be very
expensive.

Doing this requires extending the "frozen" concept, adding two new
types, a new magic method, and maybe a new builtin function.

Right now, a "frozenset" is always valid as a dictionary key. The concept
extension is to provide facilities to freeze nearly everything.

New types:
frozenlist: a tuple with count/index methods, and the constraint
that all the elements are frozen.
frozendict: A dictionary without __setitem__, and with the constraint
that all values stored in it are frozen.

New magic method: __freeze__(self): returns a frozen version of self.

Possibe new builtin:
freeze(obj) - returns the frozen form of obj. For lists, it
returns the equivalent frozenlist; for a set, it returns the
equivalent frozenset. For dicts, it returns the equivalent
frozendict. For other built-ins, it returns the original object.
For classes written in Python, if the instance has __freeze__, it
returns the result of calling that. Otherwise, if the instance
has __hash__, or does not have __cmp__ and __eq__ (i.e. - if
the class is hashable as is), it returns the instance. If all
of that fails, it throws an exception. While not strictly
required, it appears to be handy, and it makes thinking about
the new types much less simler.

You can't always create a frozenlist (or frozendict) at compile time. I.e.:

a = []
foo(a)
fa = frozenlist([a])

You don't know at compile time what a is going to be. Since you can't
change the frozenlist after creation, you have to wait until you know
the value of a to create fa.

This version is *not* suitable as an alias for tuple. It might work as
a replacement for tuple in Py3K, but a number of issues have to be
worked out - most notable tuple packing. % on strings is easy - you
make it accept either a list or a frozenlist (or, if we're going to
change it, maybe an arbitary sequence). But tuple unpacking would need
some real work. Do we blow off the comma syntax for creating tuples,
and force peole to write real lists? Doesn't seem like a good idea to
me.

<mike
--
Mike Meyer <mw*@mired.org> http://www.mired.org/home/mwm/
Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.
Nov 28 '05 #11

P: n/a

Mike> Tuples have the problem that they are immutable, except when
Mike> they're not (or for proper values of immutable, your
Mike> choice). They're hashable, except when they're not. Or
Mike> equivalently, they can be used as dictionary keys - or set
Mike> elements - except when they can't.

For those of us not following this thread closely, can you identify cases
where tuples are mutable, not hashable or can't be used as dictionary keys?
I've never encountered any such cases.

Skip
Nov 28 '05 #12

P: n/a
sk**@pobox.com writes:
For those of us not following this thread closely, can you identify cases
where tuples are mutable, not hashable or can't be used as dictionary keys?
I've never encountered any such cases.


t = ([1,2], [3,4])
Nov 28 '05 #13

P: n/a
In article <7x************@ruckus.brouhaha.com>,
Paul Rubin <http://ph****@NOSPAM.invalid> wrote:
sk**@pobox.com writes:

For those of us not following this thread closely, can you identify
cases where tuples are mutable, not hashable or can't be used as
dictionary keys? I've never encountered any such cases.


t = ([1,2], [3,4])


Exactly. Technically, the tuple is still immutable, but because it
contains mutable objects, it is no longer hashable and cannot be used as
a dict key. One of the odder bits about this usage is that augmented
assignment breaks, but not completely:
t = ([1,2], [3,4])
t[0] += [5] Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment t

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

(I'm pretty sure Skip has seen this before, but I figure it's a good
reminder.)
--
Aahz (aa**@pythoncraft.com) <*> http://www.pythoncraft.com/

"If you think it's expensive to hire a professional to do the job, wait
until you hire an amateur." --Red Adair
Nov 28 '05 #14

P: n/a
I would consider
t = ([1,2], [3,4])
to be assigning a tuple with two list elements to t.
The inner lists will be mutable but I did not know you could change the
outer tuple and still have the same tuple object.

- Pad.

Nov 28 '05 #15

P: n/a
"Paddy" <pa*******@netscape.net> writes:
I would consider
t = ([1,2], [3,4])
to be assigning a tuple with two list elements to t.
The inner lists will be mutable but I did not know you could change the
outer tuple and still have the same tuple object.


Whether t is mutable is a question of definitions. Either way, you
can't hash t or use it as a dictionary key.
Nov 28 '05 #16

P: n/a
sk**@pobox.com writes:
Mike> Tuples have the problem that they are immutable, except when
Mike> they're not (or for proper values of immutable, your
Mike> choice). They're hashable, except when they're not. Or
Mike> equivalently, they can be used as dictionary keys - or set
Mike> elements - except when they can't.
For those of us not following this thread closely, can you identify cases
where tuples are mutable, not hashable or can't be used as dictionary keys?
I've never encountered any such cases.


Actually, that didn't come from this thread. But it happens if one of
the elements in the tuple is mutable. For instance:
t = [],
type(t) <type 'tuple'> t == [], True hash(t) Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: list objects are unhashable t[0].append(1)
t == [], False {t: 23} Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: list objects are unhashable


For builtins, the three cases - hashable, immutable and usable as
dictionary keys - are all identical. Instances of Python classes with
proper __hash__ and __eq__ defintions will be hashable and usable as
dictionary keys. Immutable for them is a messy question.

<mike
--
Mike Meyer <mw*@mired.org> http://www.mired.org/home/mwm/
Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.
Nov 28 '05 #17

P: n/a
For those of us not following this thread closely, can you identify
cases where tuples are mutable, not hashable or can't be used as
dictionary keys? I've never encountered any such cases.


t = ([1,2], [3,4])
...
t = ([1,2], [3,4])
t[0] += [5] aahz> Traceback (most recent call last):
aahz> File "<stdin>", line 1, in ?
aahz> TypeError: object doesn't support item assignment t

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

aahz> (I'm pretty sure Skip has seen this before, but I figure it's a
aahz> good reminder.)

Actually, no, I hadn't. I don't use tuples that way. It's rare when I have
a tuple whose elements are not all floats, strings or ints, and I never put
mutable containers in them.

Skip
Nov 28 '05 #18

P: n/a
Antoon Pardon wrote:
Op 2005-11-28, Peter Hansen schreef <pe***@engcorp.com>:
Mike Meyer wrote:
Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?
Would it be possible to optimize your "frozenlist" so that the objects
would be created during compilation time and rather than only during
runtime? If not then tuples() have a distinct performance advantage in
code like the following where they are used as local constants:

[snip code] I'm probably missing something, but what would be the problem if this
list was created during compile time?


?? I was *asking* if there would be a problem doing that, not saying
there would be a problem. I think you misread or misunderstood something.

-Peter

Nov 29 '05 #19

P: n/a
Mike Meyer wrote:
Steven Bethard <st************@gmail.com> writes:
Dan Bishop wrote:
Mike Meyer wrote:
Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?

The % operator for strings. And in argument lists.
def __setitem__(self, (row, column), value):
...


Interesting that both of these two things[1][2] have recently been
suggested as candidates for removal in Python 3.0.
[1]http://www.python.org/dev/summary/2005-09-01_2005-09-15.html#string-formatting-in-python-3-0
[2]http://www.python.org/dev/summary/2005-09-16_2005-09-30.html#removing-nested-function-parameters


#2 I actually mentioned in passing, as it's part of the general
concept of tuple unpacking. When names are bound, you can use a
"tuple" for an lvalue, and the sequence on the rhs will be "unpacked"
into the various names in the lvalue:

for key, value = mydict.iteritems(): ...
a, (b, c) = (1, 2), (3, 4)

I think of the parameters of a function as just another case of
this; any solution that works for the above two should work for
function paremeters as well.


The difference is that currently, you have to use tuple syntax in
functions, while you have your choice of syntaxes with normal unpacking::

py> def f(a, (b, c)):
.... pass
....
py> def f(a, [b, c]):
.... pass
....
Traceback ( File "<interactive input>", line 1
def f(a, [b, c]):
^
SyntaxError: invalid syntax
py> a, (b, c) = (1, 2), (3, 4)
py> a, [b, c] = (1, 2), (3, 4)
py> a, [b, c] = [1, 2], (3, 4)
py> a, [b, c] = [1, 2], [3, 4]

Of course, the result in either case is still a tuple. So I do agree
that Python doesn't actually require tuples in function definitions;
just their syntax.

STeVe
Nov 29 '05 #20

P: n/a
Mike Meyer <mw*@mired.org> wrote:
...
concept of tuple unpacking. When names are bound, you can use a
"tuple" for an lvalue, and the sequence on the rhs will be "unpacked"
into the various names in the lvalue:

for key, value = mydict.iteritems(): ...
a, (b, c) = (1, 2), (3, 4)

I think of the parameters of a function as just another case of
this; any solution that works for the above two should work for
function paremeters as well.


I agree that conceptually the situations are identical, it's just that
currently using [ ] on the left of an equal sign is OK, while using them
in a function's signature is a syntax error. No doubt that part of the
syntax could be modified (expanded), I imagine that nothing bad would
follow as a consequence.
Alex
Nov 29 '05 #21

P: n/a
<sk**@pobox.com> wrote:
...
Actually, no, I hadn't. I don't use tuples that way. It's rare when I have
a tuple whose elements are not all floats, strings or ints, and I never put
mutable containers in them.


You never have a dict whose values are lists? Or never call .items (or
..iteritems, or popitem, ...) on that dict? I happen to use dicts with
list values often, and sometimes use the mentioned methods on them...
and said methods will then return one or more tuples "with a mutable
container in them". I've also been known to pass lists as arguments to
functions (if the functions receives arguments with *args, there you are
again: args is a then tuple with mutable containers in it), use
statements such as:
return 1, 2, [x+1 for x in wah]
which also build such tuples, and so on, and so forth... tuples get
created pretty freely in many cases, after all.
Alex

Nov 29 '05 #22

P: n/a
Steven Bethard wrote:
[1]http://www.python.org/dev/summary/2005-09-01_2005-09-15.html#string-formatting-in-python-3-0


Reading this link, I find this:

"Currently, using % for string formatting has a number of inconvenient
consequences:

* precedence issues: "a%sa" % "b"*4 produces 'abaabaabaaba', not
'abbbba' " [...]
Testing it locally:
"a%sa" % "b"*4 'abaabaabaaba'

But "b"*4 is not a tuple, as the formatting arguments are supposed to be,
so why blame its erronious behaviour?

Works fine this way:
"a%sa" % ("b"*4,)

'abbbba'

So I really do not see the problem. For my own format-strings, I always
add a final comma to make sure it's a tuple I'm using.

Just my $0.02.
--
Sincerely, | http://bos.hack.org/cv/
Rikard Bosnjakovic | Code chef - will cook for food
------------------------------------------------------------------------
Nov 29 '05 #23

P: n/a

Rikard Bosnjakovic wrote:
Steven Bethard wrote:
[1]http://www.python.org/dev/summary/2005-09-01_2005-09-15.html#string-formatting-in-python-3-0


Reading this link, I find this:

"Currently, using % for string formatting has a number of inconvenient
consequences:

* precedence issues: "a%sa" % "b"*4 produces 'abaabaabaaba', not
'abbbba' " [...]
Testing it locally:
>>> "a%sa" % "b"*4 'abaabaabaaba'

But "b"*4 is not a tuple, as the formatting arguments are supposed to be,
so why blame its erronious behaviour?

Works fine this way:
>>> "a%sa" % ("b"*4,)

'abbbba'

So I really do not see the problem. For my own format-strings, I always
add a final comma to make sure it's a tuple I'm using.

Just my $0.02.

this doesn't look like a tuple issue, but precedence of the operator.

"a%sa" % ("b"*4)

also gives the expected answer. "abbbba"

Nov 29 '05 #24

P: n/a
On 2005-11-28, Duncan Booth <du**********@invalid.invalid> wrote:
Antoon Pardon wrote:
>>> def func(x):
... if x in [1,3,5,7,8]:
... print 'x is really odd'
...
>>> dis.dis(func)
...
3 20 LOAD_FAST 0 (x)
23 LOAD_CONST 2 (1)
26 LOAD_CONST 3 (3)
29 LOAD_CONST 4 (5)
32 LOAD_CONST 5 (7)
35 LOAD_CONST 6 (8)
38 BUILD_LIST 5
41 COMPARE_OP 6 (in)


I'm probably missing something, but what would be the problem if this
list was created during compile time?


Not much in this particular instance. 'x in aList' is implemented as
aList.__contains__(x), so there isn't any easy way to get hold of the
list[*] and keep a reference to it. On the other hand:

def func(x):
return x + [1, 3, 5, 7, 8]

we could pass in an object x with an add operator which gets hold of its
right hand operand and mutates it.

So the problem is that we can't just turn any list used as a constant into
a constant list, we need to be absolutely sure that the list is used only
in a few very restricted circumstances, and since there isn't actually any
benefit to using a list here rather than a tuple it hardly seems
worthwhile.


The question is, should we consider this a problem. Personnaly, I
see this as not very different from functions with a list as a default
argument. In that case we often have a list used as a constant too.

Yet python doesn't has a problem with mutating this list so that on
the next call a 'different' list is the default. So if mutating
a list used as a constant is not a problem there, why should it
be a problem in your example?

--
Antoon Pardon
Nov 29 '05 #25

P: n/a
On 27 Nov 2005 23:33:27 -0800, "Dan Bishop" <da*****@yahoo.com> wrote:
Mike Meyer wrote:
It seems that the distinction between tuples and lists has slowly been
fading away. What we call "tuple unpacking" works fine with lists on
either side of the assignment, and iterators on the values side. IIRC,
"apply" used to require that the second argument be a tuple; it now
accepts sequences, and has been depreciated in favor of *args, which
accepts not only sequences but iterators.

Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?


The % operator for strings. And in argument lists.

def __setitem__(self, (row, column), value):
...

Seems like str.__mod__ could take an arbitary (BTW, matching length, necessarily?
Or just long enough?) iterable in place of a tuple, just like it can take
an arbitrary mapping object in place of a dict for e.g. '%(name)s'% {'name':'<name value>'}

Regards,
Bengt Richter
Nov 29 '05 #26

P: n/a
Antoon Pardon wrote:
The question is, should we consider this a problem. Personnaly, I
see this as not very different from functions with a list as a default
argument. In that case we often have a list used as a constant too.

Yet python doesn't has a problem with mutating this list so that on
the next call a 'different' list is the default. So if mutating
a list used as a constant is not a problem there, why should it
be a problem in your example?

Are you serious about that?

The semantics of default arguments are quite clearly defined (although
suprising to some people): the default argument is evaluated once when the
function is defined and the same value is then reused on each call.

The semantics of list constants are also clearly defined: a new list is
created each time the statement is executed. Consider:

res = []
for i in range(10):
res.append(i*i)

If the same list was reused each time this code was executed the list would
get very long. Pre-evaluating a constant list and creating a copy each time
wouldn't break the semantics, but simply reusing it would be disastrous.
Nov 29 '05 #27

P: n/a
Having a general frozen() system makes a lot of sense. People use
tuples for two different things: as a lightweight record type (e.g.,
(x, y) coordinate pairs), and as an immutable list. The latter is not
officially sanctioned but is widely believed to be the purpose for
tuples. And the value of an immutable list is obvious: you can use it
as a constant or pass it to a function and know it won't be abused. So
why not an immutable dictionary too? There are many times I would have
preferred this if it were available. frozen() is appealing because it
provides a general solution for all container types.

I'm not sure tuple should be eliminated, however. It still has value
to show that these things are a group. And it's very lightweight to
construct.

-- Mike Orr <sl********@gmail.com>

Nov 29 '05 #28

P: n/a
Paddy wrote:
I would consider
t = ([1,2], [3,4])
to be assigning a tuple with two list elements to t.
The inner lists will be mutable but I did not know you could change the
outer tuple and still have the same tuple object.


you can't.

but since hash(tuple) is defined in terms of map(hash, t), the resulting
tuple is not hashable.

also see:

http://effbot.org/zone/python-hash.htm#tuples

</F>

Nov 29 '05 #29

P: n/a
On 28 Nov 2005 14:48:35 GMT, Duncan Booth <du**********@invalid.invalid> wrote:
Antoon Pardon wrote:
>>> def func(x):
... if x in [1,3,5,7,8]:
... print 'x is really odd'
...
>>> dis.dis(func)
...
3 20 LOAD_FAST 0 (x)
23 LOAD_CONST 2 (1)
26 LOAD_CONST 3 (3)
29 LOAD_CONST 4 (5)
32 LOAD_CONST 5 (7)
35 LOAD_CONST 6 (8)
38 BUILD_LIST 5
41 COMPARE_OP 6 (in)


I'm probably missing something, but what would be the problem if this
list was created during compile time?


Not much in this particular instance. 'x in aList' is implemented as
aList.__contains__(x), so there isn't any easy way to get hold of the
list[*] and keep a reference to it. On the other hand:

def func(x):
return x + [1, 3, 5, 7, 8]

we could pass in an object x with an add operator which gets hold of its
right hand operand and mutates it.

So the problem is that we can't just turn any list used as a constant into
a constant list, we need to be absolutely sure that the list is used only
in a few very restricted circumstances, and since there isn't actually any
benefit to using a list here rather than a tuple it hardly seems
worthwhile.

There might be some mileage in compiling the list as a constant and copying
it before use, but you would have to do a lot of timing tests to be sure.

[*] except through f.func_code.co_consts, but that doesn't count.


If we had a way to effect an override of a specific instance's attribute accesses
to make certain attribute names act as if they were defined in type(instance), and
if we could do this with function instances, and if function local accesses would
check if names were one of the ones specified for the function instance being called,
then we could define locally named constants etc like properties.

The general mechanism would be that instance.__classvars__ if present would make
instance.x act like instance.__classvars__['x'].__get__(instance). IOW as if descriptor
access for descriptors defined in type(instance). Thus if you wrote
instance.__classvars__ = dict(t=property(lambda self, t=__import__('time').ctime:t()))

then that would make instance.t act as if you had assigned the property to type(instance).t
-- which is handy for types whose instances don't let you assign to type(instance).

This gets around to instances of the function type. Let's say we had a decorator
defined like

def deco(f):
f.__classvars__ = dict(now= property(lambda f, t=__import__('time').ctime:t()))
return f

then if function local name access acted like self.name access where self was the function
instance[1], and type(self) was checked for descriptors, meaning in this case foo.__classvars__
would get checked like type(self).__dict__, then when you wrote

@deco
def foo():
print now # => print foo.__classvars__['now'].__get__(foo, type(foo))
# (i.e., as if property defined as type(self) attribute where self is foo
# but not accessed via global name as in above illustrative expression)
now = 42 # => AttributeError: can't set attribute

This would also let you add properties to other unmodifiable types like the module type you see
via type(__import__('anymod')), e.g.,

import foomod
foomod.__classvars__ = dict(ver=property(lambda mod: '(Version specially formatted): 'r'%mod.version)
# (or foomod could define its own __classvars__)
then
foomod.ver
would act like a property defined in the __classvars__ - extended class variable namespace, and return
what a normal property would. Plain functions in foomod.__classvars__ would return bound methods with
foomod in "self" position, so you could call graphics.draw(args) and know that draw if so designed
could be defined as def draw(mod, *args): ...

Just another idea ;-)
[1] actually, during a function call, the frame instance if probably more like the "self", but let's
say the name resolution order for local access extends to foo.__classvars__ somethow as if that were
an overriding front end base class dict of the mro chain.

Regards,
Bengt Richter
Nov 29 '05 #30

P: n/a
On Tue, Nov 29, 2005 at 10:41:13AM +0000, Bengt Richter wrote:
Seems like str.__mod__ could take an arbitary (BTW, matching length, necessarily?
Or just long enough?) iterable in place of a tuple, just like it can take
an arbitrary mapping object in place of a dict for e.g. '%(name)s'% {'name':'<name value>'}


What, and break reams of perfectly working code?

s = set([1, 2, 3])
t = [4, 5, 6]
u = "qwerty"
v = iter([None])
print "The set is: %s" % s
print "The list is: %s" % t
print "The string is: %s" % u
print "The iterable is: %s" % v

Jeff

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)

iD8DBQFDjHM9Jd01MZaTXX0RAtvWAKCCv7H2SJmoJ7OpgnEhf7/dJ4X3EgCgnuLD
qzTIMVpmKZKEAud74FV6sjs=
=KMMK
-----END PGP SIGNATURE-----

Nov 29 '05 #31

P: n/a
sl********@gmail.com wrote:
Having a general frozen() system makes a lot of sense. People use
tuples for two different things: as a lightweight record type (e.g.,
(x, y) coordinate pairs), and as an immutable list. The latter is not
officially sanctioned but is widely believed to be the purpose for
tuples. And the value of an immutable list is obvious: you can use it
as a constant or pass it to a function and know it won't be abused. So
why not an immutable dictionary too? There are many times I would have
preferred this if it were available. frozen() is appealing because it
provides a general solution for all container types.


http://www.python.org/peps/pep-0351.html
http://www.python.org/dev/summary/20...reeze-protocol

</F>

Nov 29 '05 #32

P: n/a
Dan Bishop wrote:
Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?
The % operator for strings. And in argument lists.

def __setitem__(self, (row, column), value):
...

Don't forget the exception specification in a try..catch statement:
An object is compatible with an exception if it is either the object
that identifies the exception, or (for exceptions that are classes) it
is a base class of the exception, or it is a tuple containing an item
that is compatible with the exception.


Requiring a tuple here means that the code doesn't have to worry about a
possible recursive data structure.

Nov 29 '05 #33

P: n/a
Bengt Richter wrote:
If we had a way to effect an override of a specific instance's attribute accesses
to make certain attribute names act as if they were defined in type(instance), and
if we could do this with function instances, and if function local accesses would
check if names were one of the ones specified for the function instance being called,
then we could define locally named constants etc like properties.

The general mechanism would be that instance.__classvars__ if present would make


Nah... you're not nearly going far enough with this. I'd suggest a full
unification of "names" and "attributes." This would also enhance
lexical scoping and allow an "outer" keyword to set values in an outer
namespace without doing royally weird stuff.

In general, all lexical blocks which currently have a local namespace
(right now, modules and functions) would have a __namespace__ variable,
containing the current namespace object. Operations to get/set/delete
names would be exactly translated to getattr/setattr/delattrs. Getattrs
on a namespace that does not contain the relevant name recurse up the
chain of nested namespaces, to the global (module) namespace, which will
raise an AttributeError if not found.

This allows exact replication of current behaviour, with a couple
interesting twists:
1) i = i+1 with "i" in only an outer scope acutally works now; it uses
the outer scope "i" and creates a local "i" binding.
2) global variables are easily defined by a descriptor:
def global_var(name):
return property(
lambda self: getattr(self.global,name),
lambda (self, v): setattr(self.global,name,v),
lambda self: delattr(self.global,name),
"Global variable %s" % name)
3) "outer variables" under write access (outer x, x = 1) are also
well-defined by descriptor (exercise left for reader). No more weird
machinations involving a list in order to build an accumulator function,
for example. Indeed, this is probably the primary benefit.
4) Generally, descriptor-based names become possible, allowing some
rather interesting features[*]:
i) "True" constants, which cannot be rebound (mutable objects aside)
ii) Aliases, such that 'a' and 'b' actually reference the same bit,
so a = 1 -> b == 1
iii) "Deep references", such that 'a' could be a reference to my_list[4].
iv) Dynamic variables, such as a "now_time" that implicitly expands
to some function.
5) With redefinition of the __namespace__ object, interesting run-time
manipulations become possible, such as redefining a variable used by a
function to be local/global/outer. Very dangerous, of course, but
potentially very flexible. One case that comes to mind is a "profiling"
namespace, which tracks how often variables are accessed --
over-frequented variables might lead to better-optimized code, and
unaccessed variables might indicate dead code.
[*] -- I'm not saying that any of these examples are particularly good
ideas; indeed, abuse of them would be incredibly ugly. It's just that
these are the first things that come to mind, because they're also so
related to the obvious use-cases of properties.

The first reaction to this is going to be a definite "ewwwww," and I'd
agree; this would make Python names be non-absolute [then again, the
__classvars__ suggestion goes nearly as far anyway]. But this
unification does bring all the power of "instance.attribute" down to the
level of "local_name".

The single biggest practical benefit is an easy definiton of an "outer"
keyword: lexical closures in Python would then become truly on-par with
use of global variables. The accumulator example would become:
def make_accum(init):
i = init
def adder(j):
outer i #[1]
i += j
return i
return adder

[1] -- note, this 'outer' check will have to require that 'i' be defined
in an outer namespace -at the time the definition is compiled-.
Otherwise, the variable might have to be created at runtime (as can be
done now with 'global'), but there's no obvious choice on which
namespace to create it in: global, or the immediately-outer one? This
implies the following peculiar behaviour (but I think it's for the best):
# no i exists
def f(): # will error on definition outer i
print i def g(): # won't error print i i = 1
f()
g()


Definitely a Py3K proposal, though.
Nov 29 '05 #34

P: n/a
Actually, no, I hadn't. I don't use tuples that way. It's rare when
I have a tuple whose elements are not all floats, strings or ints,
and I never put mutable containers in them.


Alex> You never have a dict whose values are lists?

Sorry, incomplete explanation. I never create tuples which contain mutable
containers, so I never have the "can't use 'em as dict keys" and related
problems. My approach to use of tuples pretty much matches Guido's intent I
think: small, immutable, record-like things.

immutable-all-the-way-down-ly, y'rs,

Skip
Nov 29 '05 #35

P: n/a
On Tue, 29 Nov 2005 09:26:53 -0600, je****@unpythonic.net wrote:

--cvVnyQ+4j833TQvp
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Tue, Nov 29, 2005 at 10:41:13AM +0000, Bengt Richter wrote:
Seems like str.__mod__ could take an arbitary (BTW, matching length, necessarily?
Or just long enough?) iterable in place of a tuple, just like it can take
an arbitrary mapping object in place of a dict for e.g. '%(name)s'% {'name':'<name value>'}
What, and break reams of perfectly working code?

There you go again ;-) There I went again, d'oh ;-/
s = set([1, 2, 3])
t = [4, 5, 6]
u = "qwerty"
v = iter([None])
print "The set is: %s" % s
print "The list is: %s" % t
print "The string is: %s" % u
print "The iterable is: %s" % v


I guess I could argue for an iterable with a next method in single-arg form
just having its next method called to get successive arguments. If you
wanted the above effect for v, you would have to do the same as you now do to
print a single tuple argument, i.e., you wrap it in a 1-tuple like (v,)

This might even let you define an object that has both __getitem__ and next
methods for mixing formats like '%(first)s %s %(third)s' % map_n_next_object

reminder...
tup = (1, 2, 3)
print "The tuple is: %s" % tup Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: not all arguments converted during string formatting print "The tuple is: %s" % tup, Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: not all arguments converted during string formatting print "The tuple is: %s" % (tup,)

The tuple is: (1, 2, 3)

But, yeah, it is handy to pass a single non-tuple arg.

But iterables as iterators have possibilities too, e.g. maybe
it = iter(seq)
print 'From %s: %s' % (it,)
print 'Unpacked: %s %s' % it # as if (it.next(). it.next())
print 'Next two: %s %s' % it # ditto

You will get StopIteration if you run out of args though, so maybe str.__mod__
should catch that and convert it to its "TypeError: not enough arguments for format string"
when/if it finally happens.

Regards,
Bengt Richter
Nov 29 '05 #36

P: n/a
Bengt Richter wrote:
If we had a way to effect an override of a specific instance's attribute accesses to make certain attribute names act as if they were defined in type(instance), and if we could do this with function instances, and if function local accesses would check if names were one of the ones specified for the function instance being called, then we could define locally named constants etc like properties.

The general mechanism would be that instance.__classvars__ if present would make Nah... you're not nearly going far enough with this. I'd suggest a
full
unification of "names" and "attributes." This would also enhance
lexical scoping and allow an "outer" keyword to set values in an outer

namespace without doing royally weird stuff.

In general, all lexical blocks which currently have a local namespace
(right now, modules and functions) would have a __namespace__
variable,
containing the current namespace object. Operations to get/set/delete

names would be exactly translated to getattr/setattr/delattrs.
Getattrs
on a namespace that does not contain the relevant name recurse up the
chain of nested namespaces, to the global (module) namespace, which
will
raise an AttributeError if not found.

This allows exact replication of current behaviour, with a couple
interesting twists:
1) i = i+1 with "i" in only an outer scope acutally works now; it uses

the outer scope "i" and creates a local "i" binding.
2) global variables are easily defined by a descriptor:
def global_var(name):
return property(
lambda self: getattr(self.global,name),
lambda (self, v): setattr(self.global,name,v),
lambda self: delattr(self.global,name),
"Global variable %s" % name)
3) "outer variables" under write access (outer x, x = 1) are also
well-defined by descriptor (exercise left for reader). No more weird
machinations involving a list in order to build an accumulator
function,
for example. Indeed, this is probably the primary benefit.
4) Generally, descriptor-based names become possible, allowing some
rather interesting features[*]:
i) "True" constants, which cannot be rebound (mutable objects
aside)
ii) Aliases, such that 'a' and 'b' actually reference the same bit,

so a = 1 -> b == 1
iii) "Deep references", such that 'a' could be a reference to
my_list[4].
iv) Dynamic variables, such as a "now_time" that implicitly expands

to some function.
5) With redefinition of the __namespace__ object, interesting run-time

manipulations become possible, such as redefining a variable used by a

function to be local/global/outer. Very dangerous, of course, but
potentially very flexible. One case that comes to mind is a
"profiling"
namespace, which tracks how often variables are accessed --
over-frequented variables might lead to better-optimized code, and
unaccessed variables might indicate dead code.
[*] -- I'm not saying that any of these examples are particularly good

ideas; indeed, abuse of them would be incredibly ugly. It's just that

these are the first things that come to mind, because they're also so
related to the obvious use-cases of properties.

The first reaction to this is going to be a definite "ewwwww," and I'd

agree; this would make Python names be non-absolute [then again, the
__classvars__ suggestion goes nearly as far anyway]. But this
unification does bring all the power of "instance.attribute" down to
the
level of "local_name".

The single biggest practical benefit is an easy definiton of an
"outer"
keyword: lexical closures in Python would then become truly on-par
with
use of global variables. The accumulator example would become:
def make_accum(init):
i = init
def adder(j):
outer i #[1]
i += j
return i
return adder

[1] -- note, this 'outer' check will have to require that 'i' be
defined
in an outer namespace -at the time the definition is compiled-.
Otherwise, the variable might have to be created at runtime (as can be

done now with 'global'), but there's no obvious choice on which
namespace to create it in: global, or the immediately-outer one? This

implies the following peculiar behaviour (but I think it's for the
best):
# no i exists
def f(): # will error on definition
outer i print i def g(): # won't error
print i
i = 1
f()
g()

Definitely a Py3K proposal, though.

Nov 29 '05 #37

P: n/a
Duncan Booth <du**********@invalid.invalid> writes:
Dan Bishop wrote:
Is there any place in the language that still requires tuples instead
of sequences, except for use as dictionary keys?


The % operator for strings. And in argument lists.

def __setitem__(self, (row, column), value):
...

Don't forget the exception specification in a try..catch statement:
An object is compatible with an exception if it is either the object
that identifies the exception, or (for exceptions that are classes) it
is a base class of the exception, or it is a tuple containing an item
that is compatible with the exception.


Requiring a tuple here means that the code doesn't have to worry about a
possible recursive data structure.


How so?

except ExceptType1, (ExceptType2, ExceptType3, (ExceptType4, ExceptType5):

I suspect this won't work, but meets the description.

Lists could be used here with the same restrictions as tuples.

<mike
--
Mike Meyer <mw*@mired.org> http://www.mired.org/home/mwm/
Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.
Nov 29 '05 #38

P: n/a
> The new intended use is as an immutable sequence type, not a
"lightweight C struct". The new name to denote this new use -
following in the footsteps of the set type - is "frozenlist". The
changes to the implementation would be adding any non-mutating methods
of list to tuple, which appears to mean "index" and "count".


Yeah, I like this! "frozenlist" is nice, and we get a "real" immutable
sequence for a change.

--
Nicola Larosa - ni******@m-tekNico.net

She was up the learning curve like a mountain goat.
-- WasterDave on Slashdot, October 2005

Nov 30 '05 #39

P: n/a
Mike Meyer wrote:
An object is compatible with an exception if it is either the object
that identifies the exception, or (for exceptions that are classes)
it is a base class of the exception, or it is a tuple containing an
item that is compatible with the exception.


Requiring a tuple here means that the code doesn't have to worry
about a possible recursive data structure.


How so?

except ExceptType1, (ExceptType2, ExceptType3, (ExceptType4,
ExceptType5):

I suspect this won't work, but meets the description.

Lists could be used here with the same restrictions as tuples.


I think you meant:

except (ExceptType1, (ExceptType2, ExceptType3, (ExceptType4,
ExceptType5))):

otherwise the first comma separates the exception specification from the
target and you get a syntax error even before the syntax error you might
expect from the unmatched parentheses. And yes, that works fine.

e.g. You can do this:
ExcGroup1 = ReferenceError, RuntimeError
ExcGroup2 = MemoryError, SyntaxError, ExcGroup1
ExcGroup3 = TypeError, ExcGroup2
try: raise ReferenceError("Hello")
except ExcGroup3, e:
print "Caught",e
Caught Hello ExcGroup3 (<class exceptions.TypeError at 0x009645A0>, (<class exceptions.MemoryError
at 0x00977120>, <class exceptions.SyntaxError at 0x00964A50>, (<class
exceptions.ReferenceError at 0x009770C0>, <class exceptions.RuntimeError at
0x00964840>)))
The exception tuple has the same sort of nesting as your example, and no
matter how deeply nested the system will find the relevant exception.

Now consider what happens if you used a list here:
ExcGroup1 = [ReferenceError, RuntimeError]
ExcGroup2 = MemoryError, SyntaxError, ExcGroup1
ExcGroup3 = TypeError, ExcGroup2
ExcGroup1.append(ExcGroup3)
ExcGroup3 (<class exceptions.TypeError at 0x009645A0>, (<class exceptions.MemoryError
at 0x00977120>, <class exceptions.SyntaxError at 0x00964A50>, [<class
exceptions.ReferenceError at 0x009770C0>, <class exceptions.RuntimeError at
0x00964840>, (<class exceptions.TypeError at 0x009645A0>, (<class
exceptions.MemoryError at 0x00977120>, <class exceptions.SyntaxError at
0x00964A50>, [...]))])) try: raise ReferenceError("Hello")
except ExcGroup3, e:
print "Caught",e

Traceback (most recent call last):
File "<pyshell#53>", line 2, in -toplevel-
raise ReferenceError("Hello")
ReferenceError: Hello


As it currently stands, the exception is not caught because anything in the
exception specification which is not a tuple is considered to be a
(potential) exception.

With lists is suddenly becomes possible to have a recursive data structure.
repr goes to a lot of effort to ensure that it can detect the recursion and
replace that particular part of the output with ellipses. The exception
handling code would have to do something similar if it accepted lists and
that would slow down all exception processing. By only traversing inside
tuples we can be sure that, even if the data structure as a whole is
recursive, the part which is traversed is not.

(Note that the repr code also fails to detect the recursive tuple, it isn't
until it hits the list a second time that it spots the recursion.)
Nov 30 '05 #40

P: n/a
On 2005-11-29, Duncan Booth <du**********@invalid.invalid> wrote:
Antoon Pardon wrote:
The question is, should we consider this a problem. Personnaly, I
see this as not very different from functions with a list as a default
argument. In that case we often have a list used as a constant too.

Yet python doesn't has a problem with mutating this list so that on
the next call a 'different' list is the default. So if mutating
a list used as a constant is not a problem there, why should it
be a problem in your example?
Are you serious about that?


I'm at least that serious that I do consider the two cases
somewhat equivallent.
The semantics of default arguments are quite clearly defined (although
suprising to some people): the default argument is evaluated once when the
function is defined and the same value is then reused on each call.

The semantics of list constants are also clearly defined: a new list is
created each time the statement is executed. Consider:

res = []
for i in range(10):
res.append(i*i)

If the same list was reused each time this code was executed the list would
get very long. Pre-evaluating a constant list and creating a copy each time
wouldn't break the semantics, but simply reusing it would be disastrous.


This is not about how things are defined, but about should we consider
it a problem if it were defined differently. And no I am not arguing
python should change this. It would break too much code and would
make python all the more surprising.

But lets just consider. Your above code could simply be rewritten
as follows.

res = list()
for i in range(10):
res.append(i*i)

Personnaly I think that the two following pieces of code should
give the same result.

def Foo(l=[]): def Foo(l):
... ...

Foo() Foo([])
Foo() Foo([])

Just as I think, you want your piece of code to give the same
result as how I rewrote it.

I have a problem understanding people who find the below don't
have to be equivallent and the upper must.

--
Antoon Pardon
Nov 30 '05 #41

P: n/a
Antoon Pardon wrote:
But lets just consider. Your above code could simply be rewritten
as follows.

res = list()
for i in range(10):
res.append(i*i)
I don't understand your point here? You want list() to create a new list
and [] to return the same (initially empty) list throughout the run of the
program?

Personnaly I think that the two following pieces of code should
give the same result.

def Foo(l=[]): def Foo(l):
... ...

Foo() Foo([])
Foo() Foo([])

Just as I think, you want your piece of code to give the same
result as how I rewrote it.

I have a problem understanding people who find the below don't
have to be equivallent and the upper must.


The left one is equivalent to:

__anon = []
def Foo(l):
...

Foo(__anon)
Foo(__anon)

The left has one list created outside the body of the function, the right
one has two lists created outside the body of the function. Why on earth
should these be the same?

Or to put it even more simply, it seems that you are suggesting:

__tmp = []
x = __tmp
y = __tmp

should do the same as:

x = []
y = []
Nov 30 '05 #42

P: n/a
On 2005-11-30, Duncan Booth <du**********@invalid.invalid> wrote:
Antoon Pardon wrote:
But lets just consider. Your above code could simply be rewritten
as follows.

res = list()
for i in range(10):
res.append(i*i)
I don't understand your point here? You want list() to create a new list
and [] to return the same (initially empty) list throughout the run of the
program?


No, but I think that each occurence returning the same (initially empty)
list throughout the run of the program would be consistent with how
default arguments are treated.
Personnaly I think that the two following pieces of code should
give the same result.

def Foo(l=[]): def Foo(l):
... ...

Foo() Foo([])
Foo() Foo([])

Just as I think, you want your piece of code to give the same
result as how I rewrote it.

I have a problem understanding people who find the below don't
have to be equivallent and the upper must.
The left one is equivalent to:

__anon = []
def Foo(l):
...

Foo(__anon)
Foo(__anon)


So, why shouldn't:

res = []
for i in range(10):
res.append(i*i)

be equivallent to:

__anon = list()
...

res = __anon
for i in range(10):
res.append(i*i)
The left has one list created outside the body of the function, the right
one has two lists created outside the body of the function. Why on earth
should these be the same?
Why on earth should it be the same list, when a function is called
and is provided with a list as a default argument?

I see no reason why your and my question should be answered differently.
Or to put it even more simply, it seems that you are suggesting:

__tmp = []
x = __tmp
y = __tmp

should do the same as:

x = []
y = []


No, I'm not suggesting it should, I just don't see why it should be
considered a problem if it would do the same, provided this is the
kind of behaviour we already have with list as default arguments.

Why is it a problem when a constant list is mutated in an expression,
but isn't it a problem when a constant list is mutated as a default
argument?

--
Antoon Pardon
Nov 30 '05 #43

P: n/a
Antoon Pardon wrote:
The left one is equivalent to:

__anon = []
def Foo(l):
...

Foo(__anon)
Foo(__anon)
So, why shouldn't:

res = []
for i in range(10):
res.append(i*i)

be equivallent to:

__anon = list()
...

res = __anon
for i in range(10):
res.append(i*i)


Because the empty list expression '[]' is evaluated when the expression
containing it is executed.
The left has one list created outside the body of the function, the
right one has two lists created outside the body of the function. Why
on earth should these be the same?
Why on earth should it be the same list, when a function is called
and is provided with a list as a default argument?


Because the empty list expression '[]' is evaluated when the
expression containing it is executed.

I see no reason why your and my question should be answered
differently.


We are agreed on that, the answers should be the same, and indeed they are.
In each case the list is created when the expression (an assigment or a
function definition) is executed. The behaviour, as it currently is, is
entirely self-consistent.

I think perhaps you are confusing the execution of the function body with
the execution of the function definition. They are quite distinct: the
function definition evaluates any default arguments and creates a new
function object binding the code with the default arguments and any scoped
variables the function may have.

If the system tried to delay the evaluation until the function was called
you would get surprising results as variables referenced in the default
argument expressions could have changed their values.
Nov 30 '05 #44

P: n/a
Antoon Pardon a écrit :
On 2005-11-30, Duncan Booth <du**********@invalid.invalid> wrote:
Antoon Pardon wrote:
But lets just consider. Your above code could simply be rewritten
as follows.

res = list()
for i in range(10):
res.append(i*i)


I don't understand your point here? You want list() to create a new list
and [] to return the same (initially empty) list throughout the run of the
program?

No, but I think that each occurence returning the same (initially empty)
list throughout the run of the program would be consistent with how
default arguments are treated.


What about that :
def f(a):
res = [a]
return res

How can you return the same list that way ? Do you propose to make such
construct illegal ?
Nov 30 '05 #45

P: n/a
On 2005-11-30, Duncan Booth <du**********@invalid.invalid> wrote:
Antoon Pardon wrote:
The left one is equivalent to:

__anon = []
def Foo(l):
...

Foo(__anon)
Foo(__anon)
So, why shouldn't:

res = []
for i in range(10):
res.append(i*i)

be equivallent to:

__anon = list()
...

res = __anon
for i in range(10):
res.append(i*i)


Because the empty list expression '[]' is evaluated when the expression
containing it is executed.


This doesn't follow. It is not because this is how it is now, that that
is the way it should be.

I think one could argue that since '[]' is normally evaluated when
the expression containing it is excuted, it should also be executed
when a function is called, where '[]' is contained in the expression
determining the default value.
The left has one list created outside the body of the function, the
right one has two lists created outside the body of the function. Why
on earth should these be the same?


Why on earth should it be the same list, when a function is called
and is provided with a list as a default argument?


Because the empty list expression '[]' is evaluated when the
expression containing it is executed.


Again you are just stating the specific choice python has made.
Not why they made this choice.
I see no reason why your and my question should be answered
differently.


We are agreed on that, the answers should be the same, and indeed they are.
In each case the list is created when the expression (an assigment or a
function definition) is executed. The behaviour, as it currently is, is
entirely self-consistent.

I think perhaps you are confusing the execution of the function body with
the execution of the function definition. They are quite distinct: the
function definition evaluates any default arguments and creates a new
function object binding the code with the default arguments and any scoped
variables the function may have.
I know what happens, I would like to know, why they made this choice.
One could argue that the expression for the default argument belongs
to the code for the function and thus should be executed at call time.
Not at definion time. Just as other expressions in the function are
not evaluated at definition time.

So when these kind of expression are evaluated at definition time,
I don't see what would be so problematic when other functions are
evaluated at definition time to.
If the system tried to delay the evaluation until the function was called
you would get surprising results as variables referenced in the default
argument expressions could have changed their values.


This would be no more surprising than a variable referenced in a normal
expression to have changed values between two evaluations.

--
Antoon Pardon
Dec 1 '05 #46

P: n/a
On 2005-11-30, Christophe <ch*************@free.fr> wrote:
Antoon Pardon a écrit :
On 2005-11-30, Duncan Booth <du**********@invalid.invalid> wrote:
Antoon Pardon wrote:

But lets just consider. Your above code could simply be rewritten
as follows.

res = list()
for i in range(10):
res.append(i*i)
I don't understand your point here? You want list() to create a new list
and [] to return the same (initially empty) list throughout the run of the
program?

No, but I think that each occurence returning the same (initially empty)
list throughout the run of the program would be consistent with how
default arguments are treated.


What about that :
def f(a):
res = [a]
return res

How can you return the same list that way ? Do you propose to make such
construct illegal ?


I don't propose anything. This is AFAIC just a philosophical
exploration about the cons and pros of certain python decisions.

To answer your question. The [a] is not a constant list, so
maybe it should be illegal. The way python works now each list
is implicitely constructed. So maybe it would have been better
if python required such a construction to be made explicit.

If people would have been required to write:

a = list()
b = list()

Instead of being able to write

a = []
b = []

It would have been clearer that a and b are not the same list.

--
Antoon Pardon
Dec 1 '05 #47

P: n/a
Antoon Pardon wrote:
I know what happens, I would like to know, why they made this choice.
One could argue that the expression for the default argument belongs
to the code for the function and thus should be executed at call time.
Not at definion time. Just as other expressions in the function are
not evaluated at definition time.

Yes you could argue for that, but I think it would lead to a more complex
and confusing language.

The 'why' is probably at least partly historical. When default arguments
were added to Python there were no bound variables, so the option of
delaying the evaluation simply wasn't there. However, I'm sure that if
default arguments were being added today, and there was a choice between
using closures or evaluating the defaults at function definition time, the
choice would still come down on the side of simplicity and clarity.

(Actually, I think it isn't true that Python today could support evaluating
default arguments inside the function without further changes to how it
works: currently class variables aren't in scope inside methods so you
would need to add support for that at the very least.)

If you want the expressions to use closures then you can do that by putting
expressions inside the function. If you changed default arguments to make
them work in the same way, then you would have to play a lot more games
with factory functions. Most of the tricks you can play of the x=x default
argument variety are just tricks, but sometimes they can be very useful
tricks.
Dec 1 '05 #48

P: n/a
Antoon Pardon <ap*****@forel.vub.ac.be> writes:
I know what happens, I would like to know, why they made this choice.
One could argue that the expression for the default argument belongs
to the code for the function and thus should be executed at call time.
Not at definion time. Just as other expressions in the function are
not evaluated at definition time.


The idiom to get a default argument evaluated at call time with the
current behavior is:

def f(arg = None):
if arg is None:
arg = BuildArg()

What's the idiom to get a default argument evaluated at definition
time if it were as you suggested?

<mike
--
Mike Meyer <mw*@mired.org> http://www.mired.org/home/mwm/
Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.
Dec 1 '05 #49

P: n/a

Mike Meyer wrote:
Antoon Pardon <ap*****@forel.vub.ac.be> writes:
I know what happens, I would like to know, why they made this choice.
One could argue that the expression for the default argument belongs
to the code for the function and thus should be executed at call time.
Not at definion time. Just as other expressions in the function are
not evaluated at definition time.
The idiom to get a default argument evaluated at call time with the
current behavior is:

def f(arg = None):
if arg is None:
arg = BuildArg()

What's the idiom to get a default argument evaluated at definition
time if it were as you suggested?


Having default arguments evaluated at definition time certainly bites a
lot of newbies. It allows useful tricks with nested scopes though.

All the best,

Fuzzyman
http://www.voidspace.org.uk/python/index.shtml
<mike
--
Mike Meyer <mw*@mired.org> http://www.mired.org/home/mwm/
Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.


Dec 1 '05 #50

66 Replies

This discussion thread is closed

Replies have been disabled for this discussion.