-1

I have an iterable that returns multiple values as a list. In this case, I only care about one of the values in each iteration.

As a concrete example:

class Triples:
 def __init__(self):
  self.x = 0
 def __iter__(self):
  return self
 def __next__(self):
  self.x += 1
  if (self.x < 5):
   return((self.x*2-1, self.x*2, self.x*2*-1))
  else:
   raise StopIteration

for x in Triples():
 print(x)

This produces:

(1, 2, -2)
(3, 4, -4)
(5, 6, -6)
(7, 8, -8)

But I really just want:

2
4
6
8

I could just do:

for x in Triples():
 print(x[1])

or:

for x in Triples():
 y = x[1]
 print(y)

or:

for (_,x,*_) in Triples():
 print(x)

but it feels like it would be nicer to tell Python that I'm uninterested in all the other values instead of storing them in a place I won't look. (And adding a del(_) feels both too late and too aggressive.)

In particular, this doesn't work:

for x in Triples()[1]:
 print(x)

(It errors out with TypeError: 'Triples' object is not subscriptable; note that that makes this related question's highest-rated answer incorrect for this case.)

and this:

for x in list(Triples())[1]:
 print(x)

Does the wrong thing. It generates the whole list of lists and then returns the second list (instead of the second element of each list), and produces:

3
4
-4

Is there something better or should I just do one of the things I already mentioned?

wfaulk
  • 1,765
  • 1
  • 17
  • 24

2 Answers2

3

The solutions you already have are likely the clearest ones available.

I'd go for either indexing:

for x in Triples():
    print(x[1])

Or unpacking with dummy names:

for _, x, _ in Triples():  # no need for parentheses or *_ here
    print(x)

But if neither of those are satisfying to you, there are a few more options. If you really needed to get a list or iterator of just the second values from the iterator, you could write a list comprehension or generator expression, separate from the loop:

second_values = (x for _, x, _ in Triples())

for x in second_values:
    print(x)

For a finite iterator, you could also play around with zip(*Triples()), to transpose your data so you can index it all at once to get the second values from every result:

for x in list(zip(*Triples()))[1]:
    print(x)

That is not be very easily understandable though, and consumes the entire iterator up-front, rather than lazily as the iteration happens.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • I just used `*_` to make the example more generic, though I suppose my example also explicitly returned three values. The parentheses just read more clearly to me. I was thinking that avoiding the assignment of unused values might save memory/CPU time/resources, but it sounds like there's not really anything for me, so I can't test that. – wfaulk Aug 22 '22 at 15:39
0

I would suggest using the first option:

for x in Triples():
    print(x[1])

The highest rated answer that you mentioned is actually correct. The situation here is different; You are trying to slice an object, whereas the answer to that question is taking the return value of a function (a tuple) and slicing it.