【问题标题】:How to unpack an object as it was a tuple in a for loop?如何解压对象,因为它是 for 循环中的元组?
【发布时间】:2019-03-30 22:27:39
【问题描述】:

我尝试创建以下代码:

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

【问题讨论】:

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


【解决方案1】:

问题是您的__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) 元组,而是产生生成器。这是由于您在 __next__ 方法中使用了 yield 造成的。 __next__ 方法应该返回值,而不是yield它们。有关详细说明和修复,请参阅this question

【讨论】:

    【解决方案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))
    

    【讨论】:

      【解决方案3】:

      问题:如何解包一个对象,因为它是一个 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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-06-07
          • 1970-01-01
          • 1970-01-01
          • 2018-11-27
          • 2022-08-11
          • 2018-02-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多