问题一:如何实现可迭代对象和迭代器对象?
问题内容:
某软件要求从网络中抓取各个城市天气信息,并依次显式:
北疆: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