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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容