guxh的python笔记四:迭代
1,可迭代对象iterable,迭代器iterator,生成器generator
可迭代对象iterable:
- 实现__iter__方法的类。__iter__方法返回iterator或者generator。
- 实现__getitem__方法的类。其参数是从0开始的索引。
迭代器Iterator:
- 实现__iter__方法和__next__方法的类(即自遍历)。其中__iter__方法返回iterator自身,__next__方法不断返回元素直到没有元素后抛出StopIteration异常。
生成器generator:
- 一个含有yield句法的函数。generator支持next(),属于iterator。
上述三种类型都可作用于for循环。
备注:
1)list、dict、str虽然是iterable,却不是iterator,为什么呢?因为iterator表示的是一个数据流,可以被next()调用不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把数据流看作一个序列,但我们无法提前知道序列长度,只有不断通过next()进行下一个计算。iterator甚至可以表示无限大的数据流,list不可能无限大。
2)iterable没有__next__(自遍历)。
3)iterator没有__getitem__不支持[]分量取值和切片,没有__len__不支持获取长度。
4)可获得iterator的内置方法:zip,enumerate,map,filter,reversed。
5)可获得iterable的内置方法:range。
6)iterator(包括generator)只能被消费一次,第二次调用时会直接返回空。
2,可迭代对象iterable与迭代器iterator的关系
关于可迭代对象iterable与迭代器iterator的实现细节可参考“3实现可迭代的方法”中的经典版方法。
2.1,iterable
s = 'abc' # s是个iterable,<class 'str'>
s是iterable,可以被迭代:
for i in s:
print(i)
2.2,iterable可迭代的本质
迭代的本质是从iterable获取iterator(iterable的__iter__方法return了一个iterator),然后再不断使用iterator的next()方法获取值,直到StopIteration异常结束:
sit = iter(s) # it是个iterator,<class 'str_iterator'>
while True:
try:
print(next(sit))
except StopIteration:
break
next()可以接收默认值,当运行到StopIteration时就返回该默认值,上述代码页可以改写成这样:
sit = iter(s) # it是个iterator,<class 'str_iterator'>
i = next(sit)
while i:
print(i)
i = next(sit, None)
手工next()的话是这样的过程:
>>> s = 'abc'
>>> sit = iter(s)
>>> next(sit)
'a'
>>> next(sit)
'b'
>>> next(sit)
'c'
>>> next(sit)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
另外,如果一个obj没有实现__iter__方法,即无法通过iter(obj)获取到迭代器,但是它实现了__getitem__,那它也是可以被迭代的。
2.3,iterable和iterator判断方法
s可迭代却不能被next自遍历。sit可以迭代也可以被next自遍历。
可以用抽象基类判断iterable,iterator:
from collections import abc
print(isinstance(s, abc.Iterable)) # True
print(isinstance(s, abc.Iterator)) # False
print(isinstance(sit, abc.Iterable)) # True
print(isinstance(sit, abc.Iterator)) # True
或者用迭代的协议去判断:
print(hasattr(s, '__iter__')) # True
print(hasattr(s, '__next__')) # False
print(hasattr(sit, '__iter__')) # True
print(hasattr(sit, '__next__')) # True
3,实现对象可迭代的方法
实现一个序列类型,接受输入值x,返回从x到11的值。
3.1,方法一:python序列鸭子类型
python在尝试迭代对象时,找不到__iter__就会去调用__getitem__,__getitem__实现从0开始的索引取值即可
class Foo:
def __init__(self, data):
self.data = data def __getitem__(self, i):
return range(self.data, 11)[i]
3.2,方法二:经典版
iterable+iterator,构建Foo的iterator,缺点是代码量大。
关键点:iterable的__iter__返回iterator;iterator的__iter__返回self,__next__逐个取值。
class Foo:
def __init__(self, data):
self.data = list(data) def __iter__(self): # iterable中的__iter__返回iterator
return Foo_iterator(self.data) class Foo_iterator:
def __init__(self, data):
self.data = data def __iter__(self): # iterator中的__iter__返回自己
return self def __next__(self): # iterator实现__next__
if self.data > 10:
raise StopIteration
else:
num = self.data
self.data += 1
return num
自己实现的iterable/iterator:
f = Foo(1)
fit = iter(f)
print(type(f)) # <class '__main__.Foo'>
print(type(fit)) # <class '__main__.Foo_iterator'>
对比下内置的iterable/iterator:
s = 'abc'
sit = iter(s)
print(type(s)) # <class 'str'>
print(type(sit)) # <class 'str_iterator'>
r = range(10)
rit = iter(r)
print(type(r)) # <class 'range'>
print(type(rit)) # <class 'range_iterator'>
3.3,方法三:糟糕版
Foo自己实现__next__和__iter__,让Foo既是iterable,也是自己的iterator,混淆了iterable和iterator,糟糕不推荐
class Foo:
def __init__(self, data):
self.data = data def __iter__(self):
return self def __next__(self):
if self.data > 10:
raise StopIteration
else:
num = self.data
self.data += 1
return num
3.4,方法四:generator版
用generator实现iterable中的__iter__方法
class Foo:
def __init__(self, data):
self.data = data def __iter__(self):
for i in range(self.data, 11):
yield i
iter()返回的类名是generator,而不是iterator:
f = Foo(1)
fit = iter(f)
print(type(f)) # <class '__main__.Foo'>
print(type(fit)) # <class 'generator'>
备注:Foo中的__iter__获取数据时,用的是惰性获取range(非惰性就是list(range(self.data, 11)))。一般推荐用惰性函数实现,例如用finditer替代findall。
3.5,方法五:生成器表达式
class Foo:
def __init__(self, data):
self.data = data def __iter__(self):
return (i for i in range(self.data, 11))
3.6,方法六:委托
将iter方法委托其他iterable背后的iterator,range()是个iterable,可以通过iter(range())获取到它的iterator。
下例将Foo的迭代器实现委托给了range()的迭代器:
class Foo:
def __init__(self, data):
self.data = data def __iter__(self):
return iter(range(self.data, 11))
iter()返回的是range的iterator:
f = Foo(3)
fit = iter(f)
print(isinstance(fit, abc.Iterator)) # True
print(type(fit)) # <class 'range_iterator'>
4,生成器函数
4.1,生成器函数
如果函数返回的是列表,可以用generator改写,优点是代码简介,节省内存。
函数中只要出现了yield就会转变为一个generator,其核心特性是只会在响应迭代过程中的next操作时才会运行,和iterator一致。
generator属于iterator,3.4中就直接返回了generator作为iterator。
并发的协程还会继续深入讨论generator的yield语法。
这里写看看适合单独编写一个generator的场景。
例如输入一个序列,返回其中的偶数列表:
def fun(components):
result = []
for c in components:
if divmod(c, 2)[1] == 0:
result.append(c)
return result
生成器改写:
def fun(components):
for c in components:
if divmod(c, 2)[1] == 0:
yield c
调用生成器:
f = fun(range(20))
print(f) # <generator object fun at 0x0000022C2F1AB258>
print(list(f)) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
print(isinstance(f, abc.Iterator)) # f是个iterator,因为generator属于iterator
4.2,生成器应用 - 生成器实现管道
脚本所在目录下有foo和bar两个文件夹,分别有多个防火墙日志文件,需要取出里面的access-list配置条目,可以:
def gen_find(filepat, top):
# filepat: 文件名匹配模式;top:os.walk遍历的top目录。返回所有符合条件的文件名路径。
for path, dirlist, filelist in os.walk(top):
for name in [file for file in filelist if re.search(filepat, file)]:
yield os.path.join(path, name) def gen_opener(files):
# 返回所有文件生成器。
for file in files:
with open(file, 'rt') as f:
yield f # f是个生成器,只能for循环被消费一次 def gen_concatenate(iterators):
for it in iterators:
yield from it def gen_grep(pattern, lines):
for line in lines:
if re.search(pattern, line):
yield line lognames = gen_find('防火墙.*log', os.getcwd())
files = gen_opener(lognames)
lines = gen_concatenate(files)
acclines = gen_grep('^access-list', lines)
for each_acc in acclines:
print(each_acc)
4.3,yield from扁平化处理嵌套序列
def flatten(items):
for x in items:
if isinstance(x, Iterable):
yield from flatten(x) # 递归
else:
yield x
>>> list(flatten([1, (3, 4, 5, {6, 7})]))
[1, 3, 4, 5, 6, 7]
5,其他
5.1,iterator(包括generator)只能被迭代一次
iterable可以被多次迭代:
>>> l = range(5)
>>> list(l)
[0, 1, 2, 3, 4]
>>> list(l)
[0, 1, 2, 3, 4]
iterator只能被迭代一次:
>>> lit = iter(range(5)) # lit是iterator
>>> list(lit)
[0, 1, 2, 3, 4]
>>> list(lit)
[]
generator也是只能迭代一次:
>>> ge = (i for i in range(5))
>>> list(ge)
[0, 1, 2, 3, 4]
>>> list(ge)
[]
5.2,iter(it,x)函数的哨符值
有第二个参数时,it必须callable:
>>> iter(range(10))
<range_iterator object at 0x0000026F8968AEF0>
>>> iter(range(10), 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: iter(v, w): v must be callable
作改动:
l = iter(range(10))
def run():
return next(l)
lit = iter(run, 3) # 遇到3时终止
print(list(lit)) # [0, 1, 2]
5.3,生成器表达式高效处理文本
下面代码处理文本时,会惰性处理,不会事先读取文本至内存:
with open(filename) as f:
lines = (line.strip() for line in f)
for line in lines:
......
guxh的python笔记四:迭代的更多相关文章
- guxh的python笔记一:数据类型
1,基本概念 1.1,数据类型 基本数据类型:字符串,数字,布尔等 引用数据类型:相对不可变(元组),可变(列表,字典,集合等) 基本数据类型存放实际值,引用数据类型存放对象的地址(即引用) ==:判 ...
- guxh的python笔记二:函数基础
1,函数的参数 1.1,查看函数的参数类型 def run(a, *args, b, **kwargs): return a + b 可以通过如下方式查看参数类型: import inspect k ...
- guxh的python笔记五:面向对象
1,面向对象编程思想 类:一类具有相同属性的抽象 属性(静态属性):实例变量.类变量.私有属性 方法(动态属性):构造函数.析构函数(默认就有).函数.私有函数 对象/实例:类经过实例化后,就是对象/ ...
- guxh的python笔记三:装饰器
1,函数作用域 这种情况可以顺利执行: total = 0 def run(): print(total) 这种情况会报错: total = 0 def run(): print(total) tot ...
- guxh的python笔记十:包和模块
1,包和模块 包package:本质就是一个文件夹/目录,必须带一个__init.__.py的文件 模块module:.py结尾的python文件 2,导入方法 import pandas, coll ...
- guxh的python笔记七:抽象基类
1,鸭子类型和白鹅类型 1.1,白鹅类型 白鹅类型对接口有明确定义,比如不可变序列(Sequence),需要实现__contains__,__iter__,__len__,__getitem__,__ ...
- guxh的python笔记十一:异常处理
1,抓错方法 name = [0, 1, 2] try: name[3] except IndexError as exc: # 抓单个错误,打印错误信息e print(exc) except (In ...
- guxh的python笔记八:特殊方法
1,类的特殊方法 新建一个类,本章内容中的特殊方法如果不创建类或新增方法,默认使用的就是下面的类: class Foo: """this is Foo"&q ...
- guxh的python笔记六:类的属性
1,私有属性 class Foo: def __init__(self, x): self.x = x 类的属性在实例化之后是可以更改的: f = Foo(1) print(f.x) # 1 f.x ...
随机推荐
- [No0000198]swagger api一键导入postman
在用postman进行接口测试时,对于参数较多的接口时第一次添加接口参数是比较繁琐的,可利用swagger一键导入api接口,事例如下: 1.获取swagger地址 2.打开postman,点击imp ...
- Recurrent NN vs Recursive NN
https://www.bilibili.com/video/av9770302/?p=8 李宏毅深度学习 图很清楚的反映出两者的不同 Recurrent可以看成Recursive的特殊形式,即以特定 ...
- 实验二:MAL——简单后门 by:赵文昊
实验二:MAL--简单后门 一.后门是什么? 哪里有后门呢? 编译器留后门 操作系统留后门 最常见的当然还是应用程序中留后门 还有就是潜伏于操作系统中或伪装为特定应用的专用后门程序. 二.认识netc ...
- linux 逆向映射机制浅析
2017-05-20 聚会回来一如既往的看了会羽毛球比赛,然后想到前几天和朋友讨论的逆向映射的问题,还是简要总结下,免得以后再忘记了!可是当我添加时间……这就有点尴尬了……520还在写技术博客…… 闲 ...
- 详解FPGA中的建立时间与保持时间
概念对于一个数字系统而言,建立时间和保持时间可以说是基础中的基础,这两个概念就像是数字电路的地基,整个系统的稳定性绝大部分都取决于是否满足建立时间和保持时间.但是对于绝大部分包括我在内的初学者来说,建 ...
- html5中JavaScript删除全部节点
如果div里有这么些内容: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type ...
- 基于Spark Streaming + Canal + Kafka对Mysql增量数据实时进行监测分析
Spark Streaming可以用于实时流项目的开发,实时流项目的数据源除了可以来源于日志.文件.网络端口等,常常也有这种需求,那就是实时分析处理MySQL中的增量数据.面对这种需求当然我们可以通过 ...
- 小程序 切换到tabBar页面不刷新问题
小程序跳转的几种方式有wx.navigateTo,wx.redirectTo,wx.reLaunch,wx.switchTab等.下面我们重点研究切换到tabBar的两种方式. wx.switchTa ...
- JavaScript中各种对象之间的关系
上图: 此外,补充一下图中用到的概念: 1.内置(Build-in)对象与原生(Naitve)对象的区别在于:前者总是在引擎初始化阶段就被创建好的对象,是后者的一个子集:而后者包括了一些在运行过程中动 ...
- “流式”前端构建工具——gulp.js 简介
Grunt 一直是前端领域构建工具(任务运行器或许更准确一些,因为前端构建只是此类工具的一部分用途)的王者,然而它也不是毫无缺陷的,近期风头正劲的 gulp.js 隐隐有取而代之的态势.那么,究竟是什 ...