自学Python之Python基础:(六)可迭代对象与反迭代技巧

来源:互联网 发布:网页调用excel数据 编辑:程序博客网 时间:2024/06/07 12:06

转载的老板请注明出处:http://blog.csdn.net/cc_xz/article/details/78692064万分感谢!

在本篇中,你将了解到:
1.可迭代对象、迭代器与生成器。
2.如何进行反向迭代。
3.对迭代器进行切片。
4.同时迭代多个可迭代对象。


可迭代对象、迭代器与生成器:
容器:
容器是用来存储元素的一种数据结构,容器将所有的数据都保存在内存中,在Python中的典型容器有:list、deque、set、dict、OrderedDict等。容器很容易理解,它就是生活中的箱子、房间等一切可以盛放其他物品的容器。
通过判断对象是否包含某元素来确定它是否是一个容器:

assert 1 in [1, 2, 3, 4]  # assert是Python中的断言,断言是表达式的返回结果比如为True。assert 4 not in [1, 2, 3]  # 如果表达式的返回结果为False,则报出AssertionError异常。assert 1 in {1, 2, 3, 4, 5}  # 例如第五行中,由于元组中并不存在1,所以报出异常。assert 4 not in {1, 2, 3, 5}  # 而这些代码表明,list、set、tuple都是容器。 assert 1 in (12, 3, 4, 5)  # 因为他们都包含了多个不同的元素。assert 4 not in (1, 2, 3, 5)

输出结果为:

Traceback (most recent call last):  File "D:/Python/Code/Demo01/Demo.py", line 5, in <module>    assert 1 in (12,3,4,5)AssertionError

可迭代对象:
大部分的容器都是可迭代的,但还有一些对象也是可以迭代的,例如文件对象和管道对象等。一般而言容器所存储的元素是优先的,同样的,可迭代对象也可以用来表示一些包含有限元素的数据结构。任何对象都可以成为可迭代对象,不一定必须是基本数据结构,只要这个对象可以返回一个iterator即可。

x = [1,2,3,4,5]y = iter(x) #iter()函数用于生成一个迭代器。# print(next(x)) #默认list是可迭代对象,而不是迭代器对象。print(next(y)) #而y则是使用iter()函数所生成的迭代器对象。print(type(x))print(type(y))

输出结果为:

1<class 'list'><class 'list_iterator'>

值得注意的是,这里是x是可迭代对象,而y是迭代器,迭代器可以从可迭代对象中获取值。在可以进行迭代的类中,一般会实现__iter__()和__next__()两个函数。

x = [1,2,3,4,5]for x_ in x :   pass

当执行上述for循环中的代码时,实际上是在执行:
这里写图片描述
即:将一个可迭代对象调用iter()函数转换成一个迭代器,再调用这个迭代器的netx()函数,将其中的元素一一解析出来。


迭代器(Iterators):
任何具有__next__()函数的对象都是迭代器,对迭代器调用next()函数可以获取下一个元素。而至于该对象如何产生这个值,同它能否成为一个迭代器并没有关系(只有一个元素)。所以迭代器本质上是一个生产元素的工厂,每次向迭代器请求下一个值时,迭代器都会进行计算并返回相应的元素。
为了更好的理解迭代器的内部结构,首先来定义一个生成斐波拉契数的迭代器(菲波拉契数是值在一个数值的序列中,后面一个数的值等于前面两个数的和)

class fib:   def __init__(self):      self.prev = 0 #前一个值      self.current = 1 #当前值   def __iter__(self): #该函数返回一个可迭代对象。      return self   def __next__(self): #该函数返回一个迭代器(每次迭代返回的值)。      value = self.current      self.current += self.prev      self.prev = value      return valuetest01 = fib()for x in test01:   if x > 100:      break   print(x,end=" ")

输出结果为:

1 1 2 3 5 8 13 21 34 55 89 

这个类既是可迭代对象(因为具有__iter__()函数),又是迭代器(因为它具有__next__()函数)。迭代器内部的状态保存着当前对象的prev以及current两个变量的属性,并且在下次迭代时会调用这两个变量。而每次的迭代,都是调用__next__()函数。它会进行如下操作:
1. 修改当前的函数状态,以便下次的迭代。
2. 计算当前的变量。
3. 返回变量的值(value,即迭代结果)。
可迭代对象无法依次迭代出其中包含的元素,因为没有__next__()函数,而迭代器可以。总而言之:迭代器一定是一个迭代对象,但迭代对象不一定是迭代器。


yield关键字 :
yield是类似return的关键字,只不过这个函数是返回一个生成器。当你调用这个函数的时候,函数内部的代码并不会立刻执行。这个函数会返回一个生成器对象。而真正执行这个函数的时候,是使用for等循环进行迭代的时候。

def createYiele():   print("执行了createYiele()")   for x in range(5):      print("执行了createYiele()中的for循环")      yield x * xprint("程序开始执行")for x in createYiele():   print("执行了for循环,x的值为:",x)

输出结果为:

程序开始执行执行了createYiele()执行了createYiele()中的for循环执行了for循环,x的值为: 0执行了createYiele()中的for循环执行了for循环,x的值为: 1执行了createYiele()中的for循环执行了for循环,x的值为: 4


生成器:
生成器实际上是一种更高级更优雅的迭代器,使用生成器可以使用更简洁的语法来定义迭代器。下面也是一个生成斐波那契序列的工厂函数,不过是以生成器的方式编写的:

   prev, current = 0, 1  # 创建两个变量,并赋值。   while True:  # 创建一个死循环。      yield current  # 使用生成器返回current(当前值)      prev, current = current, prev + currenttest01 = fib()for x in test01:   if x > 100:      break   print(x, end=" ")

输出结果为:

1 1 2 3 5 8 13 21 34 55 89 

首先,fib是一个很普通的函数,但是这个函数中没有return语句,函数返回的值是一个生成器。
当调用f = fib()时,生成器被实例化,并返回,这时并不会执行任何代码,生成器处于空闲状态,注意,这里的prev,current = 0 , 1 并未执行。
然后生成器被包含在for循环中,这是一个迭代器,所以仍然没有执行上述代码。
而当for循环开始执行时,循环才开始调用fib()的next()函数。当执行到yield current时,会返回当前的current的值,然后生成器(fib()函数)又进入空闲状态。
接着再继续执行剩余的步骤,直至current返回的值大于100,执行break跳出循环。


反向迭代:
如果希望反向迭代(从末尾开始迭代)一个列表,可以使用如下操作:

"""对于反向操作,可以使用列表自带的reverse()函数,这样可以将列表中的内容进行反序。但这样实际上对原列表进行了修改,一方面如果列表比较大,容易造成较大的开销,另一方面也容易对象原列表造成影响。"""list01 = [1,2,3,4,5,6]list01.reverse()print(list01)"""当然也可以使用切片操作,使用步进值为-1的切片操作,但这样是得到了一个同原列表大小相同的新列表。容易造成资源浪费。"""list02 = [1,2,3,4,5,6]print(list02[:: -1])

输出结果为:

[6, 5, 4, 3, 2, 1][6, 5, 4, 3, 2, 1]


reversed()函数:
内置函数reversed()函数,接收一个容器,将输出一个反向列表迭代器,也就是说,reversed()函数是同iter()的作用相同,但实际得出的结果是相反的。

list01 = [1,2,3,4,5,6]list02 = reversed(list01)for x in list02:   print(x,end=" ")

输出结果为:

6 5 4 3 2 1 

也就是说,当一个容器经过iter()函数转变为可迭代对象后,是给容器增加了__iter__函数,同样的,当一个容器经过reversed()函数转变为反向可迭代对象后,也是给该容器增加了__reversed__函数。


实现反向迭代案例:
实现一个“连续浮点数产生器”,同range()函数类似,根据给定的范围(start,end)两个参数,以及步进值(step(递增))产生一些连续的浮点数。
例如,FloatRange(3.0,4.0,0.2)可产生的序列:

class FloatRange:   # 首先创建初始化函数,并定义三个关键变量:起止范围,增量值。   def __init__(self, start, end, step=0.1):      self.start = start      self.end = end      self.step = step   # __iter__用于将该对象创建为可迭代对象时执行的方法。   def __iter__(self):      t = self.start      while t <= self.end:         yield t         t += self.step   # __reversed__同iter的作用相同,只不过会将其中的元素颠倒。   def __reversed__(self):      t = self.end      while t >= self.start:         yield t         t -= self.step# 正向迭代test01 = FloatRange(1.0, 6, 0.5)for x in test01:   print(x, end=" ")print(" ") # 用做换行符效果。# 反向迭代test02 = reversed(FloatRange(1, 6, 0.5))for x in test02:   print(x, end=" ")

输出结果为:

1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0  6 5.5 5.0 4.5 4.0 3.5 3.0 2.5 2.0 1.5 1.0 


对迭代器进行切片:
切片:
在Python中我们可以直接使用索引来访问序列中的元素,同时索引可以分为正向和反向的两种,同样,切片也可以使用正向、反向索引来找出序列(列表、字典、元组等)中的元素。

list01 = [1,2,3,4,5,6]print(list01[1:6:2])

输出结果为:

[2, 4, 6]

上述代码中,实现了使用切片的方法查找序列中的元素。而切片的语法为:

list01[start_index :end_index:step]

其中:

start_index表示起始索引值,用于确定从何处开始。end_index表示结束索引值,用于确定到何处停止。step表示步长,切片操作是按照步长,截取从起始索引到结束索引,但不包含结束索引。步长不能为0,默认值为1


切片的多种操作:

list01 = list(range(1, 20))print(list01[4:13])  # 省略步长,将输出Index4-13中所有的元素。因为默认步长为1。print(list01[:13])  # 省略start_index(起始索引),保留end_index(结束索引),这样会从第一个元素开始,一直到结束索引指定(结束索引-1)的位置。print(list01[4:])  # 保留start_index(起始索引),省略end_index(结束索引),这样会从起始索引处,一直到最后一个元素。print(list01[:]) # 省略start_index和end_index则输出整个序列,即生成一个完整的新序列。print(list01[::3]) # 省略start_index和end_index但保留step,则表示对整个序列按照步长整除的规则取值。print(list01[::-1]) # 如果将步长设置为-1,则可以得到一个反向序列,但注意,这样是创建了一个新的序列,若序列较大,则耗费内存。

输出结果为:

[5, 6, 7, 8, 9, 10, 11, 12, 13][1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13][5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19][1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19][1, 4, 7, 10, 13, 16, 19][19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]


使用readlines()配合list()切片:

txt = open('Release.txt','r', encoding= u'utf-8') #创建文本对象#txt[10:30] #无法对文本对象进行切片。list01 = list(txt.readlines()) # readlines()可以将文本按照行来输出,配合list()可以将每行的文本作为一个元素赋值到列表中。print(list01[20:30]) # 从而可以对列表进行切片。

输出结果为:

x =  1['  1. 更新用户手册。\n', '\n', .....省略部分结果 \n', '- 更新:\n']

但这种方法存在一个问题,即:readlines()函数会一次性将所有文本读入到内存中,如果文本较小,则没有什么压力,但如果需要读取一些例如日志文件等动辄上G的文本,则会对内存造成较大的开销。


使用迭代器进行切片:

from itertools import islicetxt = open('Release.txt','r', encoding= u'utf-8') # 创建文本对象iter01 = islice(txt,10,20,2) # 导入文本对象,以及起始索引、结束索引和步进值。iter02 = iter(txt) # 使用iter()同样可以创建一个可迭代对象,但无法进行切片操作。for line in iter01:   print(line)

输出结果为:

  4. 调整目录结构。----------------------------------------------------------版本:1.5    1. 删除封底的公司地址。


在一个for多同时迭代多个可迭代对象:
使用索引同时迭代:

from random import randintlist01 = [randint(60,100) for x in range(20)]list02 = [randint(60,100) for x in range(20)]list03 = [randint(60,100) for x in range(20)]list04 = [randint(60,100) for x in range(20)]list05 = []for x in  range(len(list01)):   list05.append(list01[x]+list02[x]+list03[x]+list04[x])print(list05)

输出结果为:

[319, 276, 290, 307, 300, 314, 316, 385, 345, 386, 304, 334, 301, 282, 325, 326, 331, 335, 356, 320]

使用索引同时迭代可迭代对象,虽然可以实现预计的目标,但是实际使用中会有比较大的局限性。例如,如果需要迭代生成器,那么就无法使用索引进行迭代。


使用zip()函数进行并行迭代:
使用内置的zip()函数进行并行迭代(同时迭代多个可迭代对象),它能将多个可迭代对象合并,再返回一个元组。

from random import randintlist01 = [randint(60, 100) for x in range(20)]list02 = [randint(60, 100) for x in range(20)]list03 = [randint(60, 100) for x in range(20)]list04 = [randint(60, 100) for x in range(20)]list05 = []tuple01 = zip(list01, list02, list03, list04)  # 进行组包操作,将各个列表组合成一个元组。for x, y, z, i in tuple01:  # 通过for循环进行拆包,将元组中的值进行拆分。   list05.append(x + y + z + i)print(list05)

输出结果为:

[316, 328, 332, 338, 349, 352, 328, 310, 346, 344, 335, 317, 276, 315, 303, 337, 312, 374, 299, 372]

在使用这种方式时,需要注意的是,在将各个列表组合成元组时,如果列表的长度不相等,那么元组的长度将以最短的列表的长度为基准。


对可迭代对象进行串行迭代:

from itertools import chain # 使用此函数对可迭代对象进行串连。from random import randintlist01 = [randint(60, 100) for x in range(20)]list02 = [randint(60, 100) for x in range(20)]list03 = [randint(60, 100) for x in range(20)]list04 = [randint(60, 100) for x in range(20)]chain01 = chain(list01, list02, list03, list04)  # 将多个可迭代对象进行链接(串连),从而获得一个可迭代对象。count = 0  # 统计出所有值高于90的次数。for x in chain01:  # 此时的迭代,将先迭代list01的第0个元素,再迭代list02的第0个元素,以此类推。   if x > 90:      count += 1print(count)

输出结果为:

25
原创粉丝点击