为啥 __next__() 内部的 yield 会返回生成器对象?

Posted

技术标签:

【中文标题】为啥 __next__() 内部的 yield 会返回生成器对象?【英文标题】:Why does a yield from inside __next__() return generator object?为什么 __next__() 内部的 yield 会返回生成器对象? 【发布时间】:2016-06-20 19:02:40 【问题描述】:

我正在使用yield 在我的类中的__next__() 函数中返回下一个值。但是它不返回下一个值,而是返回生成器对象。

我试图更好地理解迭代器和yield。我可能以错误的方式做这件事。

看看吧。

class MyString:
    def __init__(self,s):
        self.s=s

    def __iter__(self):
        return self

    def __next__(self):
        for i in range(len(self.s)):
            yield(self.s[i])

r=MyString("abc")
i=iter(r)
print(next(i))

这会返回:

generator object __next__ at 0x032C05A0

【问题讨论】:

MyString 不应定义 __next____next__ 用于迭代器,而不是可迭代对象。同样,return self 不应该是 __iter__ 【参考方案1】:

next 在这种情况下几乎只是调用__next__()。在您的对象上调用 __next__ 将启动生成器并返回它(此时没有任何魔法)。


在这种情况下,您可能可以完全不定义__next__

class MyString:
    def __init__(self,s):
        self.s=s

    def __iter__(self):
        for i in range(len(self.s)):
            yield(self.s[i])
        # Or...
        # for item in self.s:
        #     yield item

如果您想使用__iter____next__(定义iterator 而不是简单地创建iterable),您可能想要这样做:

class MyString:
    def __init__(self,s):
        self.s = s
        self._ix = None

    def __iter__(self):
        return self

    def __next__(self):
        if self._ix is None:
            self._ix = 0

        try:
            item = self.s[self._ix]
        except IndexError:
            # Possibly reset `self._ix`?
            raise StopIteration
        self._ix += 1
        return item

【讨论】:

我没有看到最近的编辑。在问为什么它不见了 @MosesKoledoye -- 是的,这是我在您发表评论时纠正的原始帖子中的疏忽。我还意识到调用iter(mystring) 可能不应该重置迭代......谢谢你看着我的背影。 :-)【参考方案2】:

我们来看看__next__方法的用途。来自the docs:

迭代器.__next__()

从容器中返回下一个项目。如果没有其他项目,则引发 StopIteration 异常。

现在让我们看看yield 语句的作用。另一个摘自the docs:

在函数体中使用 yield 表达式会导致该函数 做一个发电机

当调用生成器函数时,它会返回一个迭代器,称为 生成器。

现在比较__next__yield__next__ 从容器中返回下一项。但是包含yield 关键字的函数返回一个迭代器。因此,在 __next__ 方法中使用 yield 会导致生成迭代器的迭代器。


如果您想使用yield 使您的类可迭代,请在__iter__ 方法中进行:

class MyString:
    def __init__(self, s):
        self.s = s

    def __iter__(self):
        for s in self.s:
            yield s

__iter__ 方法应该返回一个迭代器 - 而 yield 关键字正是这样做的。


为了完整起见,以下是使用__next__ 方法实现迭代器的方法。您必须跟踪迭代的状态,并返回相应的值。最简单的解决方案可能是每次调用 __next__ 时增加一个索引:

class MyString:
    def __init__(self,s):
        self.s = s
        self.index = -1

    def __iter__(self):
        return self

    def __next__(self):
        self.index += 1

        if self.index >= len(self.s):
            raise StopIteration

        return self.s[self.index]

【讨论】:

您是否不需要在 def __iter__(self) 上执行 self.index = -1 来重置迭代,然后,当稍后调用或嵌套为 for a in list... for b in list... if a!=b... @user 好吧,作为它们自己的迭代器的对象通常不会在 __iter__ 方法中重置。例如,考虑一个文件对象。你只能迭代一次。【参考方案3】:

据我所知,生成器函数只是具有 next 函数的类的语法糖。示例:

>>> def f():
    i = 0
    while True:
        i += 1
        yield i


>>> x = f()
>>> x
<generator object f at 0x0000000000659938>
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> class g(object):
    def __init__(self):
        self.i = 0

    def __next__(self):
        self.i += 1
        return self.i


>>> y = g()
>>> y
<__main__.g object at 0x000000000345D908>
>>> next(y)
1
>>> next(y)
2
>>> next(y)
3

事实上,我来这里是想看看有没有有什么显着的不同。有的话请喊。

所以,为了回答这个问题,您所拥有的是一个具有 __next__ 方法的类,该类返回一个也具有 __next__ 方法的对象。因此,最简单的做法是将您的yield 替换为return 并跟踪您的距离,并记住在到达数组末尾时引发一个StopIteration。所以像:

class MyString:
    def __init__(self,s):
        self.s=s
        self._i = -1

    def __iter__(self):
        return self

    def __next__(self):
        self._i += 1
        if self._i >= len(self.s):
            raise StopIteration
        return self.s[self._i]

这可能是实现我认为您正在寻找的最简单的方法。

【讨论】:

以上是关于为啥 __next__() 内部的 yield 会返回生成器对象?的主要内容,如果未能解决你的问题,请参考以下文章

Python3.x基础学习-生成器用法

Python___生成器

Python___生成器

2018-06-20-Python全栈开发day19-生成器函数详解

python 生成器yield 和 迭代器iter next 示例

python 生成器yield 和 迭代器iter next 示例