zfz
zfz

Reputation: 1637

What's the difference between "()" and "[]" when generating in Python?

There is a list: nodes = [20, 21, 22, 23, 24, 25].

I used two ways to generate new 2-dimentional objects:

tour1 = (((a,b) for a in nodes )for b in nodes)
tour2 = [[(a,b) for a in nodes ]for b in nodes]

The type of tour1 is a generator while tour2 is a list:

In [34]: type(tour1)
Out[34]: <type 'generator'>

In [35]: type(tour2)
Out[35]: <type 'list'>

I want to know why tour1 is not a tuple? Thanks.

Upvotes: 7

Views: 410

Answers (5)

pepr
pepr

Reputation: 20794

To add... Actually, the generator expression does not need the parentheses at all. You only need them when the generator expression produces wrong syntax -- here because of the assignment. When passing the generator to a function (or the like), you do not need the parentheses. Try the following:

tour3 = list(list((a,b) for a in nodes) for b in nodes)

It produces exactly the same result as your tour2. This way, you can look at the [ as at a syntactic sugar for list(, and the ] is the syntactic sugar related to the ). However, it is compiled differently by the compiler. You can try to disassembly (you need to pass a function):

>>> import dis
>>> def fn1():
...   return list(list((a,b) for a in nodes) for b in nodes)
...
>>> def fn2():
...   return [[(a,b) for a in nodes ]for b in nodes]
...
>>> dis.dis(fn1)
  2           0 LOAD_GLOBAL              0 (list)
              3 LOAD_CONST               1 (<code object <genexpr> at 000000000229A9B0, file "<stdin>", line 2>)
              6 MAKE_FUNCTION            0
              9 LOAD_GLOBAL              1 (nodes)
             12 GET_ITER
             13 CALL_FUNCTION            1
             16 CALL_FUNCTION            1
             19 RETURN_VALUE
>>> dis.dis(fn2)
  2           0 BUILD_LIST               0
              3 LOAD_GLOBAL              0 (nodes)
              6 GET_ITER
        >>    7 FOR_ITER                37 (to 47)
             10 STORE_FAST               0 (b)
             13 BUILD_LIST               0
             16 LOAD_GLOBAL              0 (nodes)
             19 GET_ITER
        >>   20 FOR_ITER                18 (to 41)
             23 STORE_FAST               1 (a)
             26 LOAD_FAST                1 (a)
             29 LOAD_FAST                0 (b)
             32 BUILD_TUPLE              2
             35 LIST_APPEND              2
             38 JUMP_ABSOLUTE           20
        >>   41 LIST_APPEND              2
             44 JUMP_ABSOLUTE            7
        >>   47 RETURN_VALUE

So you can see it is different (i.e. looks like a syntactic sugar, but it is not). Unfortunately, Python does not know how to disassembly a generator:

>>> g = (list((a,b) for a in nodes) for b in nodes)
>>> dis.dis(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python27\lib\dis.py", line 49, in dis
type(x).__name__
TypeError: don't know how to disassemble generator objects

Update:

One may be tempted--when looking at the above disassembled code--that the fn1 is faster (having the shorter code). However, it is the case of all function calls in all languages that the function call looks shorter that the unfolded code. It says nothing about the internals of the called code. Some points of The Zen of Python:

>>> import this
The Zen of Python, by Tim Peters
...
Readability counts.
...
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
...
>>>

There is the timeit standard module to measure the execution time. Let's try to use it for the two cases:

>>> import timeit
>>> t = timeit.Timer('list(list((a,b) for a in nodes) for b in nodes)',
...                  'nodes = [20, 21, 22, 23, 24, 25]')
>>> print("%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000))
17.74 usec/pass

and now with the square brackets:

>>> t = timeit.Timer('[[(a,b) for a in nodes ]for b in nodes]',
...                  'nodes = [20, 21, 22, 23, 24, 25]')
>>> print("%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000))
7.14 usec/pass
>>>

This clearly shows that making the list of lists via [ ] is faster. The reason is that there is less function calls. The Python compiler can produce more straightforward code.

Upvotes: 2

Jonathan Ballet
Jonathan Ballet

Reputation: 1003

Because the syntax (x for x in l) is a so called "generator expression": see http://docs.python.org/2/reference/expressions.html#generator-expressions

Upvotes: 5

NPE
NPE

Reputation: 500903

The fundamental difference is that the first is a generator expression, and the second is a list comprehension. The former only yields elements as they are required, whereas the latter always produces the entire list when the comprehension is run.

For more info, see Generator Expressions vs. List Comprehension

There is no such thing as a "tuple comprehension" in Python, which is what you seem to be expecting from the first syntax.

If you wish to turn tour1 into a tuple of tuples, you could use the following:

In [89]: tour1 = tuple(tuple((a,b) for a in nodes )for b in nodes)

In [90]: tour1
Out[90]: 
(((20, 20), (21, 20), (22, 20), (23, 20), (24, 20), (25, 20)),
 ((20, 21), (21, 21), (22, 21), (23, 21), (24, 21), (25, 21)),
 ((20, 22), (21, 22), (22, 22), (23, 22), (24, 22), (25, 22)),
 ((20, 23), (21, 23), (22, 23), (23, 23), (24, 23), (25, 23)),
 ((20, 24), (21, 24), (22, 24), (23, 24), (24, 24), (25, 24)),
 ((20, 25), (21, 25), (22, 25), (23, 25), (24, 25), (25, 25)))

Upvotes: 10

applicative_functor
applicative_functor

Reputation: 4986

It's generator, but you can simply change it to tuple:

>>> (i for i in xrange(4))
<generator object <genexpr> at 0x23ea9b0>
>>> tuple(i for i in xrange(4))
(0, 1, 2, 3)

Upvotes: 2

ecatmur
ecatmur

Reputation: 157484

The syntax for a tuple is not parentheses (), it's the comma ,. You can create a tuple without parentheses:

x = 1, 2, 3

If you want to create a tuple from a comprehension, just use the tuple constructor:

tuple(tuple((a,b) for a in nodes )for b in nodes)

Upvotes: 10

Related Questions