python進階:第二章(對象迭代與反迭代)

問題一:如何實現可迭代對象和迭代器對象?

問題內容:
某軟件要求從網絡中抓取各個城市天氣信息,并依次顯式:
北疆:15~20
上海:20~26
杭州:17~27
......
如果一次抓取所有城市天氣再顯示,顯示第一個城市氣溫時,有很高的延時,并且浪費存儲空間。我們期望以“用時訪問”的策略(每抓取一條,顯示一條),并且能把所有城市氣溫封裝到一個對象里,可用for語句進行迭代,如何解決?

我們現在了解一下可迭代對象和迭代器對象:
含有 iter() 方法或 getitem() 方法的對象稱之為可迭代對象。
我們可以使用 Python 內置的 hasattr() 函數來判斷一個對象是不是可迭代的。

>>> hasattr((), '__iter__')
True
>>> hasattr([], '__iter__')
True
>>> hasattr({}, '__iter__')
True
>>> hasattr(123, '__iter__')
False
>>> hasattr('abc', '__iter__')
False
>>> hasattr('abc', '__getitem__')
True

另外,我們也可使用 isinstance() 進行判斷:

>>> from collections import Iterable

>>> isinstance((), Iterable)        # 元組
True
>>> isinstance([], Iterable)        # 列表
True
>>> isinstance({}, Iterable)        # 字典
True
>>> isinstance('abc', Iterable)     # 字符串
True
>>> isinstance(100, Iterable)       # 數字
False

迭代器是指遵循迭代器協議(iterator protocol)的對象
迭代器協議(iterator protocol)是指要實現對象的 iter() 和 next() 方法(注意:Python3 要實現 next() 方法),其中,iter() 方法返回迭代器對象本身,next() 方法返回容器的下一個元素,在沒有后續元素時拋出 StopIteration 異常

可迭代對象是指能夠循環遍歷的,例如列表,字符串等。迭代器對象是指可迭代對象通過iter()韓碩生成的對象。

In [1]: iter?
Docstring:
iter(iterable) -> iterator
iter(callable, sentinel) -> iterator

Get an iterator from an object.  In the first form, the argument must
supply its own iterator, or be a sequence.
In the second form, the callable is called until it returns the sentinel.
Type:      builtin_function_or_method

In [2]: l = [1,2,3,4] 

In [3]: s = 'abcde' 

In [17]: l.__iter__()
Out[17]: <list_iterator at 0x7fa928113b70>

In [18]: iter(l)
Out[18]: <list_iterator at 0x7fa9290f5358>

我們看到iter()函數調用的是內置 __iter__()接口


In [5]: iter(s) 
Out[5]: <str_iterator at 0x7fc6dabeb518>


小結:
元組、列表、字典和字符串對象是可迭代的,但不是迭代器,不過我們可以通過 iter() 函數獲得一個迭代器對象;
Python 的 for 循環實質上是先通過內置函數 iter() 獲得一個迭代器,然后再不斷調用 next() 函數實現的;
自定義迭代器需要實現對象的 iter() 和 next() 方法(注意:Python3 要實現 next() 方法),其中,iter() 方法返回迭代器對象本身,next() 方法返回容器的下一個元素,在沒有后續元素時拋出 StopIteration 異常。

有時間閱讀
<a>http://wiki.jikexueyuan.com/project/explore-python/Advanced-Features/iterator.html</a>
<a>http://blog.csdn.net/gavin_john/article/details/49935209</a>

解決方案:
步驟一:實現一個迭代器對象WeatherIterator,next方法沒戲返回一個城市氣溫
步驟二:實現一個可迭代對象WeatherIterable,iter方法返回一個迭代器對象。

import  requests
from collections import Iterable,Iterator
# def getweather(city):
#     r = requests.get(u'http://wthrcdn.etouch.cn/weather_mini?citykey=' + city)
#     data = r.json()['data']['forecast'][0]
#     return '%s: %s , %s' % (city, data['low'],data['high'])

class WeatherIterator(Iterator):
    def __init__(self,citys):
        self.citys = citys
        self.index = 0

    def getweather(self,city):
        r = requests.get(u'http://wthrcdn.etouch.cn/weather_mini?citykey=' + city)
        data = r.json()['data']['forecast'][0]
        return '%s: %s , %s' % (r.json()['data']['city'], data['low'], data['high'])
    
    #python3的寫法
    def __next__(self):
        if self.index == len(self.citys):
            raise  StopIteration
        city = self.citys[self.index]
        self.index += 1
        return self.getweather(city)


class WeatherIterable(Iterable):
    def __init__(self,citys):
        self.citys = citys

    def __iter__(self):
        return WeatherIterator(self.citys)
    
# 北京:101010100
# 杭州:101210101
# 上海:101020100
# 廣州:101280101

for weather in WeatherIterable(['101010100','101210101','101020100','101280101']):
    print(weather)

輸出結果:

北京: 低溫 11℃ , 高溫 24℃
杭州: 低溫 18℃ , 高溫 27℃
上海: 低溫 15℃ , 高溫 21℃
廣州: 低溫 21℃ , 高溫 26℃

這里的API我使用的是城市編碼。

問題二:如何使用生成器函數實現可迭代對象?

問題內容:
實現一個可迭代對象的類,它能迭代出給定范圍內所有素數:
pn = PrimeNumbers(1,30)
for k in pn:
print(k)
輸出結果:2,3,5,7,11,13,17,19,23,29

解決方案:將該類的iter方法實現成生成器函數,每次yield返回一個素數。

In [21]: def  f(): 
    ...:     print("in f(),1") 
    ...:     yield 1
    ...:     print("in f(),2")
    ...:     yield 2
    ...:     print("in f(),3") 
    ...:     yield 3
    ...:     

In [22]: g = f() 

In [23]: next(g)
in f(),1
Out[23]: 1

In [24]: next(g) 
in f(),2
Out[24]: 2

In [25]: next(g) 
in f(),3
Out[25]: 3

In [26]: next(g)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-26-5f315c5de15b> in <module>()
----> 1 next(g)

StopIteration:

生成器會記住上次執行的位置,和迭代器一樣,是逐步迭代,到最后報StopIteration。

In [34]: for x in g:
    ...:     print(x) 
    ...:     
in f(),1
1
in f(),2
2
in f(),3
3

g是可迭代對象,并且g的iter()函數生成的迭代器是本身。

In [28]: g.__iter__()  is g 
Out[28]: True

迭代器和生成器的行為是一致的。這里的g既包含iter,又包含next接口。

我們將可迭代對象的iter方法實現成生成器函數,當函數被調用的時候不會直接運行而是返回一個包含next方法的生成器對象。

class PrimeNumber:
    def __init__(self,start,end):
        self.start = start
        self.end   = end

    def isPrimeNum(self,k):
        if k < 2:
            return  False

        for i in range(2,k):
            if k%i == 0:
                return  False
        return True

    def __iter__(self):
        for k in range(self.start,self.end + 1):
            if self.isPrimeNum(k):
                yield k


for x in PrimeNumber(1,100):
    print(x)

問題三:如何進行反向迭代以及如何實現反向迭代?

問題內容:
實現一個連續浮點數發生器FloatRange(和range類似),根據指定范圍(start,end),和步進值(step)產生一系列連續浮點數,如迭代FloatRang(3.0,4.0,0.2)可產生序列:
正向:3.0 -> 3.2 -> 3.4 -> 3.6 -> 3.8 -> 4.0
反向:4.0 -> 3.8 -> 3.6 -> 3.4 -> 3.2 -> 3.0

In [1]: l = [1,2,3,4,5] 

In [2]: l.reverse()
可以實現內置的reverse()函數將列表反序,這里改變的是原列表
In [3]: l
Out[3]: [5, 4, 3, 2, 1]

In [4]: l = [1,2,3,4,5] 
可以使用切片的反向切片
In [5]: l[::-1] 
Out[5]: [5, 4, 3, 2, 1]

In [6]: l
Out[6]: [1, 2, 3, 4, 5]
內置的reversed()函數可以生成反向迭代器
In [7]: reversed(l) 
Out[7]: <list_reverseiterator at 0x7f279caf8780>
iter()函數生成的是正向迭代器
In [8]: iter(l)
Out[8]: <list_iterator at 0x7f279cafd9e8>
我們可以對反向迭代器進行遍歷
In [9]: for x in reversed(l):
   ...:     print(x) 
   ...:     
5
4
3
2
1
正向迭代器調用的是__iter__接口
In [10]: l.__iter__
Out[10]: <method-wrapper '__iter__' of list object at 0x7f279cad8648>
反向迭代器調用的是__reversed__接口
In [11]: l.__reversed__?
Docstring: L.__reversed__() -- return a reverse iterator over the list
Type:      builtin_function_or_method

解決方案:
實現反向迭代協議的reversed方法,它返回一個反向迭代器。

class FloatRange:
    def __init__(self,start,end,step=0.1):
        self.start = start
        self.end = end
        self.step = step

    def __iter__(self):
        t = self.start
        while t <= self.end:
            yield t
            t += self.step

    def __reversed__(self):
        t = self.end
        while t >= self.start:
            yield t
            t -= self.step

#正向迭代
for x in FloatRange(1.0,4.0,0.5):
    print(x)

#反向迭代
for x in reversed(FloatRange(1.0,4.0,0.5)):
    print(x)

問題五:如何對迭代器進行切片操作?

問題內容:
有某個文本文件,我們只想讀取其中某范圍內的內容如100300行之間的內容。python中文本文件是可迭代對象,我們是否可以使用類似列表切片的方式得到一個100300行文件內容的生成器?

f = open("/var/log/dmesg")
f[100:300] #可以?

In [19]: f  = open("/var/log/dmesg",'r') 

In [20]: for  lin  in f:                 
    ...:     print(lin) 
    ...: 

解決方案:使用標準庫中的itertools.islice,它能返回一個迭代對象切片的生成器。

In [19]: f  = open("/var/log/dmesg",'r') 
In [20]: from itertools import islice    
                        
             
In [20]: from itertools import islice    

In [21]: islice?
Init signature: islice(self, /, *args, **kwargs)
Docstring:     
islice(iterable, stop) --> islice object
islice(iterable, start, stop[, step]) --> islice object

Return an iterator whose next() method returns selected values from an
iterable.  If start is specified, will skip all preceding elements;
otherwise, start defaults to zero.  Step defaults to one.  If
specified as another value, step determines how many values are 
skipped between successive calls.  Works like a slice() on a list
but returns an iterator.
Type:           type

In [22]: islice(f,100,300) 
Out[22]: <itertools.islice at 0x7f279c088638>

In [23]: for line in islice(f,100,300):
    ...:     print(line) 

從開始到500行
In [24]: islice(f,500)
Out[24]: <itertools.islice at 0x7f279c0888b8>

從500行到結束
In [25]: islice(f,100,None) 
Out[25]: <itertools.islice at 0x7f279c088228>

注意一點,islice()函數對可迭代對象是有損耗的。

In [26]: l = range(20) 

In [27]: l 
Out[27]: range(0, 20)

In [28]: t = iter(l) 

In [29]: t 
Out[29]: <range_iterator at 0x7f279ca6ad80>

In [30]: for x in islice(t,5,10):
    ...:     print(x) 
    ...:     
5
6
7
8
9

In [31]: for x in t:
    ...:     print(x) 
    ...:     
10
11
12
13
14
15
16
17
18
19

我們使用islice()的時候已經消耗t(丟棄前面的),當再次迭代的時候,只能迭代剩余的。

問題六:如何在一個for語句中迭代多個可迭代對象

問題內容:
1,某個班同學期末考試成績,語文,數學,英語分別存儲在3個列表中,同時迭代三個列表,計算每個學生的總分。(并行)
2,某年級有4個班,每次考試每班英語成績分別存儲在4個列表中,依次迭代每個列表,統計全學年成績高于90分人數。(串行)

In [32]: from random import randint 

In [33]: chinese = [ randint(60,100) for _ in range(40)] 

In [34]: math = [ randint(60,100) for _ in range(40)] 

In [35]: english = [ randint(60,100) for _ in range(40)] 

In [36]: for i in range(len(math)):
    ...:     print(chinese[i] + math[i] + english[i])

上面的方式,只適合支持索引的,如果是生成器,則不支持。

解決方案:

并行:使用內置函數zip,它能將多個可迭代對象合并,每次迭代返回一個元組。
串行:使用標準庫中的itertools.chain,它能將多個可迭代對象連接。

并行情況:

In [7]: zip([1,2,3,4],('a','b','c','d'))
Out[7]: <zip at 0x7ff9d8dfebc8>

In [8]: list(zip([1,2,3,4],('a','b','c','d')))
Out[8]: [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]

In [9]: total = [ ]

In [10]: for c,m,e in zip(chinese,math,english):
    ...:     total.append(c + m + e)
    ...:     

In [11]: total
Out[11]: 
[193,
 243,
 256,
 260,
 216,
 246,
 257,
 241,
 224,
 240,
 200,
 244,
 274,
 240,
 250,
 230,
 234,
 224,
 233,
 222,
 217,
 242,
 268,
 266,
 233,
 227,
 247,
 247,
 227,
 227,
 259,
 248,
 260,
 238,
 247,
 251,
 203,
 297,
 211,
 244]

串行情況:

In [12]: from itertools    import chain 

In [13]: chain?
Init signature: chain(self, /, *args, **kwargs)
Docstring:     
chain(*iterables) --> chain object

Return a chain object whose .__next__() method returns elements from the
first iterable until it is exhausted, then elements from the next
iterable, until all of the iterables are exhausted.
Type:           type


In [15]: for x in chain([1,2,3,4],['a','b','c','d']) :
    ...:     print(x)
    ...:     
1
2
3
4
a
b
c
d

我們看下串行統計成績大于90的人數:

                            
In [16]: e1 = [ randint(60,100) for  _ in range(40)]

In [17]: e2 = [ randint(60,100) for  _ in range(42)]

In [18]: e3 = [ randint(60,100) for  _ in range(42)]

In [19]: e4 = [ randint(60,100) for  _ in range(45)]

In [20]: count = 0 

In [21]: for s in chain(e1,e2,e3,e4):
    ...:     if s > 90:
    ...:         count += 1 
    ...:         

In [22]: count
Out[22]: 39
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容