Day4 函数、列表生成式、生成器、迭代器
温故而知新:
1. 集合
主要作用:
- 去重
- 关系测试, 交集\差集\并集\反向(对称)差集
2. 元组
只读列表,只有count, index 2 个方法
作用:如果一些数据不想被人修改, 可以存成元组,比如身份证列表
3. 字典(key-value对)
- 特性:
- 无顺序
- 去重
- 查询速度快,比列表快多了
4. 字符编码
先说python2
- py2里默认编码是ascii
- 文件开头那个编码声明是告诉解释这个代码的程序 以什么编码格式 把这段代码读入到内存,因为到了内存里,这段代码其实是以bytes二进制格式存的,不过即使是2进制流,也可以按不同的编码格式转成2进制流,你懂么?
- 如果在文件头声明了#_*_coding:utf-8*_,就可以写中文了, 不声明的话,python在处理这段代码时按ascii,显然会出错, 加了这个声明后,里面的代码就全是utf-8格式了
- 在有#_*_coding:utf-8*_的情况下,你在声明变量如果写成name=u"大保健",那这个字符就是unicode格式,不加这个u,那你声明的字符串就是utf-8格式
- utf-8 to gbk怎么转,utf8先decode成unicode,再encode成gbk
再说python3
- py3里默认文件编码就是utf-8,所以可以直接写中文,也不需要文件头声明编码了,干的漂亮
- 你声明的变量默认是unicode编码,不是utf-8, 因为默认即是unicode了(不像在py2里,你想直接声明成unicode还得在变量前加个u), 此时你想转成gbk的话,直接your_str.encode("gbk")即可以
- 但py3里,你在your_str.encode("gbk")时,感觉好像还加了一个动作,就是就是encode的数据变成了bytes里,我擦,这是怎么个情况,因为在py3里,str and bytes做了明确的区分,你可以理解为bytes就是2进制流,你会说,我看到的不是010101这样的2进制呀, 那是因为python为了让你能对数据进行操作而在内存级别又帮你做了一层封装,否则让你直接看到一堆2进制,你能看出哪个字符对应哪段2进制么?什么?自己换算,得了吧,你连超过2位数的数字加减运算都费劲,还还是省省心吧。
- 那你说,在py2里好像也有bytes呀,是的,不过py2里的bytes只是对str做了个别名(python2里的str就是bytes, py3里的str是unicode),没有像py3一样给你显示的多出来一层封装,但其实其内部还是封装了的。 这么讲吧, 无论是2还是三, 从硬盘到内存,数据格式都是 010101二进制到-->b'\xe4\xbd\xa0\xe5\xa5\xbd' bytes类型-->按照指定编码转成你能看懂的文字
编码应用比较多的场景应该是爬虫了,互联网上很多网站用的编码格式很杂,虽然整体趋向都变成utf-8,但现在还是很杂,所以爬网页时就需要你进行各种编码的转换,不过生活正在变美好,期待一个不需要转码的世界。
最后,编码is a piece of fucking shit, noboby likes it.
函数基本语法及特性
背景提要
现在老板让你写一个监控程序,监控服务器的系统状况,当cpu\memory\disk等指标的使用量超过阀值时即发邮件报警,你掏空了所有的知识量,写出了以下代码
while True: if cpu利用率 > 90%: #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 if 硬盘使用空间 > 90%: #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 if 内存占用 > 80%: #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接
这样写是不是有问题呢?是的,存在以下两个问题
- 代码重复过多,一个劲的copy and paste不符合高端程序员的气质
- 如果日后需要修改发邮件的这段代码,比如加入群发功能,那你就需要在所有用到这段代码的地方都修改一遍
要想解决这个问题,其实很简单,只需要把重复的代码提取出来,放在一个公共的地方,起个名字,以后谁想用这段代码,就通过这个名字调用就行了,如下:
def 发送邮件(内容) #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 while True: if cpu利用率 > 90%: 发送邮件('CPU报警') if 硬盘使用空间 > 90%: 发送邮件('硬盘报警') if 内存占用 > 80%: 发送邮件('内存报警')
函数是什么?
定义:函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。
特性:
- 减少重复代码
- 使程序变的可扩展
- 使程序变得易维护
语法:定义函数使用 def 关键字,一般格式如下:
def 函数名(参数列表): 函数体
注意:你可以定义一个由自己想要功能的函数,以下是简单的规则:
函数定义:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
- .函数名:任何有效的Python标识符
- 参数列表:调用参数时传递给它的值
- 参数的个数大于等于零
- 多个参数由逗号分隔
- 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
- 形式参数:定义函数时,函数名后面圆括号中的变量,简称“形参”。形参只在函数内部有效!
- 实际参数:调用函数时,函数名后面圆括号中的变量,简称“实参”
- 函数体:函数被调用时执行的代码,由一个或多个语句组成。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- 默认情况下,参数值和参数名称是按函数声明中定义的的顺序匹配起来的。
- return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None
- Python中的函数可以返回任意数据类型
- 函数调用的一般形式:函数名(参数)
函数分为两部分:函数定义和函数调用【以上是函数定义】
如下所示:
def hello(): print("Hello World!") hello() #函数调用
下面是有参数的函数定义和调用:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 计算面积函数 def area(width, height): return width * height def print_welcome(name): print("Welcome", name) print_welcome("Runoob") w = 4 h = 5 print("width =", w, " height =", h, " area =", area(w, h))
函数参数(形参和实参)
1.形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量
2.实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值
位置参数和关键字(标准调用:实参与形参位置一一对应;关键字调用:位置无需固定)
形参又分为三中不同的参数:
- 普通参数(位置参数)
- 默认参数
- 动态参数
如下所示:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def hello(): print("say hello") hello()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def hello(name,age): print("name:",name," age:",age) hello(12,"张安")
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
发现 country 这个参数 基本都 是"CN", 就像我们在网站上注册用户,像国籍这种信息,你不填写,默认就会是 中国, 这就是通过默认参数实现的,把country变成默认参数非常简单。
def stu_register(name,age,course,country="CN"):
这样,这个参数在调用时不指定,那默认就是CN,指定了的话,就用你指定的值。
另外,在没有动态参数的情况下,你可能注意到了,在把country变成默认参数后,我同时把它的位置移到了最后面!
若你的函数在定义时不确定用户想传入多少个参数,就可以使用非固定参数,非固定参数是元组!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def stu_register(name,age,*args): # *args 会把多传入的参数变成一个元组形式 print(name,age,args) stu_register("Alex",22) #输出 #Alex 22 () #后面这个()就是args,只是因为没传值,所以为空 stu_register("Jack",32,"CN","Python") #输出 # Jack 32 ('CN', 'Python')
注意:当形参是非固定参数的时候,实参就不要使用关键参数了,因为
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def stu_register(name, age, *args, **kwargs): # *kwargs 会把多传入的参数变成一个dict形式 print(name, age, args, kwargs) stu_register("Alex", 22) # 输出 # Alex 22 () {}#后面这个{}就是kwargs,只是因为没传值,所以为空 stu_register("Jack", 32, "CN", "Python", sex="Male", province="ShanDong") # 输出 # Jack 32 ('CN', 'Python') {'province': 'ShanDong', 'sex': 'Male'}
从上面这个例子中可以看出,*args接收的是普通参数,**kwargs是接收的是关键参数!
实参:
- 普通参数 按着顺序给形参传值就好
- 关键参数 关键参数要放在实参的最后,否则有错!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def stu_register(name, age,sex): # *kwargs 会把多传入的参数变成一个dict形式 print(name, age,sex) stu_register("张三",sex="Man",age=12)
总结:对于实参,关键参数要放在实参最后!
全局、局部变量、参数
代码演示如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
name = "Alex Li" def change_name(name1): sex = "Man" global name name = "hah" print(name1,sex,name) print("after change", name) change_name("汪峰")
区分:参数变量和局部变量、全局变量
返回值
函数按着有无返回值分为:有返回值函数和无返回值函数,要想获取函数的执行结果,就可以用return语句把结果返回
注意:
- 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,so 也可以理解为 return 语句代表着函数的结束
- 如果未在函数中指定return,那这个函数的返回值为None
强行插入知识点: 嵌套函数
看上面的标题的意思是,函数还能套函数?of course
name = "Alex" def change_name(): name = "Alex2" def change_name2(): name = "Alex3" print("第3层打印", name) change_name2() # 调用内层函数 print("第2层打印", name) change_name() print("最外层打印", name)
此时,在最外层调用change_name2()会出现什么效果?
没错, 出错了, 为什么呢?
嵌套函数的用法会了,但它有什么用呢?下节课揭晓。。。
匿名函数
匿名函数就是不需要显式的指定函数
匿名函数的语法:
: 为分隔符,左侧为参数【普通参数,默认参数,可变参数都可以】,右侧为表达式
为什么要用匿名函数?
1. 程序一次行使用,所以不需要定义函数名,节省内存中变量定义空间
2. 如果想让程序更加简洁时。
# 这段代码 def calc(n): return n ** n print(calc(10)) # 换成匿名函数 calc = lambda n: n ** n print(calc(10))
匿名函数的几个规则:
1. 一般也就一行表达式,必须有返回值
2. 不能有return
3. 可以没有参数,可以有一个或多个参数
eg:无参匿名函数
t = lambda : True #分号前无任何参数
带参匿名函数
lambda x: x**3 #一个参数 lambda x,y,z:x+y+z #多个参数 lambda x,y=3: x*y #允许参数存在默认值
匿名函数调用
#直接赋值给一个变量,然后再像一般函数调用
c = lambda x,y,z: x*y*z d= c(2,3,4) print(d)
直接后面传递实参
(lambda x,y: x if x> y else y)(101,102)
嵌套使用
def increment(n): return lambda x: x+n f=increment(4) f(2)
案例:求最小值
lower = lambda x,y: x if x<y else y c =lower('aa','ab') print(c)
函数式编程介绍
函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
一、定义
简单说,"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。
主要思想是把运算过程尽量写成一系列嵌套的函数调用。举例来说,现在有这样一个数学表达式:
(1 + 2) * 3 - 4
传统的过程式编程,可能这样写:
var a = 1 + 2;
var b = a * 3;
var c = b - 4;
函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:
var result = subtract(multiply(add(1,2), 3), 4);
这段代码再演进以下,可以变成这样
add(1,2).multiply(3).subtract(4)
这基本就是自然语言的表达了。再看下面的代码,大家应该一眼就能明白它的意思吧:
merge([1,2],[3,4]).sort().search("2")
因此,函数式编程的代码更容易理解。
要想学好函数式编程,不要玩py,玩Erlang,Haskell, 好了,我只会这么多了。。。
高阶函数
变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数或者函数的返回值为函数,这种函数就称之为高阶函数。
def add(x,y,f): return f(x) + f(y) res = add(3,-6,abs) print(res)
内置函数
内置参数详解 https://docs.python.org/3/library/functions.html?highlight=built#ascii
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#compile f = open("函数递归.py") data =compile(f.read(),'','exec') exec(data) #print msg = "又回到最初的起点" f = open("tofile","w") print(msg,"记忆中你青涩的脸",sep="|",end="",file=f) # #slice # a = range(20) # pattern = slice(3,8,2) # for i in a[pattern]: #等于a[3:8:2] # print(i) # # #memoryview #usage: #>>> memoryview(b'abcd') #<memory at 0x104069648> #在进行切片并赋值数据时,不需要重新copy原列表数据,可以直接映射原数据内存, import time for n in (100000, 200000, 300000, 400000): data = b'x'*n start = time.time() b = data while b: b = b[1:] print('bytes', n, time.time()-start) for n in (100000, 200000, 300000, 400000): data = b'x'*n start = time.time() b = memoryview(data) while b: b = b[1:] print('memoryview', n, time.time()-start)
递归
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
def calc(n): print(n) if int(n / 2) == 0: return n return calc(int(n / 2)) calc(10)
递归特性:
1. 必须有一个明确的结束条件
2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
堆栈扫盲http://www.cnblogs.com/lln7777/archive/2012/03/14/2396164.html
递归函数实际应用案例,二分查找
data = [1, 3, 6, 7, 9, 12, 14, 16, 17, 18, 20, 21, 22, 23, 30, 32, 33, 35] def binary_search(dataset,find_num): print(dataset) if len(dataset) >1: mid = int(len(dataset)/2) if dataset[mid] == find_num: #find it print("找到数字",dataset[mid]) elif dataset[mid] > find_num :# 找的数在mid左面 print("\033[31;1m找的数在mid[%s]左面\033[0m" % dataset[mid]) return binary_search(dataset[0:mid], find_num) else:# 找的数在mid右面 print("\033[32;1m找的数在mid[%s]右面\033[0m" % dataset[mid]) return binary_search(dataset[mid+1:],find_num) else: if dataset[0] == find_num: #find it print("找到数字啦",dataset[0]) else: print("没的分了,要找的数字[%s]不在列表里" % find_num) binary_search(data,66)
列表生成式,迭代器&生成器
列表生成式
孩子,我现在有个需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],我要求你把列表里的每个值加1,你怎么实现?你可能会想到2种方式 :
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> a [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> b = [] >>> for i in a:b.append(i+1) ... >>> b [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> a = b >>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
a = [1,3,4,6,7,7,8,9,11] for index,i in enumerate(a): a[index] +=1 print(a)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> a = map(lambda x:x+1, a) >>> a <map object at 0x101d2c630> >>> for i in a:print(i) ... 3 5 7 9 11
其实还有一种写法,如下:
>>> a = [i+1 for i in range(10)] >>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
这就叫做列表生成
生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
(expr for iter_var in iterable if cond_exp
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 L = [x * x for x in range(10)] 2 print(L) 3 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 4 g = (x * x for x in range(10)) 5 print(g)
创建L
和g
的区别仅在于最外层的[]
和()
,L
是一个list,而g
是一个generator。
我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
如果要一个一个打印出来,可以通过next()
函数获得generator的下一个返回值:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
g = (x * x for x in range(5)) print(g) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g))
我们讲过,generator保存的是算法,每次调用next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。
当然,上面这种不断调用next(g)
实在是太变态了,正确的方法是使用for
循环,因为generator也是可迭代对象:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
g = (x * x for x in range(10)) for n in g: print(n)
所以,我们创建了一个generator后,基本上永远不会调用next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误。
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for
循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n = n + 1 return 'done'
注意,赋值语句:
a, b = b, a + b
相当于:
t = (b, a + b) # t是一个tuple a = t[0] b = t[1]
但不必显式写出临时变量t就可以赋值。
上面的函数可以输出斐波那契数列的前N个数:
fib(10)
仔细观察,可以看出,fib
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib
函数变成generator,只需要把print(b)
改为yield b
就可以了:
def fib(max): n,a,b = 0,0,1 while n < max: #print(b) yield b a,b = b,a+b n += 1 return 'done'
这就是定义generator的另一种方法。如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator:
f = fib(6) print(f)
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def fib(max): n,a,b = 0,0,1 while n < max: #print(b) yield b a,b = b,a+b n += 1 return 'done' data = fib(10) print(data) print(data.__next__()) print(data.__next__()) print("干点别的事") print(data.__next__()) print(data.__next__()) print(data.__next__()) print(data.__next__()) print(data.__next__())
在上面fib
的例子,我们在循环过程中不断调用yield
,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
同样的,把函数改成generator后,我们基本上从来不会用next()
来获取下一个返回值,而是直接使用for
循环来迭代:
1 for n in fib(6): 2 print(n) 3 print(data.__next__()) 4 print(data.__next__()) 5 print(data.__next__()) 6 print(data.__next__())
但是用for
循环调用generator时,发现拿不到generator的return
语句的返回值。如果想要拿到返回值,必须捕获StopIteration
错误,返回值包含在StopIteration
的value
中:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 def fib(max): 2 n,a,b = 0,0,1 3 4 while n < max: 5 #print(b) 6 yield b 7 a,b = b,a+b 8 9 n += 1 10 return 'done' 11 g = fib(6) 12 while True: 13 try: 14 x = next(g) 15 print('g:', x) 16 except StopIteration as e: 17 print('Generator return value:', e.value) 18 break
关于如何捕获错误,后面的错误处理还会详细讲解。
还可通过yield实现在单线程的情况下实现并发运算的效果
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #_*_coding:utf-8_*_ 2 __author__ = 'Alex Li' 3 4 import time 5 def consumer(name): 6 print("%s 准备吃包子啦!" %name) 7 while True: 8 baozi = yield 9 10 print("包子[%s]来了,被[%s]吃了!" %(baozi,name)) 11 12 13 def producer(name): 14 c = consumer('A') 15 c2 = consumer('B') 16 c.__next__() 17 c2.__next__() 18 print("老子开始准备做包子啦!") 19 for i in range(10): 20 time.sleep(1) 21 print("做了2个包子!") 22 c.send(i) 23 c2.send(i) 24 25 producer("alex")
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回yield的值。并在下一次执行 next()方法时从当前位置继续运行。
迭代器
迭代是Python最强大的功能之一,是访问集合元素的一种方式。。
迭代器是一个可以记住遍历的位置的对象。也就是:可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:__iter()__ 和 next()。
我们已经知道,可以直接作用于for
循环的数据类型有以下几种:
一类是集合数据类型,如list
、tuple
、dict
、set
、str
等;
一类是generator
,包括生成器和带yield
的generator function。
这些可以直接作用于for
循环的对象统称为可迭代的:Iterable
。
区分:可迭代的和迭代器
可以使用isinstance()
判断一个对象是否是Iterable【可迭代的】
:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from collections import Iterable 2 print(isinstance([], Iterable)) 3 print(isinstance({}, Iterable)) 4 print(isinstance('abc', Iterable)) 5 print(isinstance(tuple(),Iterable)) 6 print(isinstance((x for x in range(10)), Iterable)) 7 print(isinstance(100, Iterable))
可以使用isinstance()
判断一个对象是否是Iterator
对象:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from collections import Iterator print(isinstance((x for x in range(10)), Iterator)) print(isinstance([], Iterator)) print(isinstance({}, Iterator)) print(isinstance('abc', Iterator))
可以看到生成器都是Iterator即是可迭代的,又是迭代器
,但list
、dict
、str,tuple
虽然是Iterable
,却不是Iterator
。
简而言之,生成器不但可以作用于for
循环,还可以被next()
函数不断调用并返回下一个值,直到最后抛出StopIteration
错误表示无法继续返回下一个值了,所以生成器也是一个迭代器。
把list
、dict
、str,tuple
等Iterable
变成Iterator
可以使用iter(),
然后就可以通过next()方法来访问list中的元素了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from collections import Iterator print(isinstance(iter([]), Iterator)) print(isinstance(iter('abc'), Iterator)) print(isinstance(iter(tuple()),Iterator)) print(isinstance({"name":"age"},Iterator)) #注意字典是不能转换为迭代器的!
你可能会问,为什么list
、dict
、str
等数据类型不是Iterator
?
这是因为Python的Iterator
表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
小结
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str,tuple
等是Iterable
但不是Iterator
,不过可以通过iter()
函数将这些类型转换为Iterator对象【可迭代的(Iterable)】,然后就可以通过next()方法来访问其中的元素了。
Python的for
循环本质上就是通过不断调用next()
函数实现的,例如:
for x in [1, 2, 3, 4, 5]: pass
实际上完全等价于:
# 首先获得Iterator对象: it = iter([1, 2, 3, 4, 5]) # 循环: while True: try: # 获得下一个值: x = next(it) except StopIteration: # 遇到StopIteration就退出循环 break
迭代器是访问集合元素的一种方式。迭代器的功劳是提供了一个统一的访问集合的接口,只要定义了__iter__()方法对象,就可以使用迭代器访问。
迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。
迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束。迭代器只能往前不会后退。
整合:
自定义迭代器:
既然迭代器就是满足支持迭代器协议,满足迭代器协议就是支持__next__和__iter__方法
1、迭代器基于下面两个个方法:
(1)__next__ 返回容器的下一个项目
(2)__iter__ 返回迭代器本身
2、当序列遍历完时,将抛出StopIteration异常,所以通过捕获这个异常来停止循环
下面是自定义迭代器的示例代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# encoding:UTF-8 class MyIterator(object): def __init__(self, step): self.step = step def __next__(self): if self.step == 0: raise StopIteration self.step -= 1 return self.step def __iter__(self): return self for i in MyIterator(6): print(i)
本节作业
有以下员工信息表
当然此表你在文件存储时可以这样表示
1,Alex Li,22,13651054608,IT,2013-04-01
现需要对这个员工信息文件,实现增删改查操作
- 可进行模糊查询,语法至少支持下面3种:
- select name,age from staff_table where age > 22
- select * from staff_table where dept = "IT"
- select * from staff_table where enroll_date like "2013"
- 查到的信息,打印后,最后面还要显示查到的条数
- 可创建新员工纪录,以phone做唯一键,staff_id需自增
- 可删除指定员工信息纪录,输入员工id,即可删除
- 可修改员工信息,语法如下:
- UPDATE staff_table SET dept="Market" WHERE where dept = "IT"
注意:以上需求,要充分使用函数,请尽你的最大限度来减少重复代码!