Why is there no tuple comprehension in Python?
Question:
As we all know, there’s list comprehension, like
[i for i in [1, 2, 3, 4]]
and there is dictionary comprehension, like
{i:j for i, j in {1: 'a', 2: 'b'}.items()}
but
(i for i in (1, 2, 3))
will end up in a generator, not a tuple
comprehension. Why is that?
My guess is that a tuple
is immutable, but this does not seem to be the answer.
Answers:
My best guess is that they ran out of brackets and didn’t think it would be useful enough to warrent adding an “ugly” syntax …
You can use a generator expression:
tuple(i for i in (1, 2, 3))
but parentheses were already taken for … generator expressions.
I believe it’s simply for the sake of clarity, we do not want to clutter the language with too many different symbols. Also a tuple
comprehension is never necessary, a list can just be used instead with negligible speed differences, unlike a dict comprehension as opposed to a list comprehension.
Comprehension works by looping or iterating over items and assigning them into a container, a Tuple is unable to receive assignments.
Once a Tuple is created, it can not be appended to, extended, or assigned to. The only way to modify a Tuple is if one of its objects can itself be assigned to (is a non-tuple container). Because the Tuple is only holding a reference to that kind of object.
Also – a tuple has its own constructor tuple()
which you can give any iterator. Which means that to create a tuple, you could do:
tuple(i for i in (1,2,3))
Raymond Hettinger (one of the Python core developers) had this to say about tuples in a recent tweet:
#python tip: Generally, lists are for looping; tuples for structs. Lists are homogeneous; tuples heterogeneous. Lists for variable length.
This (to me) supports the idea that if the items in a sequence are related enough to be generated by a, well, generator, then it should be a list. Although a tuple is iterable and seems like simply a immutable list, it’s really the Python equivalent of a C struct:
struct {
int a;
char b;
float c;
} foo;
struct foo x = { 3, 'g', 5.9 };
becomes in Python
x = (3, 'g', 5.9)
Tuples cannot efficiently be appended like a list.
So a tuple comprehension would need to use a list internally and then convert to a tuple.
That would be the same as what you do now : tuple( [ comprehension ] )
We can generate tuples from a list comprehension. The following one adds two numbers sequentially into a tuple and gives a list from numbers 0-9.
>>> print k
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> r= [tuple(k[i:i+2]) for i in xrange(10) if not i%2]
>>> print r
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
Parentheses do not create a tuple. aka one = (two) is not a tuple. The only way around is either one = (two,) or one = tuple(two). So a solution is:
tuple(i for i in myothertupleorlistordict)
Since Python 3.5, you can also use splat *
unpacking syntax to unpack a generator expression:
*(x for x in range(10)),
As another poster macm
mentioned in his answer, the fastest way to create a tuple from a generator is tuple([generator])
.
Performance Comparison
-
List comprehension:
$ python3 -m timeit "a = [i for i in range(1000)]"
10000 loops, best of 3: 27.4 usec per loop
-
Tuple from list comprehension:
$ python3 -m timeit "a = tuple([i for i in range(1000)])"
10000 loops, best of 3: 30.2 usec per loop
-
Tuple from generator:
$ python3 -m timeit "a = tuple(i for i in range(1000))"
10000 loops, best of 3: 50.4 usec per loop
-
Tuple from unpacking:
$ python3 -m timeit "a = *(i for i in range(1000)),"
10000 loops, best of 3: 52.7 usec per loop
My version of python:
$ python3 --version
Python 3.6.3
So you should always create a tuple from a list comprehension unless performance is not an issue.
On my python (3.5) using a generator with deque
from collections
is slightly quicker then using a list
comprehension:
>>> from collections import deque
>>> timeit.timeit(lambda: tuple([i for i in range(10000000)]),number=10)
9.294099200000005
>>> timeit.timeit(lambda: tuple(deque((i for i in range(10000000)))),number=10)
9.007653800000014
Because you can not append items to a tuple. This is how a simple list comprehension can be converted into more basic python code.
_list = [1,2,3,4,5]
clist = [ i*i for i in _list ]
print(clist)
clist1 = []
for i in _list:
clist1.append(i*i)
print(clist1)
Now using a tuple comprehension for above example means appending items into a tuple which is not allowed. Though you can covert this list to a tuple once it is ready by using tuple(clist1)
Well there is tuple comprehension in python3 now. You can follow below code snippet.
(k*k for k in range(1,n+1))
it will return a generator object comprehension.
As we all know, there’s list comprehension, like
[i for i in [1, 2, 3, 4]]
and there is dictionary comprehension, like
{i:j for i, j in {1: 'a', 2: 'b'}.items()}
but
(i for i in (1, 2, 3))
will end up in a generator, not a tuple
comprehension. Why is that?
My guess is that a tuple
is immutable, but this does not seem to be the answer.
My best guess is that they ran out of brackets and didn’t think it would be useful enough to warrent adding an “ugly” syntax …
You can use a generator expression:
tuple(i for i in (1, 2, 3))
but parentheses were already taken for … generator expressions.
I believe it’s simply for the sake of clarity, we do not want to clutter the language with too many different symbols. Also a tuple
comprehension is never necessary, a list can just be used instead with negligible speed differences, unlike a dict comprehension as opposed to a list comprehension.
Comprehension works by looping or iterating over items and assigning them into a container, a Tuple is unable to receive assignments.
Once a Tuple is created, it can not be appended to, extended, or assigned to. The only way to modify a Tuple is if one of its objects can itself be assigned to (is a non-tuple container). Because the Tuple is only holding a reference to that kind of object.
Also – a tuple has its own constructor tuple()
which you can give any iterator. Which means that to create a tuple, you could do:
tuple(i for i in (1,2,3))
Raymond Hettinger (one of the Python core developers) had this to say about tuples in a recent tweet:
#python tip: Generally, lists are for looping; tuples for structs. Lists are homogeneous; tuples heterogeneous. Lists for variable length.
This (to me) supports the idea that if the items in a sequence are related enough to be generated by a, well, generator, then it should be a list. Although a tuple is iterable and seems like simply a immutable list, it’s really the Python equivalent of a C struct:
struct {
int a;
char b;
float c;
} foo;
struct foo x = { 3, 'g', 5.9 };
becomes in Python
x = (3, 'g', 5.9)
Tuples cannot efficiently be appended like a list.
So a tuple comprehension would need to use a list internally and then convert to a tuple.
That would be the same as what you do now : tuple( [ comprehension ] )
We can generate tuples from a list comprehension. The following one adds two numbers sequentially into a tuple and gives a list from numbers 0-9.
>>> print k
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> r= [tuple(k[i:i+2]) for i in xrange(10) if not i%2]
>>> print r
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
Parentheses do not create a tuple. aka one = (two) is not a tuple. The only way around is either one = (two,) or one = tuple(two). So a solution is:
tuple(i for i in myothertupleorlistordict)
Since Python 3.5, you can also use splat *
unpacking syntax to unpack a generator expression:
*(x for x in range(10)),
As another poster macm
mentioned in his answer, the fastest way to create a tuple from a generator is tuple([generator])
.
Performance Comparison
-
List comprehension:
$ python3 -m timeit "a = [i for i in range(1000)]" 10000 loops, best of 3: 27.4 usec per loop
-
Tuple from list comprehension:
$ python3 -m timeit "a = tuple([i for i in range(1000)])" 10000 loops, best of 3: 30.2 usec per loop
-
Tuple from generator:
$ python3 -m timeit "a = tuple(i for i in range(1000))" 10000 loops, best of 3: 50.4 usec per loop
-
Tuple from unpacking:
$ python3 -m timeit "a = *(i for i in range(1000))," 10000 loops, best of 3: 52.7 usec per loop
My version of python:
$ python3 --version
Python 3.6.3
So you should always create a tuple from a list comprehension unless performance is not an issue.
On my python (3.5) using a generator with deque
from collections
is slightly quicker then using a list
comprehension:
>>> from collections import deque
>>> timeit.timeit(lambda: tuple([i for i in range(10000000)]),number=10)
9.294099200000005
>>> timeit.timeit(lambda: tuple(deque((i for i in range(10000000)))),number=10)
9.007653800000014
Because you can not append items to a tuple. This is how a simple list comprehension can be converted into more basic python code.
_list = [1,2,3,4,5]
clist = [ i*i for i in _list ]
print(clist)
clist1 = []
for i in _list:
clist1.append(i*i)
print(clist1)
Now using a tuple comprehension for above example means appending items into a tuple which is not allowed. Though you can covert this list to a tuple once it is ready by using tuple(clist1)
Well there is tuple comprehension in python3 now. You can follow below code snippet.
(k*k for k in range(1,n+1))
it will return a generator object comprehension.