在for循环中,由于它是一个元组,如何解压缩对象?

时间:2018-10-26 07:54:54

标签: python iterator generator python-3.6 iterable-unpacking

我尝试创建以下代码:

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列表的情况下解压缩python列表中的对象?对于最后一个示例,结果将是:

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

4 个答案:

答案 0 :(得分:5)

问题是您的__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__方法应返回值,而不是 yield 。有关详细说明和修复,请参见this question

答案 1 :(得分:2)

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

答案 2 :(得分:2)

您接近了,但是,您需要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

因此,为了避免在循环中进行额外的拆包,您必须通过循环遍历属性并一次生成每个属性来创建一系列生成的值。

答案 3 :(得分:1)

  

问题:由于对象是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)))
    

  1. 使用类属性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()))
    

  1. 推荐用法。没有任何魔术

    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