您的位置:首页 > 编程语言 > Python开发

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

2017-12-01 23:18 337 查看
转载的老板请注明出处: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 value

test01 = 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 * x

print("程序开始执行")
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 + current

test01 = 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 islice

txt = 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 randint

list01 = [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 randint

list01 = [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 randint

list01 = [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 += 1

print(count)


输出结果为:

25
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息