迭代器
1.什么是迭代器
迭代:更新换代(重复)的过程,每次的迭代都必须基于上一次的结果
n = 0 while True: print(n) # 不算迭代,因为只是简单的重复 s = 'hello' n = 0 while n < len(s): print(s[n]) n += 1 # 重复 + 每次迭代都是基于上一次的结果而来的才是迭代
迭代器:迭代取值的工具
2.为什么要用
迭代器给我们提供了一种不依赖于索引取值的方法
3.可迭代对象
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
n = 1 f = 1.1 s = 'hello' l = [1,2,3,4] t = (1,2,3,4) s1 = {1,2,3,4} d = {'name':'francis'} f1 = open('xxx.txt','w',encoding='utf-8') res = s.__iter__() # 相当于res = iter(s) res1 = l.__iter__() # res1 = iter(l) res2 = t.__iter__() # res2 = iter(t) res3 = s1.__iter__() # res3 = iter(s1) res4 = d.__iter__() # res4 = iter(d) res5 = f1.__iter__() # res5 = iter(f1) print(res,res1,res2,res3,res4,res5) print(f1)
(1)只有内置方法中有 __iter__ 方法的都叫做可迭代对象
(2)可迭代对象执行内置的 __iter__ 方法得到就是该对象的迭代器对象
(3)文件对象执行内置的 __iter__ 之后还是本身,没有任何变化,文件对象本身就是迭代器对象
所以基本数据类型中是可迭代对象的有:字符串(str)、列表(list)、元组(tuple)、字典(dict)、集合(set)、文件对象
补充:
针对双下线开头双下划线结尾的方法(__iter__),推荐读:双下+方法名
迭代器取值
1.迭代器对象及迭代器取值(__next__)
l = [1,2,3,4] iter_l = l.__iter__() # 把可迭代对象l转成迭代器对象 print(iter_l.__next__()) # 迭代器取值,调用__next__,而且迭代器对象的取值必须用__next__ print(iter_l.__next__()) # 一次取一个 print(iter_l.__next__()) print(iter_l.__next__()) print(iter_l.__next__()) # 如果取完了,直接报错,报错StopIteration
(1)内置方法中既有 __iter__ 方法,也有 __next__ 方法的就是迭代器对象
(2)只有 __iter__ 方法的是可迭代对象,迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象
(3)迭代器取值的特点:只能往后依次取,不能回退
""" 问:__iter__方法就是用来帮我们生成迭代器对象的 而文件对象本身就是迭代器对象,为什么还内置中还有__iter__方法? """ f1 = open('xxx.txt','r',encoding='utf-8') # 本来就是迭代器对象 iter_f = f1.__iter__() print(iter_f is f1) # 比较内存地址是否相同 l = [1,2,3,4] iter_l = l.__iter__() # 生成迭代器对象 iter_2 = iter_l.__iter__().__iter__().__iter__() print(iter_2 is iter_l) # 比较内存地址是否相同
结论:迭代器对象无论执行多少次 __iter__ 方法得到的还是迭代器对象本身
2.异常处理
迭代器取完值了会报错
l = [1,2,3,4] iter_l = l.__iter__() # 把可迭代对象l转成迭代器对象 print(iter_l.__next__()) # 迭代器取值,调用__next__,而且迭代器对象的取值必须用__next__ print(iter_l.__next__()) # 一次取一个 print(iter_l.__next__()) print(iter_l.__next__()) print(iter_l.__next__()) # 如果取完了,直接报错,报错StopIteration
如何让它不报错,使用try + except
l = [1,2,3,4] iter_l = l.__iter__() # 把可迭代对象l转成迭代器对象 while True: try: # 标识下面的代码可能会出错 print(iter_l.__next__()) except StopIteration: # 如果出现StopIteration这个错误,就走下面这一步 print('迭代器取完值了') break
for循环的本质
d = {'name':'francis','password':'123','hobby':'read'} for i in d: # for循环后面的in跟的必须是一个可迭代对象 print(i)
1.for循环内部的本质
(1)将in后面的对象调用 __iter__ 转换成迭代器对象
(2)调用 __next__ 对这个迭代器迭代取值
(3)内部有异常捕获StopIteration,当 __next__ 报了这个错,就自动结束循环
d = {'name':'francis','password':'123','hobby':'read'} iter_d = d.__iter__() # 将in后面的对象调用 __iter__ 转换成迭代器对象 while True: try: # 标识下面的代码可能会出错 print(iter_d.__next__()) # 调用 __next__ 对这个迭代器迭代取值 except StopIteration: # 如果出现StopIteration这个错误,就走下面这一步 print('迭代器取完值了') break # 结束循环
2.迭代取值的优缺点
优点:
(1)不依赖于索引取值
(2)内存中永远只占一份空间,不会导致内存溢出(一次只取一个值)
缺点:
(1)不能够获取指定的元素
(2)取完元素之后会报StopIteration这个错
生成器
1.本质
用户自定义的迭代器,本质就是迭代器
2.如何自定义迭代器
自定义函数 + yield的方法
def func(): print('第一个值') yield 111 # yield后面跟的值就是调用迭代器__next__方法时你能得到的值 print('第二个值') yield 222 print('第三个值') yield 333 print('第四个值') yield 444,555,666 # yield既可以返回一个值也可以返回多个值,并且多个值也是按照元组的形式返回 g = func() # 生成器初始化:函数内如果有yield关键字,那么加括号执行函数的时候并不会触发函数体代码的运行,而是将函数变成迭代器 print(g) print(g.__next__()) # 代码运行到第一个yield后就暂停 print(g.__next__()) # 直到再次取值,一次取一个值 print(g.__next__()) print(g.__next__()) print(g.__next__()) # 取完值就报错
3.如何自定义一个函数,实现内置函数range的功能
for i in range(1,10,2): print(i)
range其实就是一个可迭代对象
实现:
def my_range(start,end,step=1): while start < end: yield start # 第一个值小于第二个值就返回第一个值 start += step # 然后第一个值加上步长再判断 for i in my_range(1,100,2): # 步长可通过传参修改 print(i)
yield表达式形式(了解即可)
def people(name): print('%s 准备开吃'%name) while True: food = yield # 代码运行到第一个yield后就暂停 print('%s 吃了 %s'%(name,food)) g = people('francis') # 当函数内有yield关键字的时候,调用该函数不会执行函数体代码,而是将函数变成生成器 g.__next__() # 必须先将代码运行至yield,才能够为其传值 g.send('蔬菜') # 给yield左边的变量传参,并且触发__next__方法 g.send('肉')
1.yield
(1)帮你提供了一种自定义生成器的方式
(2)会帮你将函数的运行状态暂停住
(3)可以返回值
2.yield与return之间的异同点
相同点:
都可以返回值,并且都可以返回多个
不同点:
(1)yield可以返回多次值,而return只能返回一次值,而且函数立即结束
(2)yield还可以接受外部传入的值(send)
生成器表达式
跟列表、字典、集合生成式一样,不同的是外面用括号
res = (i for i in range(1,10) if i != 4) # 生成器表达式 print(res) print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__())
1.如何统计文件中的字符个数
(1)第一种
f = open('xxx.txt','r',encoding='utf-8') data = f.read() # 把文件内容一次性读完 print(len(data)) # 统计字符个数 f.close()
该方法占内存:如果文件过大,一次性读完太占内存
(2)第二种:for循环
with open('xxx.txt','r',encoding='utf-8') as f: n = 0 for line in f: # 一次只读取文件的一行内容 n += len(line) # 统计每一行的字符数并依次相加 print(n)
该方法节省内存:内存中每次只会有一行内容
(3)第三种:生成器
g = (len(line) for line in f) # 生成器表达式 print(sum(g)) # 求和(内部for循环)
把生成器g中的值for循环出来然后相加
2.面试题
def add(n,i): return n+i def test(): for i in range(4): yield i g=test() for n in [1,10]: g=(add(n,i) for i in g) res=list(g) # A. res=[10,11,12,13] # B. res=[11,12,13,14] # C. res=[20,21,22,23] # D. res=[21,22,23,24] # 选出正确答案
解答:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def add(n,i): return n+i def test(): for i in range(4): yield i g=test() # 不执行函数体代码,而是将函数变成生成器,所以不会执行函数里的for循环 for n in [1,10]: # for循环两次,第一次n=1,第二次n=10 g=(add(n,i) for i in g) ''' 第一次for循环时第二个g为生成器test(),也就是g1=(add(n,i) for i in test()) 第二次for循环时第二个g为上次循环得到的生成器g1,也就是g2=(add(n,i) for i in (add(n,i) for i in test())) 两次循环结束,得到的是两个生成器,都没有执行生成器中的for循环 ''' res=list(g) ''' 其实求的就是生成器g2的里面的值,此时求值时才会执行各生成器中的for循环,g2时n=10 把n=10代入g2生成器中也就是: g2 = (add(10,i) for i in (add(10,i) for i in range(4))) res = list(g2) print(res) 答案是C ''' # A. res=[10,11,12,13] # B. res=[11,12,13,14] # C. res=[20,21,22,23] 正确答案 # D. res=[21,22,23,24]
常用内置方法
1.abs:求绝对值
print(abs(-11.11)) # 求绝对值
2.all、any:
l = [0,1,2] print(all(l)) # 只要有一个元素为False就返回False print(any(l)) # 只要有一个元素为True就返回True
3.bool:判断数值的bool值为True还是False
print(bool(1)) print(bool(0))
4.bytes:转成二进制类型
s = 'hello' print(bytes(s,encoding='utf-8'))
5.callable:判断是否为可调用对象(加括号执行代码)
l = [1,2,3] def index(): pass print(callable(l)) print(callable(index))
6.chr、ord
print(chr(97)) # 将数字转换成ascii码表对应的字符 print(ord('a')) # 将字符按照ascii表转成对应的数字
7.dir:会返回当前对象名称空间里面所有的名字(当前对象可调用的方法)
l = [1,2,3] print(dir(l))
8.divmod:分页器
print(divmod(105,10)) # 第一个值除以第二个值,返回(结果,余数) #可以利用方法来求总页数 total_num,more = divmod(901,10) #909为数据总数,10为每页显示多少数据 if more: total_num += 1 print('总页数:',total_num)
9.enumerate:枚举
l = ['a','b'] for i,j in enumerate(l,1): # 依次给数据编号,默认从0开始,可以添加参数从参数开始编号 print(i,j)
10.eval、exec
s = "print('hello baby~')" s1 =''' x = 1 y = 2 print(x + y) ''' eval(s) exec(s) exec(s1) # 支持逻辑代码 eval(s1) # 报错,eval不支持逻辑代码,只支持一些简单的python代码
11.globals、locals
def index(): username = '我是局部名称空间里面的username' print(locals()) # 当前语句在哪个位置,就会返回哪个位置所存储的所有的名字 print(globals()) # 无论在哪,查看的都是全局名称空间 index()
12.help:查看函数注释
def login(): """ 注释 :return: """ print(help(login))
13.isinstance
n = 1 print(type(n)) print(isinstance(n,list)) # 判断对象是否属于某个数据类型,传入你认为这个对象是什么类型
14.pow:次方运算
print(pow(2,3)) # 后一个值是前一个值几次方,计算结果,此处是求2的3次方的结果
15.round:求大概值,四舍五入
print(round(3.4)) print(round(3.5))
面向过程编程
1.面向过程编程:
就类似于设计一条流水线
好处:
将复杂的问题流程化,从而简单化
坏处:
可扩展性较差,一旦需要修改,整体都会受到影响
2.本质:
就相当于按我们人类正常的思维模式,一步一步的解决问题