Python 如何编写生成器类?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42983569/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 22:22:17  来源:igfitidea点击:

How to write a generator class?

pythongeneratorfibonacci

提问by Pritam

I see lot of examples of generator functions, but I want to know how to write generators for classes. Lets say, I wanted to write Fibonacci series as a class.

我看到很多生成器函数的例子,但我想知道如何为类编写生成器。比方说,我想把斐波那契数列写成一个类。

class Fib:
    def __init__(self):
        self.a, self.b = 0, 1

    def __next__(self):
        yield self.a
        self.a, self.b = self.b, self.a+self.b

f = Fib()

for i in range(3):
    print(next(f))

Output:

输出:

<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>

Why is the value self.anot getting printed? Also, how do I write unittestfor generators?

为什么self.a不打印值?另外,我如何unittest为生成器编写代码?

回答by Aaron Hall

How to write a generator class?

如何编写生成器类?

You're almost there, writing an Iteratorclass (I show a Generator at the end of the answer), but __next__gets called every time you call the object with next, returning a generator object. Instead, to make your code work with the least changes, and the fewest lines of code, use __iter__, which makes your class instantiate an iterable(which isn't technically a generator):

你快到了,正在编写一个Iterator类(我在答案的末尾展示了一个 Generator),但是__next__每次你用 调用对象时都会被调用next,返回一个生成器对象。相反,为了使您的代码以最少的更改和最少的代码行工作,请使用__iter__,它使您的类实例化一个可迭代对象(从技术上讲,它不是一个生成器):

class Fib:
    def __init__(self):
        self.a, self.b = 0, 1
    def __iter__(self):
        while True:
            yield self.a
            self.a, self.b = self.b, self.a+self.b

When we pass an iterable to iter(), it gives us an iterator:

当我们将一个可迭代对象传递给 时iter(),它会给我们一个迭代器

>>> f = iter(Fib())
>>> for i in range(3):
...     print(next(f))
...
0
1
1

To make the class itself an iterator, it does require a __next__:

要使类本身成为迭代器,它确实需要一个__next__

class Fib:
    def __init__(self):
        self.a, self.b = 0, 1        
    def __next__(self):
        return_value = self.a
        self.a, self.b = self.b, self.a+self.b
        return return_value
    def __iter__(self):
        return self

And now, since iterjust returns the instance itself, we don't need to call it:

现在,由于iter只返回实例本身,我们不需要调用它:

>>> f = Fib()
>>> for i in range(3):
...     print(next(f))
...
0
1
1

Why is the value self.a not getting printed?

为什么值 self.a 没有被打印?

Here's your original code with my comments:

这是您的原始代码和我的评论:

class Fib:
    def __init__(self):
        self.a, self.b = 0, 1

    def __next__(self):
        yield self.a          # yield makes .__next__() return a generator!
        self.a, self.b = self.b, self.a+self.b

f = Fib()

for i in range(3):
    print(next(f))

So every time you called next(f)you got the generator object that __next__returns:

所以每次你打电话时,next(f)你都会得到__next__返回的生成器对象:

<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>

Also, how do I write unittest for generators?

另外,如何为生成器编写单元测试?

You still need to implement a send and throw method for a Generator

您仍然需要为一个对象实现一个发送和抛出方法 Generator

from collections.abc import Iterator, Generator
import unittest

class Test(unittest.TestCase):
    def test_Fib(self):
        f = Fib()
        self.assertEqual(next(f), 0)
        self.assertEqual(next(f), 1)
        self.assertEqual(next(f), 1)
        self.assertEqual(next(f), 2) #etc...
    def test_Fib_is_iterator(self):
        f = Fib()
        self.assertIsInstance(f, Iterator)
    def test_Fib_is_generator(self):
        f = Fib()
        self.assertIsInstance(f, Generator)

And now:

现在:

>>> unittest.main(exit=False)
..F
======================================================================
FAIL: test_Fib_is_generator (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<stdin>", line 7, in test_Fib_is_generator
AssertionError: <__main__.Fib object at 0x00000000031A6320> is not an instance of <class 'collections.abc.Generator'>

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)
<unittest.main.TestProgram object at 0x0000000002CAC780>

So let's implement a generator object, and leverage the Generatorabstract base class from the collections module (see the source for its implementation), which means we only need to implement sendand throw- giving us close, __iter__(returns self), and __next__(same as .send(None)) for free (see the Python data model on coroutines):

因此,让我们实现一个生成器对象,并利用Generatorcollections 模块中的抽象基类(请参阅其实现的源代码),这意味着我们只需要免费实现sendthrow- 给我们close__iter__(返回自身)和__next__(与 相同.send(None)) (请参阅关于协程Python 数据模型):

class Fib(Generator):
    def __init__(self):
        self.a, self.b = 0, 1        
    def send(self, ignored_arg):
        return_value = self.a
        self.a, self.b = self.b, self.a+self.b
        return return_value
    def throw(self, type=None, value=None, traceback=None):
        raise StopIteration

and using the same tests above:

并使用上述相同的测试:

>>> unittest.main(exit=False)
...
----------------------------------------------------------------------
Ran 3 tests in 0.002s

OK
<unittest.main.TestProgram object at 0x00000000031F7CC0>

Python 2

蟒蛇 2

The ABC Generatoris only in Python 3. To do this without Generator, we need to write at least close, __iter__, and __next__in addition to the methods we defined above.

美国广播公司Generator仅在Python 3.要做到这一点没有Generator,我们需要至少写close__iter__以及__next__除了我们上面定义的方法。

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1        
    def send(self, ignored_arg):
        return_value = self.a
        self.a, self.b = self.b, self.a+self.b
        return return_value
    def throw(self, type=None, value=None, traceback=None):
        raise StopIteration
    def __iter__(self):
        return self
    def next(self):
        return self.send(None)
    def close(self):
        """Raise GeneratorExit inside generator.
        """
        try:
            self.throw(GeneratorExit)
        except (GeneratorExit, StopIteration):
            pass
        else:
            raise RuntimeError("generator ignored GeneratorExit")

Note that I copied closedirectly from the Python 3 standard library, without modification.

请注意,我close直接从 Python 3标准库中复制而来,没有进行任何修改。

回答by chepner

__next__should returnan item, not yield it.

__next__应该返回一个项目,而不是产生它。

You can either write the following, in which Fib.__iter__returns a suitable iterator:

您可以编写以下内容,其中Fib.__iter__返回一个合适的迭代器:

class Fib:
    def __init__(self, n):
        self.n = n
        self.a, self.b = 0, 1

    def __iter__(self):
        for i in range(self.n):
            yield self.a
            self.a, self.b = self.b, self.a+self.b

f = Fib(10)

for i in f:
    print i

or make each instance itself an iterator by defining __next__.

或者通过定义__next__.

class Fib:
    def __init__(self):
        self.a, self.b = 0, 1

    def __iter__(self):
        return self

    def __next__(self):
        x = self.a
        self.a, self.b = self.b, self.a + self.b
        return x

f = Fib()

for i in range(10):
    print next(f)

回答by Sarath Sadasivan Pillai

Do not use yieldin __next__function and implement nextalso for compatibility with python2.7+

不要yield__next__函数中使用,next也为了与python2.7+兼容而实现

Code

代码

class Fib:
    def __init__(self):
        self.a, self.b = 0, 1
    def __next__(self):
        a = self.a
        self.a, self.b = self.b, self.a+self.b
        return a
    def next(self):
        return self.__next__()

回答by martineau

If you give the class an __iter__()method implemented as a generator, it will automatically return a generator object when called, so thatobject's __iter__and __next__methods will be the ones used.

如果你给一个类__iter__()的方法作为发电机来实现,调用时它会自动返回一个发电机对象,因此对象__iter____next__方法将是所使用的那些。

Here's what I mean:

这就是我的意思:

class Fib:
    def __init__(self):
        self.a, self.b = 0, 1

    def __iter__(self):
        while True:
            value, self.a, self.b = self.a, self.b, self.a+self.b
            yield value

f = Fib()

for i, value in enumerate(f, 1):
    print(value)
    if i > 5:
        break

Output:

输出:

0
1
1
2
3
5

回答by Jared Deckard

Using yieldin a method makes that method a generator, and calling that method returns a generator iterator. next()expects a generator iterator which implements __next__()and returns an item. That is why yielding in __next__()causes your generator class to output generator iterators when next()is called on it.

yield在方法中使用使该方法成为生成器,调用该方法返回生成器迭代器next()需要一个生成器迭代器来实现__next__()returns 一个项目。这就是为什么yielding in__next__()会导致您的生成器类在next()被调用时输出生成器迭代器。

https://docs.python.org/3/glossary.html#term-generator

https://docs.python.org/3/glossary.html#term-generator

When implementing an interface, you need to define methods and map them to your class implementation. In this case the __next__()method needs to call through to the generator iterator.

在实现接口时,您需要定义方法并将它们映射到您的类实现。在这种情况下,该__next__()方法需要调用生成器迭代器。

class Fib:
    def __init__(self):
        self.a, self.b = 0, 1
        self.generator_iterator = self.generator()

    def __next__(self):
        return next(self.generator_iterator)

    def generator(self):
        while True:
            yield self.a
            self.a, self.b = self.b, self.a+self.b

f = Fib()

for i in range(3):
    print(next(f))
# 0
# 1
# 1