【问题标题】:python iterators, generators and in betweenpython迭代器、生成器和介于两者之间
【发布时间】:2020-06-14 18:44:21
【问题描述】:

所以我得到了用于惰性求值和生成器表达式的生成器函数,也就是生成器理解作为它的语法糖等价物。

我了解类似的课程

class Itertest1:
    def __init__(self):
        self.count = 0
        self.max_repeats = 100

    def __iter__(self):
        print("in __inter__()")
        return self

    def __next__(self):
        if self.count >= self.max_repeats:
            raise StopIteration
        self.count += 1
        print(self.count)
        return self.count

作为实现迭代器接口的一种方式,即 iter() 和 next() 在同一个类中。

但那是什么

class Itertest2:
    def __init__(self):
        self.data = list(range(100))

    def __iter__(self):
        print("in __inter__()")
        for i, dp in enumerate(self.data):
            print("idx:", i)
            yield dp

iter成员函数中使用yield语句?

我还注意到在调用 iter 成员函数时

it = Itertest2().__iter__()
batch = it.__next__()

仅在第一次调用 next() 时才会执行打印语句。 这是由于产量和迭代的这种奇怪的混合吗?我认为这很违反直觉......

【问题讨论】:

  • 在生成器中,值是延迟计算的值仅在需要时计算。
  • (1) 你应该以不同的方式命名这两个类,至少 Itertest1 和 Itertest2。 (2) Itertest2 是一个可迭代对象,它在其__iter__ 方法中创建新的独立迭代器。生成器函数返回这样一个迭代器。 Itertest1 是一个迭代器,按照惯例,它在__iter__ 中返回自身。例如。 Java 更清晰地区分可迭代和迭代器,但不太舒服。
  • 谢谢,但为什么在“__inter__()”中只在下一次调用而不是调用 __iter__() 之后才打印?
  • @CD86 因为对__iter__ 的调用只会返回您的生成器。事情是,yield 旨在简化编写迭代器的过程(除其他外),幕后发生了很多事情。因此,您没有在定义 both __iter__ __next__ 时所做的控制级别;你把它们挤在一起并用yield粘在一起。
  • __iter__ itself 是一个生成器函数,所以每次调用Itertest2.__iter__ 都会返回一个独立的迭代器,这与Itertest1 不同,Itertest1 的实例本身带有迭代状态。

标签: python iterator generator


【解决方案1】:

可以使用单独的迭代器类编写与 Itertest2 等效的内容。

class Itertest3:
    def __init__(self):
        self.data = list(range(100))

    def __iter__(self):
        return Itertest3Iterator(self.data)


class Itertest3Iterator:
    def __init__(self, data):
        self.data = enumerate(data)

    def __iter__(self):
        return self

    def __next__(self):
        print("in __inter__()")
        i, dp = next(self.state)  # Let StopIteration exception propagate
        print("idx:", i)
        return dp

将此与Itertest1 进行比较,Itertest1 的实例本身在其中携带了迭代的状态。对Itertest1.__iter__ 的每次调用都返回相同的对象(Itertest1 的实例),因此它们无法独立地迭代数据。

请注意,我将 print("in __iter__()") 放入 __next__,而不是 __iter__。正如您所观察到的,在第一次调用__next__ 之前,生成器函数中实际上没有执行。生成器函数本身创建一个生成器;它实际上并没有开始执行其中的代码。

【讨论】:

    【解决方案2】:

    在任何函数中的任何位置使用yield 语句都会将函数代码包装在(本机)生成器对象中,并将函数替换为为您提供所述生成器对象的存根。

    所以,在这里,调用__iter__ 会给你一个匿名的生成器对象,它会执行你想要的代码。

    __next__ 的主要用例是提供一种无需依赖(本机)生成器即可编写迭代器的方法。

    __iter__ 的用例是区分对象和对所述对象的迭代状态。考虑类似的代码

    c = some_iterable()
    for a in c:
        for b in c:
            # do something with a and b
    

    您不希望两个交错的迭代相互干扰。这就是为什么这样的循环会脱糖到像

    c = some_iterable()
    _iter1 = iter(c)
    try:
        while True:
            a = next(_iter1)
            _iter2 = iter(c)
            try:
                while True:
                    b = next(_iter2)
                    # do something with a and b
            except StopIteration:
                pass
     except StopIteration:
         pass
    

    通常,自定义迭代器实现一个返回 self 的存根 __iter__,因此 iter(iter(x)) 等效于 iter(x)。这在编写迭代器包装器时很重要。

    【讨论】:

    • 明白了。不过,在第一次调用相当隐蔽的 next 方法之前,不喜欢在所述成员函数中隐式延迟代码。似乎很奇怪。因此,iter 成员函数的主要用例是能够让类与纯生成器函数相比保持状态,同时比具有 iter 和 next 方法?
    猜你喜欢
    • 1970-01-01
    • 2018-02-14
    • 2018-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多