pythonlisttupleslist-comprehension

Tuple comprehension creates generator; List comprehension evaluates all elements right away


I am using a long ad hoc script for exploratory data analysis -- not for tool development. The script has gotten quite long, so I've taken to deling ephemeral variables to keep the Spyder Variable Explorer clean. I've done this all over the script.

I tried to be streamline the script by coding some loops as tuple comprehensions, thus eliminating the need for an extra line of code to del the iteration variable. Here is an example of three ways to iterate through figures to clear the plots:

# Generate the figures
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.close('all')
plt.scatter([1,2,3],[4,5,6])
plt.figure()
plt.scatter([1,2,3],[6,5,4])

# 3 ways to clear the figures

if True: # Use Tuple Comprehension
   ( plt.figure(iFig).clf() for iFig in plt.get_fignums() )

elif True: # Use List Comprehension

   [ plt.figure(iFig).clf() for iFig in plt.get_fignums() ]

else: # Don't use comprehension

   for iFig in plt.get_fignums(): plt.figure(iFig).clf()
   del iFig # The extra line of source code I want to avoid

The 3rd and final option is the one I've been using. The 1st and 2nd options are my attempts at tuple comprehension and list comprehension.

The tuple comprehension returns a generator object and doesn't actually execute evaluate the invocation to clf unless I assign the tuple to x and execute next(x) twice. I can see the figures clearing each time.

This is unnecessary for the list comprehension.

I started off with tuple comprehension with the rationale that I can avoid using a mutable container if I'm not going to use the contents anyway.

Why does the tuple comprehension yield a generator that must be iterated through while list comprehension evaluates all the elements needed to build the list right away?

Is there a known Python rule about list and tuple comprehension that would have allowed me to foresee this?


Solution

  • This is not a "tuple comprehension":

    ( plt.figure(iFig).clf() for iFig in plt.get_fignums() )
    

    It's just a generator wrapped in parentheses. They're like parentheses around any other expression, for syntactic grouping, they don't create a tuple. Consider the expression (1) -- this is equivalent to just 1, not (1,).

    Tuples are created using a comma, not parentheses, although often we wrap the comma-separated items in parentheses to make the grouping clear. This is specifically necessary when the tuple is an argument to a function, because the comma interpretation as argument separators takes precedence. Consider:

    x = 1, 2 # sets x to a tuple
    

    If you want to create a tuple from the generator, call the tuple() function:

    tuple(plt.figure(iFig).clf() for iFig in plt.get_fignums())
    

    But stylistically, this is inappropriate in the first place (i.e. it's not considered Pythonic). Generators and comprehensions shouldn't be used purely for side effects, use them when you actually want to process the results (either lazily with a generator, or collecting them with a comprehension). Use a for loop when you only care about side effects.