首頁 >後端開發 >Python教學 >一文詳解python生成器

一文詳解python生成器

WBOY
WBOY轉載
2022-06-09 16:02:172691瀏覽

本篇文章為大家帶來了關於python的相關知識,其中主要介紹了關於生成器的相關問題,包括了生成器的概念、生成器的執行過程、yield以及生成器方法等內容,下面一起來看一下,希望對大家有幫助。

一文詳解python生成器

推薦學習:python影片教學

#本篇文章為大家帶來了關於Python的相關知識,其中主要介紹了關於生成器的相關問題,包括了生成器的概念、生成器的執行過程、yield以及生成器方法等內容,下面一起來看一下,希望對大家有幫助。

1. 生成器概念

生成器(英文:generator)是一個非常迷人的東西,也常被認為是 Python 的高階程式設計技能。不過,我依然很

樂意在這裡跟讀者——儘管你可能是個初學者——探討這個話題,因為我相信各位大佬看本教程的目的,絕非僅僅將自己限制於初學者水平,一定有一顆不羈的心——要成為Python 高手。那麼,開始了解生成器吧。

還記得上節的「迭代器」嗎?生成器和迭代器有著一定的淵源關係。生成器必須是可迭代的,誠然它又不僅僅是

迭代器,但除此之外,又沒有太多的別的用途,所以,我們可以把它理解為非常方便的自定義迭代器。

2. 簡單的生成器

>>> my_generator = (x*x for x in range(4))

這是不是跟列表解析很類似呢?仔細觀察,它不是列表,如果這樣的得到的才是列表:

>>> my_list = [x*x for x in range(4)]

以上兩的區別在於是 [] 還是 () ,雖然是細小的差別,但是結果完全不一樣。

>>> dir(my_generator)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__',
'__iter__',
'__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running',
'next',
'send', 'throw']

為了容易觀察,我將上述結果進行了重新排版。是不是發現了在迭代器中必有的方法 __inter__() 和 next() ,這表示它是迭代器。如果是迭代器,就可以用for 迴圈來依序讀出其值

>>> for i in my_generator:
... print i
...
0
1
4
9
>>> for i in my_generator:
... print i
...

當第一遍循環的時候,將my_generator 裡面的值依次讀出並列印,但是,當再讀一次的時候,就發現沒有任何結果。這種特性也正是迭代器所具有的。

如果對那個列表,就不一樣了:

>>> for i in my_list:
... print i
...
0
1
4
9
>>> for i in my_list:
... print i
...
0
1
4
9

難道產生器就是把列表解析中的 [] 換成 () 就行了嗎?這只是生成器的一種表現形式和使用方法罷了,仿照

列表解析式的命名,可以稱之為「生成器解析式」(或:生成器推導式、生成器表達式)。

生成器解析式是有很多用途的,在不少地方替代列表,是一個不錯的選擇。特別是針對大量值的時候,如上節所說的,列表佔內存較多,迭代器(生成器是迭代器)的優勢就在於少佔內存,因此無需將生成器(或者說是迭代器)實例化為一個列表,直接對其進行操作,方顯示出其迭代的優勢。例如:

>>> sum(i*i for i in range(10))
285

注意觀察上面的 sum() 運算,不要以為裡面少了一個括號,就是這麼寫。是不是很迷人呢?如果列表,你

不得不:

>>> sum([i*i for i in range(10)])
285

               透過產生器解析式而產生的產生器,掩蓋了產生器的一些細節,並且適用領域也有限。下面就要剖析生成器的內部,深入理解這個魔法工具。

3. 定義和執行過程

yield 這個字在漢語中有「生產、生產」之意,在Python 中,它作為一個關鍵字(你在變數、函數、類的名稱中

就不能用這個了),是生成器的標誌。

>>> def g():
... yield 0
... yield 1
... yield 2
...
>>> g
<function g at 0xb71f3b8c>

建立了一個非常簡單的函數,跟以往看到的函數唯一不同的地方是用了三個 yield 語句。然後進行下面的操作:

>>> ge = g()
>>> ge
<generator object g at 0xb7200edc>
>>> type(ge)
<type &#39;generator&#39;>

上面建立的函數傳回值是一個生成器(generator)類型的物件。

>>> dir(ge)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw']

在這裡看到了 __iter__() 和 next() ,說明它是迭代器。既然如此,當然可以:

>>> ge.next()
0
>>> ge.next()
1
>>> ge.next()
2
>>> ge.next()
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
StopIteration

從這個簡單例子中可以看出,那個含有 yield 關鍵字的函數回傳值是一個生成器類型的對象,這個生成器物件就是迭代器。

我們把含有 yield 語句的函數稱為生成器。生成器是一種用普通函數語法定義的迭代器。透過上面的例子可以看出,這個生成器(也是迭代器),在定義過程中並沒有像上節迭代器那樣寫__inter__() 和next() ,而是只要用了yield 語句,那個普通函數就神奇般地成為了生成器,也就具備了迭代器的功能特性。

yield 語句的作用,就是在呼叫的時候傳回對應的值。詳細剖析一下上面的運行過程:

1. ge = g() :除了傳回生成器之外,什麼也沒有操作,任何值也沒有被回傳。

2. ge.next() :直到這時候,生成器才開始執行,遇到了第一個 yield 語句,將值返回,並暫停執行(有的稱之

为挂起)。

3. ge.next() :从上次暂停的位置开始,继续向下执行,遇到 yield 语句,将值返回,又暂停。

4. gen.next() :重复上面的操作。

5. gene.next() :从上面的挂起位置开始,但是后面没有可执行的了,于是 next() 发出异常。

从上面的执行过程中,发现 yield 除了作为生成器的标志之外,还有一个功能就是返回值。那么它跟 return 这个返回值有什么区别呢?

4. yield

为了弄清楚 yield 和 return 的区别,我写了两个函数来掩饰:

>>> def r_return(n):
... print "You taked me."
... while n > 0:
... print "before return"
... return n
... n -= 1
... print "after return"
...
>>> rr = r_return(3)
You taked me.
before return
>>> rr
3

从函数被调用的过程可以清晰看出, rr = r_return(3) ,函数体内的语句就开始执行了,遇到 return,将值返

回,然后就结束函数体内的执行。所以 return 后面的语句根本没有执行。这是 return 的特点

下面将 return 改为 yield:

>>> def y_yield(n):
... print "You taked me."
... while n > 0:
...     print "before yield"
...     yield n
...     n -= 1
...     print "after yield"
...
>>> yy = y_yield(3) #没有执行函数体内语句
>>> yy.next() #开始执行
You taked me.
before yield
3 #遇到 yield,返回值,并暂停
>>> yy.next() #从上次暂停位置开始继续执行
after yield
before yield
2 #又遇到 yield,返回值,并暂停
>>> yy.next() #重复上述过程
after yield
before yield
1
>>> yy.next()
after yield #没有满足条件的值,抛出异常
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
StopIteration

结合注释和前面对执行过程的分析,读者一定能理解 yield 的特点了,也深知与 return 的区别了。

一般的函数,都是止于 return。作为生成器的函数,由于有了 yield,则会遇到它挂起,如果还有 return,遇到它就直接抛出 SoptIteration 异常而中止迭代。

#!/usr/bin/env Python
# coding=utf-8

def fibs(max):
    """
    斐波那契数列的生成器
    """
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
if __name__ == "__main__":
    f = fibs(10)
    for i in f:
        print i ,

运行结果如下:

$ python 21501.py
1 1 2 3 5 8 13 21 34 55

用生成器方式实现的斐波那契数列是不是跟以前的有所不同了呢?大家可以将本教程中已经演示过的斐波那契数列实现方式做一下对比,体会各种方法的差异。

经过上面的各种例子,已经明确,一个函数中,只要包含了 yield 语句,它就是生成器,也是迭代器。这种方式显然比前面写迭代器的类要简便多了。但,并不意味着上节的就被抛弃。是生成器还是迭代器,都是根据具体的使用情景而定。

5. 生成器方法

在 python2.5 以后,生成器有了一个新特征,就是在开始运行后能够为生成器提供新的值。这就好似生成器

和“外界”之间进行数据交流。

>>> def repeater(n):
... while True:
...     n = (yield n)
...
>>> r = repeater(4)
>>> r.next()
4
>>> r.send("hello")
'hello

当执行到 r.next() 的时候,生成器开始执行,在内部遇到了 yield n 挂起。注意在生成器函数中, n = (yield

n) 中的 yield n 是一个表达式,并将结果赋值给 n,虽然不严格要求它必须用圆括号包裹,但是一般情况都这

么做,请大家也追随这个习惯。

当执行 r.send("hello") 的时候,原来已经被挂起的生成器(函数)又被唤醒,开始执行 n = (yield n) ,也就是

讲 send() 方法发送的值返回。这就是在运行后能够为生成器提供值的含义。

如果接下来再执行 r.next() 会怎样?

>>> r.next()

什么也没有,其实就是返回了 None。按照前面的叙述,读者可以看到,这次执行 r.next() ,由于没有传入任何值,yield 返回的就只能是 None.

还要注意,send() 方法必须在生成器运行后并挂起才能使用,也就是 yield 至少被执行一次。如果不是这样:

>>> s = repeater(5)
>>> s.send("how")
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator

就报错了。但是,可将参数设为 None:

>>> s.send(None)
5

这是返回的是调用函数的时传入的值。

此外,还有两个方法:close() 和 throw()

• throw(type, value=None, traceback=None):用于在生成器内部(生成器的当前挂起处,或未启动时在定

义处)抛出一个异常(在 yield 表达式中)。

• close():调用时不用参数,用于关闭生成器。

推荐学习:python视频教程

以上是一文詳解python生成器的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除