Death to tuples!

A

Alex Martelli

Mike Meyer said:
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
 
A

Alex Martelli

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
 
R

Rikard Bosnjakovic

Steven said:

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:
'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:
'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.
 
B

bonono

Rikard said:
Steven said:

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:
'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:
'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"
 
A

Antoon Pardon

Antoon said:
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?
 
F

Fredrik Lundh

Paddy said:
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>
 
B

Bengt Richter

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
 
D

Duncan Booth

Antoon said:
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.
 
S

sluggoster

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 <[email protected]>
 
B

Bengt Richter

Antoon said:
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
 
F

Fredrik Lundh

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/2005-10-16_2005-10-31.html#freeze-protocol

</F>
 
J

jepler

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-----
 
D

Duncan Booth

Dan said:
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.
 
C

Christopher Subich

Bengt said:
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):
outer i
print i
Definitely a Py3K proposal, though.
 
S

skip

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
 
B

Bengt Richter

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



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...
Traceback (most recent call last):
Traceback (most recent call last):
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
 
N

Nicola Larosa

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.
 
C

Christopher Subich

Bengt Richter wrote
If we had a way to effect an override of a specific instance' attribute accesse
to make certain attribute names act as if they were defined i type(instance), an
if we could do this with function instances, and if function loca accesses woul
check if names were one of the ones specified for the functio instance being called
then we could define locally named constants etc like properties

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

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/delet

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, whic
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 use

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 accumulato
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 object
aside
ii) Aliases, such that 'a' and 'b' actually reference the same bit

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

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

manipulations become possible, such as redefining a variable used by

function to be local/global/outer. Very dangerous, of course, but
potentially very flexible. One case that comes to mind is
"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 goo

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

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'

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 t
the
level of "local_name"

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

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

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.
 
M

Mike Meyer

Duncan Booth said:
Don't forget the exception specification in a try..catch statement:


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
 
D

Duncan Booth

Mike said:
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:
raise ReferenceError("Hello")
except ExcGroup3, e:
print "Caught",e


Caught Hello(<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>, [...]))])) 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.)
 

Ask a Question

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

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

Ask a Question

Members online

Forum statistics

Threads
473,781
Messages
2,569,616
Members
45,306
Latest member
TeddyWeath

Latest Threads

Top