流暢的python學習筆記-第14章

第14章 可迭代的對象、迭代器和生成器

<!-- TOC -->python

<!-- /TOC -->code

可迭代對象

舉個例子:orm

import re

re_word = re.compile(r'\w+')


class Sentence(object):
    def __init__(self, text):
        self.text = text
        self.word = re_word.findall(text)

    def __getitem__(self, item):
        return self.word[item]

    def __len__(self):
        return len(self.word)

    def __str__(self):
        return 'Sentence(%s)' % self.word


if __name__ == "__main__":

    s = Sentence("Today is Tuesday")
    print(s)

    for word in s:
        print(word)

返回結果:協程

Sentence(['Today', 'is', 'Tuesday'])
Today
is
Tuesday

咱們知道一個對象能夠迭代是由於實現了__iter__方法,
可是在Sentence中並無實現__iter__方法。那爲何能夠迭代呢。對象

緣由在於在python中實現了iter和getitem的都是可迭代的。首先會檢查是否實現了iter方法,若是實現了則調用,若是沒有可是實現了__getitem__方法。blog

Python就會建立一個迭代器。索引

嘗試按照順序獲取元素。若是嘗試失敗則會拋出typeerror異常,提示object is not iterable.

所以:

若是對象實現了能返回迭代器的 __iter__方法,那麼對象就是可迭代的。

若是實現了__getitem__方法,並且其參數是從零開始的索引。

這種對象也能夠迭代。

咱們用__iter__方法來改造以前的Sentence。
__iter__中返回一個可迭代對象iter(self.word)。
當執行for word in s的時候就會調用__iter__方法

import re

re_word = re.compile(r'\w+')


class Sentence(object):
    def __init__(self, text):
        self.text = text
        self.word = re_word.findall(text)

    def __iter__(self):
        return iter(self.word)

    def __len__(self):
        return len(self.word)

    def __str__(self):
        return 'Sentence(%s)' % self.word


if __name__ == "__main__":

    s = Sentence("Today is Tuesday")
    print(s)

    for word in s:
        print(word)

再來看下next, next的做用是返回下一個元素,若是沒有元素了,拋出stopIteration異常。

咱們來看下next的使用方法。若是要遍歷一個字符串,最簡單的方法以下:

s = 'abc'

for char in s:
    print(char)

若是不用for方法,代碼須要修改以下:

s = 'abc'

it = iter(s)

while True:
    try:
        print(next(it))
    except StopIteration:
        del it
        break

首先將s變成一個iter對象,而後不斷調用next獲取下一個字符,若是沒有字符了,則會拋出StopIteration異常釋放對it的引用

s = 'abc'

it = iter(s)

print(next(it))
print(next(it))
print(next(it))
print(next(it)) # StopIteration

由於只有3個字符,可是調用了4次it.next()致使已經找不到字符所以拋出異常。

總結:

可迭代對象:實現了 __iter__方法,就是可迭代的,能夠返回自身做爲迭代器。
也能夠返回其餘一個可迭代對象

迭代器

迭代器是什麼

迭代器:在Python2中實現了next方法,在python3中實現了__next__方法。

首先要讓x經過iter變成一個可迭代對象,而後使用迭代器來調用元素

使用迭代器好處就是:每次只從對象中讀取一條數據,不會形成內存的過大開銷。

Alt text

迭代器內存消耗檢測

能夠看看它的內存佔用:

使用python2.7.15+:

import sys
i = iter(range(1000000))
print sys.getsizeof(i)


r = range(1000000)
print sys.getsizeof(r)


y = xrange(1000000)    # 注意 xrange跟range的區別: xrange是range的迭代器的表達方式
print sys.getsizeof(y)

結果返回:

56
8000064
32

使用python 3.6+

import sys
i = iter(range(1000000))
print (sys.getsizeof(i))


r = range(1000000)
print (sys.getsizeof(r))

返回結果:

32
48

這裏有個問題:爲何在python2跟Python3的運行結果相差這麼大呢?
這是由於python3內部機制已經將range轉換成了一個迭代器了。

這裏能夠看的出來適合大的數據,好比好幾個G的數據, 使用了迭代器 內存使用大幅度減小,這是迭代器最大的優勢。

總結下:
咱們簡單說迭代器就是訪問集合元素,迭代器就是有一個next()方法的對象,而不是經過索引來計數的。

迭代器使用

那麼咱們怎麼能訪問迭代器裏面的元素呢?

迭代器有兩個方法 ,分別是iter()和next()方法

這兩個方法是迭代器最基本的方法
一個用來得到迭代器對象,一個用來獲取容器中的下一個元素。

itertools是python提供的很是高效建立與使用迭代器的模塊

from itertools import chain
test = chain.from_iterable('ABCDEFG')
# print(test)
# print(dir(test)) # 查看類具體方法

print(test.__next__())  # 'A'
print(test.__next__())  # 'B'
print(test.__next__())  # 'C'

test2 = chain('AB', 'CDE', 'F')
print(list(test2))  # ['A', 'B', 'C', 'D', 'E', 'F']

咱們知道迭代器是不支持索引的,緣由就是索引需實現明元素確佔用的內存地址,而迭代器是用到元素的時候纔會建立。以下:

i = iter(range(3))  # 建立迭代器
# i.index(2)  # 獲取元素爲2的索引
# AttributeError: 'range_iterator' object has no attribute 'index'

# 列表
l = range(3)
print(l.index(2))  # 獲取索引2

這個時候可使用內建函數enumerate(),這個函數很重要。

for i, j in enumerate(iter(['A', 'B', 'C'])):
    # for i, j in enumerate(['A', 'B', 'C']):
    print(i, j)

運行結果返回:

0 A
1 B
2 C


能夠看下這個函數的源碼是怎麼寫的

class enumerate(Iterator[Tuple[int, _T]], Generic[_T]):
    def __init__(self, iterable: Iterable[_T], start: int = ...) -> None: ...
    def __iter__(self) -> Iterator[Tuple[int, _T]]: ...
    if sys.version_info >= (3,):
        def __next__(self) -> Tuple[int, _T]: ...
    else:
        def next(self) -> Tuple[int, _T]: ..

生成器

生成器是迭代器,但你只能遍歷它一次(iterate over them once)

由於生成器並無將全部值放入內存中,而是實時地生成這些值

mygenerator = (x * x for x in range(3))  # 生成器
# mygenerator = [x * x for x in range(3)] # 列表
for i in mygenerator:
    print("i=", i)

for i in mygenerator:
    print("New=", i)

運行結果:

i= 0
i= 1
i= 4

注意,你不能執行for i in mygenerator第二次,由於每一個生成器只能被使用一次

yield生成器

在Python中,使用生成器能夠很方便的支持迭代器協議。

生成器經過生成器函數產生,生成器函數能夠經過常規的def語句來定義,可是不用return返回,
而是用yield一次返回一個結果,在每一個結果之間掛起和繼續它們的狀態,來自動實現迭代協議。

也就是說,yield是一個語法糖,內部實現支持了迭代器協議

同時yield內部是一個狀態機,維護着掛起和繼續的狀態。

下面看看生成器的使用:

def Zrange(n):
    i = 0
    while i < n:
        yield i
        i += 1


zrange = Zrange(3)
print(zrange)  # <generator object Zrange at 0x000002997DE75468>
print([i for i in zrange])  # [0, 1, 2]

在這個例子中,定義了一個生成器函數,函數返回一個生成器對象,而後就能夠經過for語句進行迭代訪問了。

其實,生成器函數返回生成器的迭代器。 「生成器的迭代器」這個術語一般被稱做」生成器」。
要注意的是生成器就是一類特殊的迭代器。做爲一個迭代器,生成器必需要定義一些方法,其中一個就是next()。

如同迭代器同樣,咱們可使用next()函數來獲取下一個值。

生成器執行過程

例子:

def Zrange(n):
    print("begin of Zrange")
    i = 0
    while i < n:
        print("before yield:", i)
        yield i
        i += 1
        print("after yield:", i)

    print("begin of Zrange")


zrange = Zrange(3)
# print(zrange)

print(zrange.__next__())
print("-" * 10)
print(zrange.__next__())
print("-" * 10)
print(zrange.__next__())
# print(zrange.__next__()) # StopIteration

運行結果:

begin of Zrange
before yield: 0
0
----------
after yield: 1
before yield: 1
1
----------
after yield: 2
before yield: 2
2

經過結果能夠看到:

  • 當調用生成器函數的時候,函數只是返回了一個生成器對象,並無 執行。
  • 當next()方法第一次被調用的時候,生成器函數纔開始執行,執行到yield語句處中止
  • next()方法的返回值就是yield語句處的參數(yielded value)
  • 當繼續調用next()方法的時候,函數將接着上一次中止的yield語句處繼續執行,併到下一個yield處中止;若是後面沒有yield就拋出StopIteration異常

總結:生成器是迭代器的一種,但功能方法比迭代器多

生成器建立

生成器的建立可使用yield關鍵字, 也可使用生成器表達式

(x*2 for i in range(10))

生成器判斷

判斷是否爲生成器函數可用isgeneratorfunction, 判斷是否爲生成器對象可用isgenerator

from inspect import isgeneratorfunction, isgenerator

g = (i for i in range(3))
print(isgenerator(g))  # True


def demo():
    yield 1


print(isgenerator(demo()))  # True
print(isgeneratorfunction(demo()))  # False
print(isgeneratorfunction(demo))  # True

yield與協程

def coroutine():
    print("coroutine start...")
    result = None
    while True:
        s = yield result
        result = 'result:{}'.format(s)


c = coroutine()
c.send(None)  # coroutine start...

print(c.send("first"))  # result:first
print(c.send("second"))  # result:second

c.close()

# c.send("hello")  # StopIteration
相關文章
相關標籤/搜索