Как распаковать объект как кортеж в цикле for? - PullRequest
0 голосов
/ 26 октября 2018

Я попытался создать следующий код:

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)

Но когда я пытаюсь бежать, python говорит:

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)

Я могу обойти это, распаковав вручную:

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)

Как мы можем распаковать объекты в списке python, не делая этого явно, как продемонстрировал обходной путь? Для последнего примера результат будет таким:

arg1 1 arg2 2 arg3 3
arg1 2 arg2 3 arg3 4
arg1 3 arg2 4 arg3 5

Ответы [ 4 ]

0 голосов
/ 26 октября 2018

Вы близки, однако вам нужно yield значения в методе __iter__, а не в методе __next__:

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 даст результат tuple(1, 2, 3), который при переборе по списку требует дополнительной распаковки, т. Е.:

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

Таким образом, чтобы избежать дополнительной распаковки в цикле, вы должны создать поток сгенерированных значений с помощью циклаатрибуты и урожайность каждого, по одному за раз.

0 голосов
/ 26 октября 2018

__next__ полезно, если у вас есть переменное количество элементов для возврата. Поскольку у вас есть фиксированное количество атрибутов, которые можно получить от вашего итератора, вы можете просто использовать функцию iter, чтобы создать итератор из трех атрибутов и заставить метод __iter__ возвращать итератор вместо этого:

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))

, что эквивалентно, без использования функции iter:

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))
0 голосов
/ 26 октября 2018

Вопрос : Как распаковать объект, который был кортежем в цикле for?

Вы ошибаетесь, вы получаете Test(object) вцикл.
Чтобы получить tuple(arg1, arg2, arg3) от объекта, вы должны вызвать его.

  1. , используя 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)
    

    или принудительно .format(... чтобы увидеть t как тип tuple:

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

с использованием свойства класса 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)

или с .format(... и *

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

Рекомендовать использование.без какой-либо магии :

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

или с .format(...

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

Проверено на Python: 3,5

0 голосов
/ 26 октября 2018

Проблема в том, что ваш метод __next__ не реализован правильно.Вы сделали класс Test итеративным, но он не повторяет то, как вы думаете:

>>> 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>

Вместо получения кортежа (self.arg1, self.arg2, self.arg3) он дает генераторы.Это вызвано тем, что вы используете yield в методе __next__.Метод __next__ должен возвращать значений, а не давать их.См. этот вопрос для подробного объяснения и исправления.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...