简体   繁体   English

为什么 __next__() 内部的 yield 返回生成器 object?

[英]Why does a yield from inside __next__() return generator object?

I am using yield to return the next value in the __next__() function in my class. However it does not return the next value, it returns the generator object.我正在使用yield在我的 class 的__next__() function 中返回下一个值。但是它不返回下一个值,它返回生成器 object。

I am trying to better understand iterators and yield .我试图更好地理解迭代器和yield I might be doing it in the wrong way.我可能做错了。

Have a look.看一看。

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

This returns:这将返回:

generator object __next__ at 0x032C05A0

next pretty much just calls __next__() in this case.在这种情况next几乎只是调用__next__() Calling __next__ on your object will start the generator and return it (no magic is done at this point).在你的对象上调用__next__将启动生成器并返回它(此时没有魔法完成)。


In this case, you might be able to get away with not defining __next__ at all:在这种情况下,您可能完全不定义__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

If you wanted to use __iter__ and __next__ (to define an iterator rather than simply making an iterable ), you'd probably want to do something like this:如果你想使用__iter____next__ (定义一个迭代器而不是简单地创建一个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

Let's take a look at the purpose of the __next__ method.让我们来看看__next__方法的目的。 From the docs :文档

iterator.__next__()迭代器.__next__()

Return the next item from the container.从容器中返回下一个项目。 If there are no further items, raise the StopIteration exception.如果没有其他项目,则引发 StopIteration 异常。

Now let's see what the yield statement does.现在让我们看看yield语句的作用。 Another excerpt from the docs : 文档的另一个摘录:

Using a yield expression in a function's body causes that function to be a generator在函数体中使用 yield 表达式会导致该函数成为生成器

And并且

When a generator function is called, it returns an iterator known as a generator.当一个生成器函数被调用时,它返回一个称为生成器的迭代器。

Now compare __next__ and yield : __next__ returns the next item from the container .现在比较__next__yield__next__返回容器中的下一项 But a function containing the yield keyword returns an iterator .但是包含yield关键字的函数返回一个迭代器 Consequently, using yield in a __next__ method results in an iterator that yields iterators.因此,在__next__方法中使用yield一个产生迭代器的迭代器。


If you want to use yield to make your class iterable, do it in the __iter__ method:如果您想使用yield使您的类可迭代,请在__iter__方法中进行:

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

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

The __iter__ method is supposed to return an iterator - and the yield keyword makes it do exactly that. __iter__方法应该返回一个迭代器——而yield关键字使它完全做到了这一点。


For completeness, here is how you would implement an iterator with a __next__ method.为了完整__next__ ,下面是如何使用__next__方法实现迭代器。 You have to keep track of the state of the iteration, and return the corresponding value.您必须跟踪迭代的状态,并返回相应的值。 The easiest solution is probably to increment an index every time __next__ is called:最简单的解决方案可能是每次调用__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]

As far as I can tell, generator functions are just syntactic sugar for classes with a next function.据我所知,生成器函数只是具有next函数的类的语法糖。 Example:示例:

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

In fact, I came here looking to see if there is any significant difference.其实,我来这里是想看看是否任何显著差异。 Please shout if there is.有的话请喊一声。

So, to answer the question, what you have is a class with a __next__ method that returns an object that also has a __next__ method.因此,要回答这个问题,您拥有的是一个具有 __next__ 方法的类,该类返回一个也具有 __next__ 方法的对象。 So the simplest thing to do would be to replace your yield with a return and to keep track of how far along you are, and to remember to raise a StopIteration when you reach the end of the array.因此,最简单的做法是用return替换您的yield并跟踪您走了多远,并记住在到达数组末尾时引发 StopIteration。 So something like:所以像:

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]

That's probably the simplest way to achieve what I think you're looking for.这可能是实现我认为您正在寻找的最简单方法。

OBSERVATION观察

If next() function calls __next__() method, what's happening in the following example.如果next() function 调用__next__()方法,下面的示例中会发生什么。

Code:代码:

class T:
    def __init__(self):
        self.s = 10
        
    def __iter__(self):
        for i in range(self.s):
            yield i
    
    def __next__(self):
        print('__next__ method is called.')
    
if __name__== '__main__':
    obj = T()
    k = iter(obj)
    print(next(k)) #0
    print(next(k)) #1
    print(next(k)) #2
    print(next(k)) #3
    print(next(k)) #4
    print(next(k)) #5
    print(next(k)) #6
    print(next(k)) #7
    print(next(k)) #8
    print(next(k)) #9
    print(next(k))
    print(next(k))

Terminal:终端:

C:...>python test.py
0
1
2
3
4
5
6
7
8
9
Traceback (most recent call last):
  File "test.py", line 25, in <module>
    print(next(k))
StopIteration

WHAT IS HAPPENING?怎么了?

It seams that next() function does not calling __next__ method.它接缝next() function 不调用__next__方法。 I cannot understand why python docs states that " next(iterator, default) Retrieve the next item from the iterator by calling its __next__() method."我不明白为什么python 文档指出“ next(iterator, default)通过调用它的__next__()方法从迭代器中检索下一个项目。” If someonw knows, let us help!如果有人知道,让我们帮忙!

Case: __iter__ with __next__ in custom class with yield案例: __iter__ with __next__ in custom class with yield

So, if you want to use yield (in order to create a generator) with __iter__ and __next__ methods in a custom class , do not put just the yield into the __next__ method, but use it with __iter__(self) and return self.__next__() instead return self .所以,如果你想在自定义class中使用yield (为了创建生成器)和__iter____next__方法,不要只将yield放入__next__方法,而是将它与__iter__(self)一起使用并return self.__next__()而不是return self

Code:代码:

class T:
    def __init__(self):
        self.s = 10
        
    def __iter__(self):
        return self.__next__()
    
    def __next__(self):
        for i in range(self.s):
            yield i
    
if __name__== '__main__':
    obj = T()
    for i in obj:
        print(i)

Terminal:终端:

C:\...>python test.py
0
1
2
3
4
5
6
7
8
9

C:...>

Also, you can call from __iter__ any other method instead __next__() .此外,您可以从__iter__调用任何其他方法而不是__next__()

Code:代码:

class T:
    def __init__(self):
        self.s = 10
        
    def __iter__(self):
        return self.foo()
    
    def foo(self):
        for i in range(self.s):
            yield i
    
if __name__== '__main__':
    obj = T()
    for i in obj:
        print(i)

You will have exactly the same results.您将得到完全相同的结果。

Case: yield in __iter__ method without __next__ method案例:在没有__next__方法的情况下在__iter__方法中yield

I don't think it is a good idea to use yield in __iter__ .我认为在__iter__中使用yield不是一个好主意。 Ok, it works, but I think that destroys the class API.好的,它有效,但我认为这会破坏 class API。

Case: __iter__ with __next__ in custom class without yield案例: __iter__ with __next__ in custom class without yield

Use these methods ( __iter__ and __next__ ).使用这些方法( __iter____next__ )。 In the __iter__ return self and do not forget to raise StopIteration in __next__ method.__iter__ return self中,不要忘记在__next__方法中raise StopIteration

Code:代码:

class T:
    def __init__(self):
        self.s = 10
        
    def __iter__(self):
        self.__i = -1
        return self
    
    def __next__(self):
        while  self.__i < self.s-1:
            self.__i+=1
            return self.__i
        raise StopIteration
    
if __name__== '__main__':
    obj = T()
    for i in obj:
        print(i)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM