生成器


一、yield關鍵字

yield的英文單詞意思是生產,在函數中但凡出現yield關鍵字,再調用函數,就不會繼續執行函數體代碼,而是會返回一個值。

52生成器-停止.jpg?x-oss-process=style/watermark

def func():
    print(1)
    yield
    print(2)
    yield


g = func()
print(g)
<generator object func at 0x10ddb6b48>

生成器的本質就是迭代器,同時也並不僅僅是迭代器,不過迭代器之外的用途實在是不多,所以我們可以大聲地說:生成器提供了非常方便的自定義迭代器的途徑。並且從Python 2.5+開始,[PEP 342:通過增強生成器實現協同程序]的實現為生成器加入了更多的特性,這意味着生成器還可以完成更多的工作。這部分我們會在稍后的部分介紹。

def func():
    print('from func 1')
    yield 'a'
    print('from func 2')
    yield 'b'


g = func()
print(F"g.__iter__ == g: {g.__iter__() == g}")

res1 = g.__next__()
print(f"res1: {res1}")

res2 = next(g)
print(f"res2: {res2}")

# next(g)  # StopIteration
g.__iter__ == g: True
from func 1
res1: a
from func 2
res2: b
def func():
    print('from func 1')
    yield 'a'
    print('from func 2')
    yield 'b'


g = func()
for i in g:
    print(i)

print(f"list(func()): {list(func())}")
from func 1
a
from func 2
b
from func 1
from func 2
list(func()): ['a', 'b']

1.1 yield+return??

既然生成器函數也是函數,那么它可以使用return輸出返回值嗎?

親,既然你都選擇自定義一個函數作為生成器,你還return干啥?如果這是在Python2中,Python解釋器會贈送給你一個異常,但是在Python3中,他也不管你這種傻瓜行為了。

def i_wanna_return():
    yield 'a'
    yield 'b'
    return None
    yield 'c'


for i in i_wanna_return():
    print(i)
a
b

1.2 迭代器套迭代器

如果我需要在生成器的迭代過程中接入另一個生成器的迭代怎么辦?寫成下面這樣好傻好天真。並且你這樣做的意圖是什么???

def sub_generator():
    yield 1
    yield 2
    for i in range(3):
        yield i


for i in sub_generator():
    print(i)
1
2
0
1
2
def sub_generator():
    yield 1
    yield 2
    yield from range(3)


for i in sub_generator():
    print(i)
1
2
0
1
2

二、協同程序

協同程序(協程)一般來說是指這樣的函數:

  • 彼此間有不同的局部變量、指令指針,但仍共享全局變量;
  • 可以方便地掛起、恢復,並且有多個入口點和出口點;
  • 多個協同程序間表現為協作運行,如A的運行過程中需要B的結果才能繼續執行。

協程的特點決定了同一時刻只能有一個協同程序正在運行(忽略多線程的情況)。得益於此,協程間可以直接傳遞對象而不需要考慮資源鎖、或是直接喚醒其他協程而不需要主動休眠,就像是內置了鎖的線程。在符合協程特點的應用場景,使用協程無疑比使用線程要更方便。

從另一方面說,協程無法並發其實也將它的應用場景限制在了一個很狹窄的范圍,這個特點使得協程更多的被拿來與常規函數進行比較,而不是與線程。當然,線程比協程復雜許多,功能也更強大,所以我建議大家牢牢地掌握線程即可,是不是聽了一臉懵逼,那么就別管他了,因為並發編程你會重新學習他。因此這一節里我也就不列舉關於協程的例子了,以下介紹的方法了解即可。

由於Python2.5+對生成器的增強實現了協程的其他特點,在這個版本中,生成器加入了如下方法:

2.1 send(value):

send是除next外另一個恢復生成器的方法。Python2.5+中,yield語句變成了yield表達式,這意味着yield現在可以有一個值,而這個值就是在生成器的send方法被調用從而恢復執行時,調用send方法的參數。

def h():
    print('--start--')
    first = yield 5  # 等待接收 Fighting! 值
    print('1', first)
    second = yield 12  # 等待接收 hahaha! 值
    print('2', second)
    yield 13
    print('--end--')


g = h()
first = next(g)  # m 獲取了yield 5 的參數值 5
# (yield 5)表達式被賦予了'Fighting!',  d 獲取了yield 12 的參數值12
second = g.send('Fighting!')
third = g.send('hahaha!')  # (yield 12)表達式被賦予了'hahaha!'
print(f'--over--')
print(f"first:{first}, second:{second}, third:{third}")
--start--
1 Fighting!
2 hahaha!
--over--
first:5, second:12, third:13
  • 調用send傳入非None值前,生成器必須處於掛起狀態,否則將拋出異常。不過,未啟動的生成器仍可以使用None作為參數調用send。
  • 如果使用next恢復生成器,yield表達式的值將是None。

2.2 close()

這個方法用於關閉生成器。對關閉的生成器后再次調用next或send將拋出StopIteration異常。

def repeater():
    n = 0
    while True:
        n = (yield n)


r = repeater()
r.close()
print(next(r))  # StopIteration

2.3 throw(type, value=None, traceback=None)

中斷Generator是一個非常靈活的技巧,可以通過throw拋出一個GeneratorExit異常來終止Generator。Close()方法作用是一樣的,其實內部它是調用了throw(GeneratorExit)的。我們看close的源代碼:

def close(self):
    try:
        self.throw(GeneratorExit)
    except (GeneratorExit, StopIteration):
        pass 
    else:
        raise RuntimeError("generator ignored GeneratorExit") # Other exceptions are not caught

三、自定義range()方法

def my_range(start, stop, step=1):
    while start < stop:
        yield start
        start += 1


g = my_range(0, 3)
print(f"list(g): {list(g)}")
list(g): [0, 1, 2]

四、總結

yield:

  1. 提供一種自定義迭代器的方式
  2. yield可以暫停住函數,並提供當前的返回值

yield和return:

  1. 相同點:兩者都是在函數內部使用,都可以返回值,並且返回值沒有類型和個數的限制
  2. 不同點:return只能返回一次之;yield可以返回多次值

五、生成器表達式

  • 把列表推導式的[]換成()就是生成器表達式
  • 優點:省內存,一次只產生一個值在內存中
t = (i for i in range(10))
print(t)
print(f"next(t): {next(t)}")
<generator object <genexpr> at 0x1101c4888>
next(t): 0

5.1 生成器表達式和列表推導式

列表推導式相當於直接給你一筐蛋,而生成器表達式相當於給你一只老母雞。

52生成器-土雞.jpg?x-oss-process=style/watermark

# 生成器表達式
with open('52.txt', 'r', encoding='utf8') as f:
    nums = [len(line) for line in f]

print(max(nums))
1
# 列表推導式
with open('52.txt','r',encoding='utf8') as f:
    nums = (len(line) for line in f)

print(max(nums)) # ValueError: I/O operation on closed file.

六、自定義range方法(復雜版本)

def range(*args, **kwargs):
    if not kwargs:
        if len(args) == 1:
            count = 0
            while count < args[0]:
                yield count
                count += 1
        if len(args) == 2:
            start, stop = args
            while start < stop:
                yield start
                start += 1
        if len(args) == 3:
            start, stop, step = args
            while start < stop:
                yield start
                start += step

    else:
        step = 1

        if len(args) == 1:
            start = args[0]
        if len(args) == 2:
            start, stop = args

        for k, v in kwargs.items():
            if k not in ['start', 'step', 'stop']:
                raise ('參數名錯誤')

            if k == 'start':
                start = v
            elif k == 'stop':
                stop = v
            elif k == 'step':
                step = v

        while start < stop:
            yield start
            start += step


for i in range(3):
    print(i)
print('*' * 50)
for i in range(99, 101):
    print(i)
print('*' * 50)
for i in range(1, 10, 3):
    print(i)
print('*' * 50)
for i in range(1, step=2, stop=5):
    print(i)
print('*' * 50)
for i in range(1, 10, step=2):
    print(i)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM