問題一:如何實現可迭代對象和迭代器對象?
問題內容:
某軟件要求從網絡中抓取各個城市天氣信息,并依次顯式:
北疆: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