Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unpack an object as it was a tuple in a for loop?

I tried create the following code:

class Test(object):

    def __init__(self, arg):
        self.arg1 = arg + 1
        self.arg2 = arg + 2
        self.arg3 = arg + 3

    def __iter__(self):
        return self

    def __next__(self):
        yield self.arg1, self.arg2, self.arg3


test_list = [Test(0), Test(1), Test(2)]

for arg1, arg2, arg3 in test_list:
    print('arg1', arg1, 'arg2', arg2, 'arg3', arg3)

But when I try to run, python says:

Traceback (most recent call last):
  File "test.py", line 20, in <module>
    for arg1, arg2, arg3 in test_list:
ValueError: too many values to unpack (expected 3)

I can work around it by unpacking by hand:

class Test(object):

    def __init__(self, arg):
        self.arg1 = arg + 1
        self.arg2 = arg + 2
        self.arg3 = arg + 3

test_list = [Test(0), Test(1), Test(2)]

for test in test_list:
    arg1, arg2, arg3 = test.arg1, test.arg2, test.arg3
    print('arg1', arg1, 'arg2', arg2, 'arg3', arg3)

How can we unpack objects in a python list without doing it explicitly as the workaround demonstrated? For the last example, the result would be like:

arg1 1 arg2 2 arg3 3
arg1 2 arg2 3 arg3 4
arg1 3 arg2 4 arg3 5
like image 267
user Avatar asked Oct 26 '18 07:10

user


People also ask

How do I unpack a tuple in a for loop?

Unpack a Tuple in a for Loop in Python The number of variables on the left side or before the equals sign should equal the length of the tuple or the list. For example, if a tuple has 5 elements, then the code to unpack it would be as follows. We can use the same syntax to unpack values within a for loop.

How do you extract an element from a tuple?

To extract the n-th elements from a list of tuples with Python, we can use list comprehension. We get the n-th element from each tuple and put them into a list with [x[n] for x in elements] . x is the tuple being retrieved. Therefore, e is [1, 3, 5] .

Can you unpack a tuple?

In python tuples can be unpacked using a function in function tuple is passed and in function values are unpacked into normal variable.

How do you display a tuple element using a loop?

You can loop through the list items by using a while loop. Use the len() function to determine the length of the tuple, then start at 0 and loop your way through the tuple items by refering to their indexes. Remember to increase the index by 1 after each iteration.


4 Answers

You are close, however, you need to yield the values in the __iter__ method, not the __next__ method:

class Test:
  def __init__(self, arg):
    self.arg1 = arg + 1
    self.arg2 = arg + 2
    self.arg3 = arg + 3
  def __iter__(self):
    yield from [self.arg1, self.arg2, self.arg3]

for a, b, c in [Test(0), Test(1), Test(2)]:
  pass

yield self.arg1, self.arg2, self.arg3 will give a tuple result (1, 2, 3) which, when iterating over the list, requires additional unpacking i.e:

for [(a, b, c)] in [Test(0), Test(1), Test(2)]:
  pass

Thus, in order to avoid the additional unpacking in the loop, you have to create a stream of generated values by looping over the attributes and yielding each, one at a time.

like image 113
Ajax1234 Avatar answered Oct 20 '22 00:10

Ajax1234


The problem is that your __next__ method isn't implemented correctly. You made the Test class iterable, but it doesn't iterate how you think it does:

>>> itr = iter(Test(0))
>>> next(itr)
<generator object Test.__next__ at 0x7ff609ade9a8>
>>> next(itr)
<generator object Test.__next__ at 0x7ff609ade930>
>>> next(itr)
<generator object Test.__next__ at 0x7ff609ade9a8>

Instead of yielding a (self.arg1, self.arg2, self.arg3) tuple, it yields generators. This is caused by your use of yield inside the __next__ method. A __next__ method should return values, not yield them. See this question for a detailed explanation and fix.

like image 41
Aran-Fey Avatar answered Oct 20 '22 01:10

Aran-Fey


__next__ is useful if you have a variable number of items to return. Since you have a fixed number of attributes to yield from your iterator, you can simply use the iter function to create an iterator from the three attributes and make the __iter__ method return the iterator instead:

class Test(object):

    def __init__(self, arg):
        self.arg1 = arg + 1
        self.arg2 = arg + 2
        self.arg3 = arg + 3

    def __iter__(self):
        return iter((self.arg1, self.arg2, self.arg3))

which is equivalent to, without using the iter function:

class Test(object):
    class Iter:
        def __init__(self, lst):
            self.lst = lst
            self.index = 0

        def __next__(self):
            if self.index == len(self.lst):
                raise StopIteration()
            value = self.lst[self.index]
            self.index += 1
            return value

    def __init__(self, arg):
        self.arg1 = arg + 1
        self.arg2 = arg + 2
        self.arg3 = arg + 3

    def __iter__(self):
        return self.Iter((self.arg1, self.arg2, self.arg3))
like image 29
blhsing Avatar answered Oct 20 '22 00:10

blhsing


Question: How to unpack an object as it was a tuple in a for loop?

You are wrong, you get a Test(object) in the loop.
To get a tuple(arg1, arg2, arg3) from the object, you have to trigger it.

  1. using iter:

    class Test(object):
       ...
       def __iter__(self):
           yield self.arg1
           yield self.arg2
           yield self.arg3
    
    
    for arg1, arg2, arg3 in map(iter, test_list):
        print('arg1', arg1, 'arg2', arg2, 'arg3', arg3)
    

    or force .format(... to see t as type tuple:

    for t in test_list:
        print("arg1={}, arg2={}, arg3={}".format(*tuple(t)))
    

  1. using class property values(self, ...:

    class Test(object):
       ...
        #@property
        def values(self):
            return self.arg1, self.arg2, self.arg3
    
    
    for arg1, arg2, arg3 in map(Test.values, test_list):
        print('arg1', arg1, 'arg2', arg2, 'arg3', arg3)
    

    or with .format(... and *

    for t in test_list:
        print("arg1={}, arg2={}, arg3={}".format(*t.values()))
    

  1. Recommend usage. without any magic:

    for t in test_list:
        print('arg1', t.arg1, 'arg2', t.arg2, 'arg3', t.arg3)
    

    or with .format(...

    for t in test_list:
        print("arg1={t.arg1}, arg2={t.arg2}, arg3={t.arg3}".format(t=t))
    

Tested with Python: 3.5

like image 41
stovfl Avatar answered Oct 20 '22 00:10

stovfl