Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling empty case with tuple filtering and unpacking

I have a situation with some parallel lists that need to be filtered based on the values in one of the lists. Sometimes I write something like this to filter them:

lista = [1, 2, 3]
listb = [7, 8, 9]
filtered_a, filtered_b = zip(*[(a, b) for (a, b) in zip(lista, listb) if a < 3])

This gives filtered_a == (1, 2) and filtered_b == (7, 8)

However, changing the final condition from a < 3 to a < 0 causes an exception to be raised:

Traceback (most recent call last):
  ...
ValueError: need more than 0 values to unpack

I know why this is happening: the list comprehension is empty, so it's like calling zip(*[]), which is like zip(), which just returns an empty list which cannot be unpacked into separate filtered_a and filtered_b iterables.

Is there a better (shorter, simpler, more pythonic) filtering function that handles the empty case? In the empty case, I would expect filtered_a, and filtered_b to be empty iterables so any following code could remain unchanged.

like image 655
Justin Avatar asked Nov 07 '16 15:11

Justin


People also ask

What is unpacking of tuple?

Unpacking a tuple means splitting the tuple's elements into individual variables. For example: x, y = (1, 2) Code language: Python (python)

What is packing and unpacking of tuple?

Tuple packing refers to assigning multiple values into a tuple. Tuple unpacking refers to assigning a tuple into multiple variables.

How does tuple unpacking work in Python?

Unpacking Tuples When we put tuples on both sides of an assignment operator, a tuple unpacking operation takes place. The values on the right are assigned to the variables on the left according to their relative position in each tuple . As you can see in the above example, a will be 1 , b will be 2 , and c will be 3 .


2 Answers

You could simply short-circuit with the default values:

filtered_a, filtered_b = zip(*[(a, b) for a, b in zip(lista, listb) if a < 0]) or ([], [])
print(filtered_b, filtered_a)
# [] []

For Python 3, you'll need to call list on the iterator returned by zip so the first operand can be evaluated as an empty list (not an iterator), else the default value is never reached even when the iterator is potentially empty since bool(iter([])) is True.

like image 97
Moses Koledoye Avatar answered Oct 03 '22 01:10

Moses Koledoye


I'd probably do something like:

lista = [1, 2, 3]
listb = [7, 8, 9]

filtered_abs = ((a, b) for (a, b) in zip(lista, listb) if a < 3])

for a, b in filtered_abs:
    do_thing(a, b)
like image 21
Tom Dalton Avatar answered Oct 02 '22 23:10

Tom Dalton