2

Python中迭代器&生成器的"奇技淫巧"

 2 years ago
source link: https://liruilongs.github.io/2022/05/30/Python/Python%E5%AE%9E%E6%88%98%E4%B9%8B%E8%BF%AD%E4%BB%A3%E5%99%A8%E5%92%8C%E7%94%9F%E6%88%90%E5%99%A8/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Python中迭代器&生成器的"奇技淫巧"

一厢情愿,就得愿赌服输。 ——八月长安《最好的我们》

  • 和小伙伴们分享一些Python 迭代器生成器的笔记
  • 博文为《Python Cookbook》读书笔记整理
  • 博文内容涉及:
    • 不用for循环手动访问迭代器中的元素
    • 委托代理迭代(自定义可迭代对象如何迭代)
    • 用生成器创建新的迭代模式
    • 如何实现一个迭代协议
    • 定义自定义行为的生成器函数
    • 对迭代器做切片操作
    • 对可迭代对象自定义行为过滤
    • 迭代所有可能的组合或排列
    • 以索引-值对的形式迭代序列
    • 同时迭代多个可迭代对象
    • 在不同的可迭代对象中进行合并迭代
    • 解构迭代(扁平化处理嵌套型的可迭代对象)
    • 合并多个有序迭代对象,再对整个有序迭代对象进行迭代
    • 用迭代器取代while循环
  • 食用方式:
    • 了解Python基本语法即可
  • 理解不足小伙伴帮忙指正

一厢情愿,就得愿赌服输。 ——八月长安《最好的我们》


迭代器和生成器

关于迭代器小伙伴们应该不陌生,但是生成器貌似是python特有的,

Python 的迭代器语法简单,部分思想和Java8 Stream API有类似的地方(当然,Python要比Java年长),引入lambda表达式,predicate,函数式编程,行为参数化等可以做很多事情,同时和JAVA一样,对迭代行为进行了语法封装。但是本质上还是通过调用可迭代对象的迭代器来实现。

Python 的生成器yield,通过yieldyield from语法,可以很简单处理一些深度遍历的问题。

学习环境版本

┌──[[email protected]]-[~]
└─$python3
Python 3.6.8 (default, Nov 16 2020, 16:55:22)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

手动访问迭代器中的元素

当你希望遍历一个可迭代对象中的所有元素,但是却不想使用 for 循环。

为了手动的遍历可迭代对象,使用 next() 函数并在代码中捕获 StopIteration 异常。比如,下面的例子手动读取一个文件中的所有行:

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
"""
@File : Untitled-1.py
@Time : 2022/05/23 00:18:55
@Author : Li Ruilong
@Version : 1.0
@Contact : [email protected]
@Desc : None
"""

# here put the import lib


def manual_iter():
with open('/etc/passwd') as f:
try:
while True:
line = next(f)
print(line, end='')
except StopIteration:
pass
manual_iter()
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt

StopIteration 用来指示迭代的结尾。然而,如果你手动使用上面演示的next() 函数的话,你还可以通过返回一个指定值来标记结尾,比如 None 。

with open('/etc/passwd') as f:
while True:
line = next(f)
if line is None:
break
print(line, end='')

下面的列表在数据的末尾插入一个None值,那么你可以利用上面的if line is None: break来提早的结束迭代,避免异常的捕获

>>> items = [1, 2, 3,None]
>>> it = iter(items)
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>

关于迭代器的原理,有三个必不可少的元素,

  • 一个需要迭代的列表items
  • 通过iter()方法来获取一个可迭代对象的迭代器
  • 通过next()方法来获取当前可迭代的元素
>>> items = [1, 2, 3]
>>> it = iter(items)
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

委托代理迭代

当你构建了一个自定义容器对象,里面包含有列表、元组或其他可迭代对象。你想直接在你的这个新容器对象上执行迭代操作如何处理

所谓的委托代理迭代,即通过重写迭代对象的 __iter__魔法方法,增加新的迭代行为。而所谓的新迭代行为即将迭代操作代理到容器内部的对象上去。

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
"""
@File : Untitled-1.py
@Time : 2022/05/24 00:20:38
@Author : Li Ruilong
@Version : 1.0
@Contact : [email protected]
@Desc : None
"""

# here put the import lib


class Node:
def __init__(self, value):
self._value = value
self._children = []

def __repr__(self):
return 'Node({!r})'.format(self._value)

def add_child(self, node):
self._children.append(node)

def __iter__(self):
return iter(self._children)


# Example
if __name__ == '__main__':
root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
# Outputs Node(1), Node(2)
for ch in root:
print(ch)

通过运行我们可以看到

┌──[[email protected]]-[~/python_cookbook]
└─$./622.py
Node(1)
Node(2)

Python的迭代器协议需要 __iter__方法返回一个实现了next()方法迭代器对象

如果你只是迭代遍历其他容器的内容,你无须担心底层是怎样实现的。你所要做的只是传递迭代请求既可。

这里的iter()函数的使用简化了代码,iter()只是简单的通过调用s.__iter__()方法来返回对应的迭代器对象,就跟1en(s)会调用s.len()原理是一样的。

用生成器创建新的迭代模式

实现一个自定义迭代模式,跟普通的内置函数比如range() , reversed()不一样。

如果想实现一种新的迭代模式,使用一个生成器函数来定义它。下面是一个生产某个范围内浮点数的生成器:

>>> def frange(start, stop, increment):
... x = start
... while x < stop:
... yield x
... x += increment
...
>>> for n in frange(0, 4, 0.5):
... print(n)
...
0
0.5
1.0
1.5
2.0
2.5
3.0
3.5
>>>

一个函数中需要有一个 yield 语句即可将其转换为一个生成器。跟普通函数不同的是,生成器只能用于迭代操作。可以把生成器理解为函数中途的retuen, 函数块中的代码可以看做是一个流水线,那么yield就是流水线中某个环境给调用方法者的反馈,但是他并不会影响流水线。

在来看一个Demo

>>> def countdown(n):
... print('Starting to count from', n)
... while n > 0:
... yield n
... n -= 1
... print('Done!')
...
>>> c = countdown(3)
>>> next(c)
Starting to count from 3
3
>>> next(c)
2
>>> next(c)
1
>>> next(c)
Done!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> c
<generator object countdown at 0x7fd33ac44200>
>>>

一个生成器函数主要特征是它只会回应在迭代中使用到的 next 操作。一旦生成器函数返回退出,迭代终止。

实现迭代协议

构建一个能支持迭代操作的自定义对象,并希望找到一个能实现迭代协议的简单方法

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
"""
@File : Untitled-1.py
@Time : 2022/05/24 22:41:58
@Author : Li Ruilong
@Version : 1.0
@Contact : [email protected]
@Desc : None
"""

class Node:
def __init__(self, value):
self._value=value
self._children=[]
def __repr__(self):
return ' Node({!r})'.format(self._value)

def add_child(self, node):
self._children. append(node)
def __iter__(self):
return iter(self._children)

def depth_first(self):
yield self
for c in self:
yield from c.depth_first()

# Example

if __name__ == '__main__':

root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(Node(3))
child1.add_child(Node(4))
child2.add_child(Node(5))
for ch in root.depth_first():
print(ch)

在这段代码中,depth_first() 方法是点睛之笔。它首先返回自己本身并迭代每一个子节点并通过调用子节点的depth_first() 方法 (使用yield from语句) 返回对应元素

def depth_first(self):
yield self
for c in self:
yield from c.depth_first()
┌──[[email protected]]-[~/python_cookbook]
└─$vim 644.py
┌──[[email protected]]-[~/python_cookbook]
└─$chmod +x 644.py
┌──[[email protected]]-[~/python_cookbook]
└─$./644.py
Node(0)
Node(1)
Node(3)
Node(4)
Node(2)
Node(5)

Python 的迭代协议要求一个iter ()方法返回一个特殊的迭代器对象,这个迭代器对象实现了next ()方法并通过 StopIteration 异常标识迭代的完成。

反方向迭代一个序列

使用内置的reversed()函数,

>>> a= [1,2,3,4]
>>> for i in reversed(a):
... print(i)
...
4
3
2
1
>>>

反向迭代仅仅当对象的大小可预先确定或者对象实现了 __reversed__() 的特殊方法时才能生效。如果两者都不符合,那你必须先将对象转换为一个列表才行.

>>> f = open('/etc/passwd')
>>> for line in reversed(list(f)):
... print(line,end='')
...
opensips:x:997:993:OpenSIPS SIP Server:/var/run/opensips:/sbin/nologin
oprofile:x:16:16:Special user account to be used by OProfile:/var/lib/oprofile:/sbin/nologin
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin
......

自定义实现反向迭代,通过在自定义类上实现__reversed()__ 方法来实现反向迭代。

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
"""
@File : Untitled-1.py
@Time : 2022/05/24 22:41:58
@Author : Li Ruilong
@Version : 1.0
@Contact : [email protected]
@Desc : None
"""

# here put the import lib


class Countdown:
def __init__(self, start):
self.start = start

# Forward iterator
def __iter__(self):
n = self.start
while n > 0:
yield n
n -= 1
# Reverse iterator
def __reversed__(self):
n = 1
while n <= self.start:
yield n
n += 1

print(format('逆序','*>20'))
for rr in reversed(Countdown(5)):
print(rr)
print(format('正序','*>20'))
for rr in Countdown(5):
print(rr)

简单分析一下这个逆序的迭代器,魔法方法__iter__返回一个可迭代的对象,这里通过生成器来实现,当n>0的时候,通过生成器返回迭代元素。魔法方法__reversed__实现一个逆序的迭代器,原理和默认迭代器基本相同,不同的是,默认迭代器是默认调用,而逆向迭代器是主动调用

┌──[[email protected]]-[~/python_cookbook]
└─$./653.py
******************逆序
1
2
3
4
5
******************正序
5
4
3
2
1

定义带有额外状态的生成器函数

定义一个生成器函数,但是它会调用某个你想暴露给用户使用的外部状态值。

如果想让生成器暴露外部状态给用户,可以简单的将它实现为一个类,然后把生成器函数放到__iter__()方法中过去,简单来讲就是上面我们演示的代码,通过生成器来模拟next()方法行为

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
"""
@File : Untitled-1.py
@Time : 2022/05/24 22:41:58
@Author : Li Ruilong
@Version : 1.0
@Contact : [email protected]
@Desc : None
"""
# 这里的`deque(maxlen=N)创建了一个固定长度的双端队列
from collections import deque
def count(n):
while True:
yield n
n += 1

class linehistory:
def __init__(self, lines, histlen=3):
self.lines = lines
self.history = deque(maxlen=histlen)

def __iter__(self):
for lineno, line in enumerate(self.lines, 1):
self.history.append((lineno, line))
yield line

def clear(self):
self.history.clear()


if __name__ == "__main__":
with open('/etc/services') as f:
lines = linehistory(f)
for line in lines:
if '8080' in line:
for lineno, hline in lines.history:
print('{}:{}'.format(lineno, hline), end='')

这里的deque(maxlen=N)创建了一个固定长度的双端队列,用于存放要保留的数据,把文件的所有的行数据存放到lines里,默认队列的大小是3,然后通过for循环迭代,在获取迭代器的方法里,我们可以看到通过enumerate来获取迭代对象和索引,然后放到队列里,通过yield模拟next方法返回迭代元素,所以队列里存放的默认为当前元素的前两个元素,

┌──[[email protected]]-[~/python_cookbook]
└─$./662.py
553:xfs 7100/tcp font-service # X font server
554:tircproxy 7666/tcp # Tircproxy
555:webcache 8080/tcp http-alt # WWW caching service
554:tircproxy 7666/tcp # Tircproxy
555:webcache 8080/tcp http-alt # WWW caching service
556:webcache 8080/udp http-alt # WWW caching service
┌──[[email protected]]-[~/python_cookbook]
└─$(cat -n /etc/services | grep -B2 -m 1 8080;cat -n /etc/services | grep -B1 -m 2 8080)
553 xfs 7100/tcp font-service # X font server
554 tircproxy 7666/tcp # Tircproxy
555 webcache 8080/tcp http-alt # WWW caching service
554 tircproxy 7666/tcp # Tircproxy
555 webcache 8080/tcp http-alt # WWW caching service
556 webcache 8080/udp http-alt # WWW caching service

如果你在迭代操作时不使用 for 循环语句,那么你得先调用iter()函数获取迭代器,然后通过next来获取迭代元素

对迭代器做切片操作

得到一个由迭代器生成的切片对象,但是标准切片操作并不能做到。

函数itertools.islice()正好适用于在迭代器和生成器上做切片操作

>>> def count(n):
... while True:
... yield n
... n += 1
...
>>> c = count(0)
>>> c[10:20]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable
>>> import itertools
>>> for x in itertools.islice(c,5,10):
... print(x)
...
5
6
7
8
9
>>>

迭代器和生成器不能使用标准的切片操作,因为它们的长度事先我们并不知道 (并且也没有实现索引)。函数islice()返回一个可以生成指定元素的迭代器,它通过遍历并丢弃直到切片开始索引位置的所有元素。然后才开始一个个的返回元素,并直到切片结束索引位置。islice() 会消耗掉传入的迭代器中的数据。必须考虑到迭代器是不可逆的这个事实。

跳过可迭代对象中的前一部分元素

遍历一个可迭代对象,但是它开始的某些元素你并不感兴趣,想跳过它们

itertools 模块中有一些函数可以完成这个任务。 首先介绍的是itertools.dropwhile() 函数。使用时,你给它传递一个函数对象和一个可迭代对象。它会返回一个迭代器对象,丢弃原有序列中直到函数返回 True 之前的所有元素,然后返回后面所有元素。

类似一个过滤器,返回满足条件的数据

┌──[[email protected]]-[~/python_cookbook]
└─$tee temp.txt <<- EOF
> #sdfsdf
> #dsfsf
> #sdfsd
> sdfdsf
> sdfs
> EOF
#sdfsdf
#dsfsf
#sdfsd
sdfdsf
sdfs
>>> with open('temp.txt') as f:
... for line in dropwhile(lambda line: line.startswith('#'),f):
... print(line,end=' ')
...
sdfdsf
sdfs
>>>

如果你已经明确知道了要跳过的元素的个数的话,那么可以使用 itertools.islice() 来代替

>>> from itertools import islice
>>> items = ['a', 'b', 'c', 1, 4, 10, 15]
>>> for x in islice(items, 3, None):
... print(x)
...
1
4
10
15
>>>

islice() 函数最后那个 None 参数指定了你要获取从第 3 个到最后的所有元素,如果 None 和 3 的位置对调,意思就是仅仅获取前三个元素恰恰相反,(这个跟切片的相反操作 [3:] 和 [:3] 原理是一样的)。

迭代所有可能的组合或排列

想迭代遍历一个集合中元素的所有可能的排列或组合

itertools模块提供了三个函数来解决这类问题。其中一个是itertools.permutations(),它接受一个集合并产生一个元组序列,每个元组由集合中所有元素的一个可能排列组成。也就是说通过打乱集合中元素排列顺序生成一个元组

>>> items = ['a','b','c']
>>> from itertools import permutations
>>> for p in permutations(items):
... print(p)
...
('a', 'b', 'c')
('a', 'c', 'b')
('b', 'a', 'c')
('b', 'c', 'a')
('c', 'a', 'b')
('c', 'b', 'a')
>>>

如果你想得到指定长度的所有排列,你可以传递一个可选的长度参数。

>>> for p in permutations(items,2):
... print(p)
...
('a', 'b')
('a', 'c')
('b', 'a')
('b', 'c')
('c', 'a')
('c', 'b')
>>>

使用itertools.combinations()可得到输入集合中元素的所有的组合

>>> from itertools import combinations
>>> for i in combinations(items,3):
... print(i)
...
('a', 'b', 'c')
>>> for i in combinations(items,2):
... print(i)
...
('a', 'b')
('a', 'c')
('b', 'c')
>>> for i in combinations(items,1):
... print(i)
...
('a',)
('b',)
('c',)
>>>

这里需要注意的一点,combinations必须要指定参与组合的元素个数

>>> for i in combinations(items):
... print(i)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Required argument 'r' (pos 2) not found

以索引-值对的形式迭代序列

迭代一个序列的同时跟踪正在被处理的元素索引。

内置的 enumerate() 函数可以很好的解决这个问题:

>>> my_list = ['a', 'b', 'c']
>>> for idx,item in enumerate(my_list):
... print(idx,item)
...
0 a
1 b
2 c
>>>

可以指定索引

>>> for idx,item in enumerate(my_list,1):
... print(idx,item)
...
1 a
2 b
3 c
>>>

在处理列表嵌套的元组的时候需要注意的问题

>>> data = [ (1, 2), (3, 4), (5, 6), (7, 8) ]
>>> for n, (x, y) in enumerate(data):
... print(n,x,y)
...
0 1 2
1 3 4
2 5 6
3 7 8
>>> for n, x, y in enumerate(data):
... print(n,x,y)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 2)
>>>

同时迭代多个序列

同时迭代多个序列,每次分别从一个序列中取一个元素

为了同时迭代多个序列,使用 zip() 函数

>>> xpts = [1, 5, 4, 2, 10, 7]
>>> ypts = [101, 78, 37, 15, 62, 99]
>>> for x, y in zip(xpts, ypts):
... print(x,y)
...
1 101
5 78
4 37
2 15
10 62
7 99
>>>

zip(a,b)会生成一个可返回元组(x,y)的迭代器,其中x来自a,y来自b。一旦其中某个序列到底结尾,迭代宣告结束。因此迭代长度跟参数中最短序列长度一致。

>>> xpts = [1, 5, 4, 2, 10, 7]
>>> ypts = [101, 78, 37]
>>> for x, y in zip(xpts, ypts):
... print(x,y)
...
1 101
5 78
4 37
>>>

如果这个不是你想要的效果,那么还可以使用itertools.zip_longest()函数来代替。

>>> from itertools import zip_longest
>>> for i in zip_longest(xpts,ypts):
... print(i)
...
(1, 101)
(5, 78)
(4, 37)
(2, None)
(10, None)
(7, None)
>>>

使用 zip()可以将两个list打包并生成一个字典:

>>> xpts = [1, 5, 4, 2, 10, 7]
>>> ypts = [101, 78, 37]
>>> dict(zip(xpts,ypts))
{1: 101, 5: 78, 4: 37}
>>>

zip() 会创建一个迭代器来作为结果返回。如果你需要将结对的值存储在列表中,要使用 list() 函数

>>> zip(xpts,ypts)
<zip object at 0x7f413b75e1c8>
>>> list(zip(xpts,ypts))
[(1, 101), (5, 78), (4, 37)]
>>> dict(zip(xpts,ypts))
{1: 101, 5: 78, 4: 37}
>>>

在不同的容器中进行合并迭代

想在多个对象执行相同的操作,但是这些对象在不同的容器中,你希望代码在不失可读性的情况下避免写重复的循环。

itertools.chain() 方法可以用来简化这个任务。它接受一个可迭代对象列表作为输入,并返回一个迭代器,有效的屏蔽掉在多个容器中迭代细节

>>> from itertools import chain
>>> a = [1, 2, 3, 4]
>>> b = ['x', 'y', 'z']
>>> for x in chain(a, b):
... print(x)
...
1
2
3
4
x
y
z
>>>

其他的容器也可以

# Various working sets of items
active_items = set()
inactive_items = set()
# Iterate over all items
for item in chain(active_items, inactive_items):
# Process item

扁平化处理嵌套型的序列

将一个多层嵌套的序列展开成一个单层列表

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
"""
@File : Untitled-1.py
@Time : 2022/05/30 03:10:41
@Author : Li Ruilong
@Version : 1.0
@Contact : [email protected]
@Desc : None
"""

# here put the import lib

from collections import Iterable


def flatten(items, ignore_types=(str, bytes)):
for x in items:
if isinstance(x, Iterable) and not isinstance(x, ignore_types):
yield from flatten(x)
else:
yield x


items = [1, 2, [3, 4, [5, 6], 7], 8]
# Produces 1 2 3 4 5 6 7 8
for x in flatten(items):
print(x)

items = ['Dave', 'Paula', ['Thomas', 'Lewis']]
for x in flatten(items):
print(x)

简单的梳理一下这个方法,利用递归和yield from 的语法,深度遍历所以的元素,返回的遍历的每个元素,需要说明isinstance(x, Iterable)用于判断某个元素是否可迭代。

额外的参数ignore_types和检测语句isinstance(x,ignore_types)用来将字符串和字节排除在可迭代对象外,防止将它们再展开成单个的字符。这样的话字符串数组就能最终返回我们所期望的结果了。比如:

┌──[[email protected]]-[~]
└─$./listtolist.py
1
2
3
4
5
6
7
8
Dave
Paula
Thomas
Lewis

关于语句yield from在你想在生成器中调用其他生成器作为子例程的时候非常有用,当然也可以通过for循环来实现

合并多个有序序列,再对整个有序序列进行迭代

有多个排序序列,想将它们合并后得到一个排序序列并在上面迭代遍历

我们可以通过heapq.merge() 函数来实现

>>> import heapq
>>> a = [1, 4, 7, 10]
>>> b = [2, 5, 8, 11]
>>> for c in heapq.merge(a,b):
... print(c)
...
1
2
4
5
7
8
10
11
>>>

heapq.merge 可迭代特性意味着它不会立马读取所有序列。这就意味着你可以在非常长的序列中使用它,而不会有太大的开销,但是有一点要强调的是heapq.merge()需要所有输入序列必须是排过序的。特别的,它并不会预先读取所有数据到堆栈中或者预先排序,也不会对输入做任何的排序检测。它仅仅是检查所有序列的开始部分并返回最小的那个,这个过程一直会持续直到所有输入序列中的元素都被遍历完。

>>> import  heapq
>>> with open('16943_26281_10022_20220523_DATA.txt','rt') as file1,\
... open('16943_26281_10022_20220523_DATA.txt','rt') as file2,\
... open('16943_26281_10022_20220523_DATA_2022.txt','wt') as file3:
... for c in heapq.merge(file1,file2):
... file3.write(c)
...

这个和前面的itertools.chain() 有些类似,同样是合并迭代容器,不同的是迭代方式不同,一个是简单的合并,一个是保持有序的合并

┌──[[email protected]]-[~]
└─$wc -l 16943_26281_10022_20220523_DATA_2022.txt
1810 16943_26281_10022_20220523_DATA_2022.txt
┌──[[email protected]]-[~]
└─$wc -l 16943_26281_10022_20220523_DATA.txt
905 16943_26281_10022_20220523_DATA.txt
┌──[[email protected]]-[~]
└─$

用迭代器取代while循环

使用 while 循环来迭代处理数据,因为它需要调用某个函数或者和一般迭代模式不同的测试条件。能不能用迭代器来重写这个循环呢?

>>> import  sys
>>> f = open('/etc/passwd')
>>> for i in iter(lambda : f.read(10),''):
... n = sys.stdout.write(i)
...
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologi

iter函数一个鲜为人知的特性是它接受一个可选的callable 对象一个标记(结尾)值作为输入参数。当以这种方式使用的时候,它会创建一个迭代器,这个迭代器会不断调用callable对象直到返回值和标记值相等为止。

我们来看一下对应的方法,通过open函数获取当前文件的内容,我们一般会通过whit的方式来接收,这里我们通过iter函数,利用lambda表达式获取当前文件的内容,以及结束的标识,反复迭代输出文件内容。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK