4

When working with a function that returns multiple values with a tuple, I will often find myself using the following idiom to unpack the results from inside a list comprehension.

fiz, buz = zip(*[f(x) for x in input])

Most of the time this works fine, but it throws a ValueError: need more than 0 values to unpack if input is empty. The two ways I can think of to get around this are

fiz = []
buz = []
for x in input:
    a, b = f(x)
    fiz.append(a)
    buz.append(b)

and

if input:
    fiz, buz = zip(*[f(x) for x in input])
else:
    fiz, buz = [], []

but neither of these feels especially Pythonic—the former is overly verbose and the latter doesn't work if input is a generator rather than a list (in addition to requiring an if/else where I feel like one really shouldn't be needed).

Is there a good simple way to do this? I've mostly been working in Python 2.7 recently, but would also be interested in knowing any Python 3 solutions if they are different.

Nyle
  • 51
  • 5

3 Answers3

1

If f = lambda x: (x,x**2) then this works

x,y = zip(*map(f,input)) if len(input) else ((),())

If input=[], x=() and y=().

If input=[2], x=(2,) and y=(4,)

If input=[2,3], x=(2,3) and y=(4,9)

They're tuples (not lists), but thats thats pretty easy to change.

woodpav
  • 1,917
  • 2
  • 13
  • 26
0

I would consider using collections.namedtuple() for this sort of thing. I believe the named tuples are deemed more pythonic, and should avoid the need for complicated list comprehensions and zipping / unpacking.


From the documentation:

>>> p = Point(11, y=22)     # instantiate with positional or keyword arguments
>>> p[0] + p[1]             # indexable like the plain tuple (11, 22)
33
>>> x, y = p                # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y               # fields also accessible by name
33
>>> p                       # readable __repr__ with a name=value style
Point(x=11, y=22)
Tom Wyllie
  • 2,020
  • 13
  • 16
  • I'm familiar with `namedtuple`s, but I don't see how they would help here. Typically when I'm doing something like this `fiz` and `buz` will be two separate things that are convenient or necessary to calculate together, but will be dealt with separately once they've been unpacked. – Nyle Jun 29 '17 at 17:05
0

You could use:

fiz = []
buz = []
results = [fiz, buz]

for x in input:
    list(map(lambda res, val: res.append(val), results, f(x)))

print(results)

Note about list(map(...)): in Python3, map returns a generator, so we must use it if we want the lambda to be executed.list does it.

(adapted from my answer to Pythonic way to append output of function to several lists, where you could find other ideas.)

Thierry Lathuille
  • 23,663
  • 10
  • 44
  • 50