<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Python中神奇的迭代器和生成器

          共 13929字,需瀏覽 28分鐘

           ·

          2021-05-12 12:49

          給定字符串 st ,判斷 s 是否為 t 的子序列。

          字符串的一個(gè)子序列是原始字符串刪除一些(也可以不刪除)字符而不改變剩余字符相對(duì)位置形成的新字符串。(例如,"ace"是"abcde"的一個(gè)子序列,而"aec"不是)。

          來源:力扣(LeetCode)

          鏈接:https://leetcode-cn.com/problems/is-subsequence

          要解決這個(gè)問題,常規(guī)算法是貪心算法。我們維護(hù)兩個(gè)指針指向兩個(gè)字符串的開頭,然后對(duì)第二個(gè)字符串一路掃過去,如果某個(gè)字符和第一個(gè)指針指的一樣,那么就把第一個(gè)指針前進(jìn)一步。第一個(gè)指針移出第一個(gè)序列最后一個(gè)元素的時(shí)候,返回 True,否則返回 False。

          不過,這個(gè)算法正常寫的話,寫下來怎么也得八行左右:

          def is_subsequence(s: str, t: str) -> bool:
              n, m = len(s), len(t)
              i = j = 0
              while i < n and j < m:
                  if s[i] == t[j]:
                      i += 1
                  j += 1
              return i == n

          print(is_subsequence("ace""abcde"))
          print(is_subsequence("aec""abcde"))

          但如果我們使用迭代器和生成器,代碼量將大幅度減少:

          def is_subsequence(s: str, t: str) -> bool:
              t = iter(t)
              return all(i in t for i in s)
           
          print(is_subsequence("ace""abcde"))
          print(is_subsequence("aec""abcde"))

          而且運(yùn)行結(jié)果正確與上面一樣,都是:

          True
          False

          但如果你對(duì)python的生成器運(yùn)行機(jī)制不太了解,一定會(huì)看的一臉懵逼。

          不過不用擔(dān)心,我今天分享的主題便是python的迭代器和生成器剖析

          本文目錄


          • 迭代器和可迭代對(duì)象

          • 列表生成式與列表生成器

          • 函數(shù)生成器(generator)

          • 迭代器和生成器的關(guān)系

          • 利用生成器判斷子序列詳解

          • 總結(jié)


          迭代器和可迭代對(duì)象

          在 Python 中一切皆對(duì)象,對(duì)象的抽象就是類,而對(duì)象的集合就是容器。

          列表(list: [0, 1, 2]),元組(tuple: (0, 1, 2)),字典(dict: {0:0, 1:1, 2:2}),集合(set: set([0, 1, 2]))都是容器。對(duì)于容器,可以認(rèn)為是多個(gè)元素在一起的單元;而不同容器的區(qū)別,正是在于內(nèi)部數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)方法。

          所有的容器都是可迭代對(duì)象(iterable):

          from collections.abc import Iterable
          params = [
              1234,
              '1234',
              [1234],
              set([1234]),
              {11223344},
              (1234)
          ]

          for param in params:
              print(f'{param}是否為可迭代對(duì)象? ', isinstance(param, Iterable))

          運(yùn)行結(jié)果:

          1234是否為可迭代對(duì)象?  False
          1234是否為可迭代對(duì)象?  True
          [1, 2, 3, 4]是否為可迭代對(duì)象?  True
          {1, 2, 3, 4}是否為可迭代對(duì)象?  True
          {1: 1, 2: 2, 3: 3, 4: 4}是否為可迭代對(duì)象?  True
          (1, 2, 3, 4)是否為可迭代對(duì)象?  True

          可以看到所有的集合容器都是可迭代對(duì)象(iterable),字符串也是可迭代對(duì)象,唯有單個(gè)數(shù)字不是可迭代對(duì)象。

          而可迭代對(duì)象,可以通過 iter() 函數(shù)返回一個(gè)迭代器,當(dāng)然迭代器本身也屬于可迭代對(duì)象:

          from collections.abc import Iterable, Iterator
          params = [
              '1234',
              [1234],
              set([1234]),
              {11223344},
              (1234)
          ]

          for param in params:
              param = iter(param)
              print("----------")
              print(f'{param}是否為可迭代對(duì)象? ', isinstance(param, Iterable))
              print(f'{param}是否為迭代器對(duì)象? ', isinstance(param, Iterator))

          運(yùn)行結(jié)果:

          這意味著迭代器本身也可以獲取它自己的迭代器,例如:

          for i in iter(l):
              print(i, end=",")
          print()
          for i in iter(iter(l)):
              print(i, end=",")

          運(yùn)行結(jié)果:

          1,2,3,4,
          1,2,3,4,

          迭代器(iterator)提供了一個(gè) __next__的方法。調(diào)用這個(gè)方法后,你要么得到這個(gè)容器的下一個(gè)對(duì)象,要么得到一個(gè) StopIteration 的錯(cuò)誤:

          l = [1234]
          l = iter(l)
          while True:
              print(l.__next__())

          結(jié)果:

          1
          2
          3
          4
          ---------------------------------------------------------------------------
          StopIteration                             Traceback (most recent call last)
          <ipython-input-16-e106f3a7bd73> in <module>()
                2 l = iter(l)
                3 while True:
          ----> 4     print(l.__next__())

          StopIteration: 

          當(dāng)然上面的l.__next__()應(yīng)該改寫成next(l),next()方法的本質(zhì)就是調(diào)用目標(biāo)對(duì)象的__next__()方法。

          實(shí)際上for循環(huán):

          l = [1234]
          for i in l:
              print(i)

          的本質(zhì)等價(jià)于:

          l = [1234]
          l_iter = iter(l)
          while True:
              try:
                  i = next(l_iter)
              except StopIteration:
                  break
              print(i)

          for in 語(yǔ)句將這個(gè)過程隱式化了。


          列表生成式與列表生成器

          列表生成式即List Comprehensions,是Python內(nèi)置的非常簡(jiǎn)單卻強(qiáng)大的可以用來創(chuàng)建list的生成式。

          print([x * x for x in range(111)])
          print([x * x for x in range(111if x % 2 == 0])

          ##還可以使用兩層循環(huán),可以生成全排列:
          print([m + n for m in 'ABC' for n in 'XYZ'])
          print([str(x)+str(y) for x in range(1,6for y in range(11,16)])

          ##for循環(huán)其實(shí)可以同時(shí)使用兩個(gè)甚至多個(gè)變量,比如dict的items()可以同時(shí)迭代key和value:
          d = {'x''A''y''B''z''C' }
          print([k + '=' + v for k, v in d.items()])

          結(jié)果:

          [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
          [4, 16, 36, 64, 100]
          ['AX''AY''AZ''BX''BY''BZ''CX''CY''CZ']
          ['111''112''113''114''115''211''212''213''214''215''311''312''313''314''315''411''412''413''414''415''511''512''513''514''515']
          ['x=A''y=B''z=C']

          通過列表生成式,我們可以直接創(chuàng)建一個(gè)列表。但是,受到內(nèi)存限制,列表容量肯定是有限的。而且,創(chuàng)建一個(gè)包含100萬個(gè)元素的列表,不僅占用很大的存儲(chǔ)空間,如果我們僅僅需要訪問前面幾個(gè)元素,那后面絕大多數(shù)元素占用的空間都白白浪費(fèi)了。

          所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環(huán)的過程中不斷推算出后續(xù)的元素呢?這樣就不必創(chuàng)建完整的list,從而節(jié)省大量的空間。在Python中,這種一邊循環(huán)一邊計(jì)算的機(jī)制,稱為生成器:generator。

          只要把一個(gè)列表生成式的[]改成(),就創(chuàng)建了一個(gè)generator:

          g = (x * x for x in range(10))

          generator保存的是算法,每次調(diào)用next(g),就計(jì)算出g的下一個(gè)元素的值,直到計(jì)算到最后一個(gè)元素,沒有更多的元素時(shí),拋出StopIteration的錯(cuò)誤。

          用一個(gè)示例,感受一下生成器相對(duì)生成式的優(yōu)勢(shì),首先創(chuàng)建一個(gè)查看當(dāng)前內(nèi)存情況的方法:

          import os
          import psutil

          ## 顯示當(dāng)前 python 程序占用的內(nèi)存大小
          def show_memory_info(hint):
              pid = os.getpid()
              p = psutil.Process(pid)

              info = p.memory_full_info()
              memory = info.uss / 1024. / 1024
              print(f'{hint}內(nèi)存使用: {memory} MB')

          測(cè)試一下列表生成式

          def test_iterator():
              show_memory_info('initing iterator')
              list_1 = [i for i in range(100000000)]
              show_memory_info('after iterator initiated')
              print(sum(list_1))
              show_memory_info('after sum called')

          %time test_iterator()

          結(jié)果:

          initing iterator內(nèi)存使用: 48.69140625 MB
          after iterator initiated內(nèi)存使用: 3936.2890625 MB
          4999999950000000
          after sum called內(nèi)存使用: 3936.29296875 MB
          Wall time: 9.39 s

          測(cè)試一下列表生成器

          def test_generator():
              show_memory_info('initing generator')
              list_2 = (i for i in range(100000000))
              show_memory_info('after generator initiated')
              print(sum(list_2))
              show_memory_info('after sum called')

          %time test_generator()

          結(jié)果:

          initing generator內(nèi)存使用: 48.8515625 MB
          after generator initiated內(nèi)存使用: 48.85546875 MB
          4999999950000000
          after sum called內(nèi)存使用: 49.11328125 MB
          Wall time: 7.95 s

          聲明一個(gè)迭代器很簡(jiǎn)單,[i for i in range(100000000)]就可以生成一個(gè)包含一億元素的列表。每個(gè)元素在生成后都會(huì)保存到內(nèi)存中,你通過上面的代碼可以看到,它們占用了巨量的內(nèi)存,內(nèi)存不夠的話就會(huì)出現(xiàn) OOM 錯(cuò)誤。

          不過,我們并不需要在內(nèi)存中同時(shí)保存這么多東西,比如對(duì)元素求和,我們只需要知道每個(gè)元素在相加的那一刻是多少就行了,用完就可以扔掉了。

          函數(shù)生成器(generator)

          如果推算的算法比較復(fù)雜,用類似列表生成式的for循環(huán)無法實(shí)現(xiàn)的時(shí)候,還可以用函數(shù)來實(shí)現(xiàn)。

          比如,著名的斐波拉契數(shù)列(Fibonacci),除第一個(gè)和第二個(gè)數(shù)外,任意一個(gè)數(shù)都可由前兩個(gè)數(shù)相加得到:

          1, 1, 2, 3, 5, 8, 13, 21, 34, ...

          斐波拉契數(shù)列用列表生成式寫不出來,但是用函數(shù)把它打印出來卻很容易:

          def fib(max):
              n, a, b = 001
              while n < max:
                  print(b)
                  a, b = b, a + b
                  n = n + 1

          fib(6)

          打印結(jié)果:

          1
          1
          2
          3
          5
          8

          上面的函數(shù)和生成器(generator)僅一步之遙,只要把print(b)改為yield b,fib函數(shù)就會(huì)變成生成器(generator):

          def fib(max):
              n, a, b = 001
              while n < max:
                  yield b
                  a, b = b, a + b
                  n = n + 1

          這就是除了列表生成器以外定義generator的另一種方法。

          如果一個(gè)函數(shù)定義中包含yield關(guān)鍵字,那么這個(gè)函數(shù)就不再是一個(gè)普通函數(shù),而是一個(gè)generator:

          fib(6)

          結(jié)果:

          <generator object fib at 0x0000000005F04A98>

          在前面的列表生成器中我已經(jīng)講過,對(duì)于生成器可以使用for循環(huán)進(jìn)行遍歷:

          for i in fib(6):
              print(i)

          打印結(jié)果:

          1
          1
          2
          3
          5
          8

          這里,最難理解的就是generator和函數(shù)的執(zhí)行流程不一樣。函數(shù)是順序執(zhí)行,遇到return語(yǔ)句或者最后一行函數(shù)語(yǔ)句就返回。而變成generator的函數(shù),在每次調(diào)用next()的時(shí)候執(zhí)行,遇到y(tǒng)ield語(yǔ)句返回,再次執(zhí)行時(shí)從上次返回的yield語(yǔ)句處繼續(xù)執(zhí)行。

          舉個(gè)簡(jiǎn)單的例子,定義一個(gè)generator,依次返回?cái)?shù)字1,3,5:

          def odd():
              print('step 1')
              yield 1
              print('step 2')
              yield(3)
              print('step 3')
              yield(5)

          調(diào)用該generator時(shí),首先要生成一個(gè)generator對(duì)象,然后用next()函數(shù)不斷獲得下一個(gè)返回值:

          o = odd()
          while True:
              print(next(o))

          結(jié)果:

          step 1
          1
          step 2
          3
          step 3
          5
          ---------------------------------------------------------------------------
          StopIteration                             Traceback (most recent call last)
          <ipython-input-7-554c5fb505f8> in <module>()
                1 o = odd()
                2 while True:
          ----> 3     print(next(o))

          StopIteration: 

          可以看到,odd不是普通函數(shù),而是generator,在執(zhí)行過程中,遇到y(tǒng)ield就中斷,下次又繼續(xù)執(zhí)行。執(zhí)行3次yield后,已經(jīng)沒有yield可以執(zhí)行了,所以,第4次調(diào)用next()就拋出StopIteration異常。

          對(duì)于函數(shù)生成器(generator)來說,遇到return語(yǔ)句就是結(jié)束generator的指令(函數(shù)體最后一行語(yǔ)句其實(shí)隱式執(zhí)行了return None),for循環(huán)隨之結(jié)束。

          迭代器和生成器的關(guān)系

          其實(shí)生成器就是一種特殊的迭代器,而迭代器包括了生成器并不等價(jià)于生成器,它們都可以通過next()方法不斷的獲取下一個(gè)對(duì)象,都具備記憶已經(jīng)讀取的位置的特點(diǎn)。

          例如:

          l = [1234]
          l_iter = iter(l)

          完成可以理解為產(chǎn)生了一個(gè)列表生成器:

          l = [1234]
          l_iter = (i for i in l)

          也可以理解成產(chǎn)生了一個(gè)函數(shù)生成器:

          l = [1234]

          def func_generator(l):
              for i in l:
                  yield i

          l_iter = func_generator(l)

          利用生成器判斷子序列詳解

          有了前面的基礎(chǔ)知識(shí),相信文章開頭的代碼還稍微有點(diǎn)眉目了。現(xiàn)在我們?cè)倩氐轿恼麻_頭的代碼,詳細(xì)分析一下:

          def is_subsequence(s: str, t: str) -> bool:
              t = iter(t)
              return all(i in t for i in s)
           
          print(is_subsequence("ace""abcde"))
          print(is_subsequence("aec""abcde"))

          首先t = iter(t)我們可以理解為產(chǎn)生了一個(gè)生成器:

          t = (i for i in t)

          i in t基本上等價(jià)于:

          while True:
              val = next(t)
              if val == i:
                  yield True

          測(cè)試一下:

          t = "abcde"
          t = (i for i in t)
          print('a' in t)
          print('c' in t)
          print(next(t))

          結(jié)果:

          True
          True
          d

          可以看到最后一行直接返回了匹配到c的下一個(gè)值'd'。

          這樣我們?cè)贉y(cè)試:

          t = "abcde"
          t = (i for i in t)
          print('a' in t)
          print('c' in t)
          print('b' in t)

          結(jié)果:

          True
          True
          False

          于是就可以順利的使用生成器計(jì)算子序列的每個(gè)元素是否滿足條件:

          t = iter("abcde")
          [i in t for i in "aec"]

          結(jié)果:

          [True, True, False]

          all()函數(shù)即可判斷是否全部都滿足條件:

          print(all([True, True, False]), all([True, True, True]))

          結(jié)果:

          False True

          而上述代碼all(i in t for i in s)沒有申明all([i in t for i in s])列表生成式形式則代表是對(duì)一個(gè)列表生成器進(jìn)行all運(yùn)算。

          總結(jié)

          于是到此,我們就很優(yōu)雅地解決了這道題。不過一定要注意,實(shí)際工作中盡量不要用這種技巧,因?yàn)槟愕念I(lǐng)導(dǎo)和同事有可能并不知道生成器的用法,你即使寫了詳細(xì)的注釋他們也難以理解,不如用常規(guī)方法解決比較好!經(jīng)過今天的學(xué)習(xí),希望你已經(jīng)在生成器這個(gè)技術(shù)知識(shí)點(diǎn)上比其他人更加熟練了。

          今天本文分享了容器、可迭代對(duì)象、迭代器和生成器四種不同的對(duì)象:

          • 容器是可迭代對(duì)象,可迭代對(duì)象調(diào)用 iter() 函數(shù)可以得到一個(gè)迭代器。
          • 迭代器可以通過 next() 函數(shù)來得到下一個(gè)元素,從而支持遍歷。
          • 生成器是一種特殊的迭代器(迭代器卻不見得是生成器)。


          推薦閱讀:

          入門: 最全的零基礎(chǔ)學(xué)Python的問題  | 零基礎(chǔ)學(xué)了8個(gè)月的Python  | 實(shí)戰(zhàn)項(xiàng)目 |學(xué)Python就是這條捷徑


          干貨:爬取豆瓣短評(píng),電影《后來的我們》 | 38年NBA最佳球員分析 |   從萬眾期待到口碑撲街!唐探3令人失望  | 笑看新倚天屠龍記 | 燈謎答題王 |用Python做個(gè)海量小姐姐素描圖 |


          趣味:彈球游戲  | 九宮格  | 漂亮的花 | 兩百行Python《天天酷跑》游戲!


          AI: 會(huì)做詩(shī)的機(jī)器人 | 給圖片上色 | 預(yù)測(cè)收入 | 碟中諜這么火,我用機(jī)器學(xué)習(xí)做個(gè)迷你推薦系統(tǒng)電影


          年度爆款文案


          點(diǎn)閱讀原文,領(lǐng)全套AI資料!

          瀏覽 20
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品自拍XXX | 麻豆三级片在线观看 | 殴美一二三区 | 黄色级片视频视频 | 北条麻妃AV在线 |