美文网首页
深入理解迭代与解析

深入理解迭代与解析

作者: 罗泽坤 | 来源:发表于2018-11-01 19:26 被阅读0次

迭代与解析

本章要点

  • 掌握可迭代对象的解析
  • 掌握生成器的概念与正确使用生成器

迭代与解析在python语言中的意义:

  • 迭代与解析是python语言在众多编程语言中特立独行的特性之一
  • 可迭代的对象使得python语言能够在数据量庞大的时候节约内存,它使得用户能够根据自己的需要获得想要的数据
  • 解析在极大程度上提高了python语言的灵活性和运行效率,通过解析迭代器能够使得python语言能够用最短的代码
    实现强大的功能
# 列表的解析与map内置函数的使用
'''
把可迭代对象range用迭代工具for将其值都施加表达式x**x
的映射之后做成一个列表如下
'''
lis = [x ** 2 for x in range(6)]
lis
[0, 1, 4, 9, 16, 25]
# 函数式编程工具map与filter的使用与解析
a = map(lambda x:x**2,range(10))  # map返回一个迭代器
a                 
<map at 0x1a256665860>
list(a)          #使用迭代工具list迫使其输出所有的值
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
'''
列表解析是把一个可迭代对象所产生的对象用一个表达式
映射成另外一个对象,map函数是把一个函数映射遍一个对象的
所有值,并且返回一个迭代器,常常需要使用list迫使其输出
所有的值做成一个列表
'''
'\n列表解析是把一个可迭代对象所产生的对象用一个表达式\n映射成另外一个对象,map函数是把一个函数映射遍一个对象的\n所有值,并且返回一个迭代器,常常需要使用list迫使其输出\n所有的值做成一个列表\n'
'''
filter函数的使用在形式上与map类似,都是把一个
函数作用于一个可迭代的对象,也都返回一个迭代器
不过是用于过滤数据,满足函数则过滤掉
'''
b = filter(lambda x:x%2==0,range(10))
iter(b) is b           # 显然b本身就是一个迭代器
True
list(b)         # 过滤掉素数
[0, 2, 4, 6, 8]
'''
通常for语句的使用比filter与map的使用更具有通用性
一般for语句可以通过扩展性语句与解析实现filter与
map的功能例子如下
'''
[x**2 for x in range(10) if x%2==0]   # 一 列表解析
[0, 4, 16, 36, 64]
# 用map与filter实现一式
list(map((lambda x:x**2),filter((lambda x:x%2==0),range(10)))) 
[0, 4, 16, 36, 64]
'''
显然使用map与filter要复杂的多
在列表解析中可以嵌套多个for..in..if
语句可以实现强大的功能
'''
# 列表的解析和矩阵
matrix = [[1,2,3],   #定义一个矩阵
          [4,5,6],
          [7,8,9]]
for x in matrix:    #迭代矩阵每一行
    print(x)
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
# 矩阵索引
print(matrix[0])  #索引第一行
print(matrix[1])  #第二行
print(matrix[2])  #第三行
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
# 索引第二列元素
[row[1] for row in matrix] #按行地址索引
[2, 5, 8]
[matrix[x][1] for x in [0,1,2]]
[2, 5, 8]
matrix[1][-1]  #索引第二行倒数第一列元素
6
# 用列表解析和map筛选出值
listoftuple = [('jack',20,'java'),('rose',19,'python')]
[age for name,age,major in listoftuple] #列表解析
[20, 19]
list(map((lambda x:x[1]),listoftuple))  # map返回一个迭代器后用list迭代工具迭代出所有的值
[20, 19]
'''
生成器的概念:
在python中许多内置对象都有其自身的迭代器
有的自身就是迭代器,毋庸置疑迭代器的
使用为程序带来了很多好处,但是还有很多
自定义的类型我们也希望把他变成迭代器,使其
拥有迭代协议,生成器就是把一个自定义函数变
成迭代器,要做到这点只需要在函数中添加一条
yield语句,就能使得在调用函数之后返回一个迭代
器对象,拥有迭代协议(即可以调用__next__方法)
具体例子如下
'''
def funiteration(N):
    for x in range(N):
        yield x**2         # 定义一个生成器
a = funiteration(2)       # 返回一个迭代器给a
a
<generator object funiteration at 0x000002D3248E3DB0>
next(a)
0
next(a)               # 调用迭代协议
1
next(a)               # 迭代工具for..in..触发异常退出生成器
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-18-3f6e2eea332d> in <module>()
----> 1 next(a)


StopIteration: 
a = funiteration(10)
list(a)              # 使用迭代工具迫使其产生所有值
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

生成器与常规函数的不同之处在与生成器是生成一个值而不是返回一个值且退出函数
生成器在生成一个值后定格住当前函数运行状态(即保存运行的数据并且继续函数执行)
使得函数在生成下一个值的时候能够从它生成上一个值的时候离开的地方开始。
上面的例子我们已经对yield有了一个感性的认识,yield给一个自定义函数增加了一个
next协议,使得函数变成了生成器,另外yield还给函数增加了一个send方法,下面我
们就来用一个简单的例子引出send方法。

def procefun():
    for x in range(6):
        y = (yield x)
        print('y的值为:',y)
add = procefun()
next(add)
0
next(add)
y的值为: None





1
next(add)
y的值为: None





2
add.send(10)
y的值为: 10





3

上面的结果能够引发我们的思考,注意等式 y = yield x,我们不是把生成的值赋值
给了y吗?为什么y的值依旧为None嘞?实际上yield可以理解为一个表达式,表达式
默认的返回值为None, 当调用 send 方法推送一个值给生成器时候,yield表达式就
返回推送的值,以实现人机交互,在来看下面的例子。

'''
扩展生成函数:send
生成器函数协议中增加了一个send方法
此方法可以使得生成器生成一个发送的
值,得已实现调用者与生成器之间的通信
具体事例如下
'''
def sen(n):
     while True:
        x = yield n       #用x接收调用者传来的值
        print('x:',x)
        n-=1
        if x is not None: # x不为None则重置n为x
            n = x
a = sen(6)
next(a)
6
next(a)
x: None





5
a.send(10)  # 更新n的值并调用next方法
x: 10





10
next(a)
x: None





9
'''
以上例子可以看出当调用send方法推送一个值给生成器时yield返回一个
推送的值,然后继续迭代生成器,并且由一个结果进入下一个结果。
'''
# 生成器与协程模拟淘宝买家与卖家互动
def consumer():               #消费者
    print('等待商家发货。。。')
    while True:
        data = (yield)
        print('收到货:',data)

def producer(n):             #生产者
    c = consumer()
    c.__next__()
    for i in range(n):
        print('发送一个包裹...','包裹%d' % i)
        c.send('货:%d' % i)
producer(6)                 # 六个订单
'''
上面的例子模拟了买卖双方实时物流
send推送一个
'''
等待商家发货。。。
发送一个包裹... 包裹0
收到货: 货:0
发送一个包裹... 包裹1
收到货: 货:1
发送一个包裹... 包裹2
收到货: 货:2
发送一个包裹... 包裹3
收到货: 货:3
发送一个包裹... 包裹4
收到货: 货:4
发送一个包裹... 包裹5
收到货: 货:5





'\n上面的例子模拟了买卖双方实时物流\nsend推送一个\n'
'''
从语法上来讲生成器表达式与列表解析表达式
一样,但是一个用圆括号括起来一个用方括号
他们的实现方式是不同,一个返回一个迭代器对象
,一个返回一个列表
'''
[x for x in range(7)]  #列表解析
[0, 1, 2, 3, 4, 5, 6]
(x for x in range(7))  #生成器表达式返回一个迭代器
<generator object <genexpr> at 0x000002D3249160F8>
g = (x for x in range(4) if x%2==0) #返回一个迭代器对象给g
g is iter(g)
True
next(g)
0
next(g)
2
next(g)
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-100-5f315c5de15b> in <module>()
----> 1 next(g)


StopIteration: 
# 使用迭代工具迭代生成器表达式
for x in (n**2 for n in range(4)):
    print(x)
0
1
4
9
sum(n**2 for n in range(4))     #迭代求和
14
sorted((n**2 for n in range(4)),reverse=True)
[9, 4, 1, 0]
import math
list(map(math.sqrt,(n**2 for n in range(4))))
[0.0, 1.0, 2.0, 3.0]

如一个列表解析往往可以使用多个for语句替换一样,生成器表达式也往往
可以用一个生成函数来代替,并且两个的前者往往比后者要更加简洁,请
看如下例子:

list(n**2 for n in range(10) if n%2==0) #对生成器表达式使用list迭代器
[0, 4, 16, 36, 64]
def replace():                 #用生成函数代替上式
    for n in range(10):
        if(n%2==0):
            yield n**2
a = replace()
list(a)
[0, 4, 16, 36, 64]
'''
用迭代工具模拟zip和map
实际在很多的内置函数都是由几种最基本
的迭代工具组合生成的,python就是这样
太极生两仪,两仪生四象,四象生八卦,
变化多端是python语言的特点。当我们了解了用
一个或多个对象模拟另一个对像功能时我们就能够
了解python语言的一些内在工作机制
'''
# 模拟map
def mymap(fun,*seqs):   #参数*seqs允许输入多个参数,并且把参数做成一个元组
    res = []
    for arg in zip(*seqs):       # *seqs解包
        res.append(fun(*arg))
    return res
print(mymap(abs,[-1,0,1]))
print(mymap(pow,[1,2,3],[1,2,3,4]))
[1, 0, 1]
[1, 4, 27]
#上式还可以用生成器表达式更加精简以下输出值与上一致
def Map(fun,*seqs):
    return [fun(*x) for x in zip(*seqs)]
print(mymap(abs,[-1,0,1]))
print(mymap(pow,[1,2,3],[1,2,3,4]))
[1, 0, 1]
[1, 4, 27]
# 用迭代工具模拟截断的zip和
list(zip([1,2,3],[1,2,3,4,5]))
[(1, 1), (2, 2), (3, 3)]
#模拟截断的zip
a = 'abc'
b = 'xyzjk'
def myzip(*seqs):
    seqs = [list(s) for s in seqs]#得到的结果为seqs =[[a,b,c],[x,y,z,j,k]]
    res = []
    while all(seqs):
        res.append(tuple(s.pop(0) for s in seqs))  # pop函数的使用时删除指定序号的元素并且返回
    return res
print(myzip(a,b))
[('a', 'x'), ('b', 'y'), ('c', 'z')]
# 模拟未截断的zip
def myzip(*seqs,temp=None):
    seqs = [list(s) for s in seqs]
    res = []
    while any(seqs):
        res.append(tuple(s.pop(0) if s else temp for s in seqs))
    return res
print(myzip(a,b))
[('a', 'x'), ('b', 'y'), ('c', 'z'), (None, 'j'), (None, 'k')]

注意上面函数中内置函数中all和any的使用
all:当seqs所有的元素都为真时候在为真,即当删除了seqs[0]与seqs[1]中任何一个的所有字符后就结束循环
any:当seqs中只要有元素为真即为真继续循环,即只有当seqs[0]与seqs[1]中所有的字符都删除后就结束循环

'''
上面的函数都不能用于列表解析转换
把上面的函数变成生成函数如下
'''
#模拟截断的zip
a = 'abc'
b = 'xyzjk'
def myzip(*seqs):
    seqs = [list(s) for s in seqs]#得到的结果为seqs =[[a,b,c],[x,y,z,j,k]]
    res = []
    while all(seqs):
        yield(tuple(s.pop(0) for s in seqs))  # pop函数的使用时删除指定序号的元素并且返回
list(myzip(a,b))
[('a', 'x'), ('b', 'y'), ('c', 'z')]
# 模拟未截断的zip
def myzip(*seqs,temp=None):
    seqs = [list(s) for s in seqs]
    res = []
    while any(seqs):
        yield(tuple(s.pop(0) if s else temp for s in seqs))
list(myzip(a,b))
[('a', 'x'), ('b', 'y'), ('c', 'z'), (None, 'j'), (None, 'k')]
'''
上面模拟zip和map我们都是用的all和any来控制循环条件
下面我们用字符串的长度len来控制循环条件
'''
a = 'abc'
b = 'xyzjk'
def myzip(*seqs):
    minlen = min(len(s) for s in seqs)
    return [tuple(s[i] for s in seqs) for i in range(minlen)] 
    # 注意上式中外层实一个列表解析,内层还有一个元组解析生成器表达式
myzip(a,b)
[('a', 'x'), ('b', 'y'), ('c', 'z')]
a = 'abc'
b = 'xyzjk'
def MyZipTemp(*seqs,temp = None):
    maxlen = max(len(s) for s in seqs)
    return [tuple(s[i] if i<len(s) else temp for s in seqs) for i in range(maxlen)] 
MyZipTemp(a,b)
[('a', 'x'), ('b', 'y'), ('c', 'z'), (None, 'j'), (None, 'k')]
#解析语法概括
[x*x for x in range(10)]        #列表解析
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
(x*x for x in range(10))         #解析表达式
<generator object <genexpr> at 0x000002D324961C50>
{x:x*x for x in range(10)}        #字典解析
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
#解析集合和字典解析
set(x*x for x in range(10))
{0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
dict((x,x*x) for x in range(10))
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
#针对集合和字典的扩展的解析语法
[x*x for x in range(10) if x%2==0]
[0, 4, 16, 36, 64]
{x*x for x in range(10) if x%2==0}
{0, 4, 16, 36, 64}
{x:x*x for x in range(10) if x%2==0}
{0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
[x+y for x in [1,2,3] for y in [4,5,6]]
[5, 6, 7, 6, 7, 8, 7, 8, 9]
{x+y for x in [1,2,3] for y in [4,5,6]} #字典解析去掉了重复的值
{5, 6, 7, 8, 9}
{x:y for x in [1,2,3] for y in [4,5,6]}  #因为键值的唯一性所以循环遍历的时候后面的覆盖了前面的
{1: 6, 2: 6, 3: 6}

总结:

迭代与解析是python中的重要概念迭代器与迭代工具的使用使得python语言能够很好的处理好内存问题,解析使得python语言能够处理好各种对象的转换,且python语言中的类型与对象都是变动的有了迭代与解析就能够很好的随机应变,且编写程序时候能够灵活多变。

相关文章

网友评论

      本文标题:深入理解迭代与解析

      本文链接:https://www.haomeiwen.com/subject/pvxfxqtx.html