python学习笔记DAY06(函数_迭代器、生成器、三元表达式)
这是我个人的学习笔记,都赖于(egon老师)的分享,以下是老师博客的原地址:
https://www.cnblogs.com/xiaoyuanqujing/articles/11640888.html
python函数
一、迭代器
迭代器就是迭代取值的工具(有没有点似曾相识,对说的就是for循环),迭代是一个重复的过程,但是每次重复都是基于上一次结果而进行的。
# 最原始的迭代取值方案 list1 = ["nida","yaoyao","lili"] i = 0 while i<len(list1): # i的初始值是0,当i小于列表总长度时,依次循环从列表取值 print(list1[i]) i +=1 # i每次加1
有上可见,原始的迭代取值是基于索引取值的,但是有些数据类型没有索引(比如字典),该如何取值呢?
这时候就需要迭代器出马了。(迭代器是python提供的一种通用的迭代取值方式)
-
迭代器取值范围:列表/字符串/元组/字典/集合/打开的文件(凡是内部有__iter__方法的数据类型,都是可迭代对象)可迭代对象就是可以被转换成迭代器的对象
-
迭代器对象(可迭代对象,调用自己的iter方法,会得到一个迭代器对象,就可以开始取值了)
age = {"nida":22,"yaoyao":15,"lili":18} d = age.__iter__() # 字典age调用自己的iter(iteration=迭代)方法,得到一个迭代器对象 d . x = d.__next__() # 迭代器对象调用自己的next方法,每次调用一次去取一个值 y = d.__next__() z = d.__next__() print(x,y,z) # 结果:nida yaoyao lili简化代码:
age = {"nida":22,"yaoyao":15,"lili":18} d = age.__iter__() while True: try: print(d.__next__()) # 当可迭代对象的值被取完之后,继续取值就会报错(StopIteration) except StopIteration: # 所以需要加try 过滤掉对应报错信息 break注意:在一个迭代器被取值取干净之后,无法再取值了。除非再造一个迭代器。
- 可迭代对象使用iter转换成迭代器对象之后,迭代器本身还有iter方法,而迭代器对象调用iter方法,得到的还是它自己,那这个迭代器自带的iter方法是不是多余了呢?
- 其实这是为了for循环的工作原理,为了for循环不仅可以迭代,可迭代对象;也可以迭代,迭代器对象
for循环工作原理
-
for x in 可迭代对象/迭代器对象 ,in后方无论跟的是谁,都是会直接调用它的iter方法,来进行遍历。为了使迭代器对象放在这里也可以正常使用,它就必须有iter方法。供for循环使用。
# 迭代取值 for循环两行代码搞定 age = {"nida":22,"yaoyao":15,"lili":18} #之所以这么简约,是因为for循环帮你做了很多事 for x in age: # in后方的是,调用了age的iter方法,得到一个迭代器对象后,又调用了迭代器的next方法 print(x) # 把每次next的返回值赋值给了x,并且在取值结束后,break备注:打开的文件,本身就是一个迭代器对象。(为什么文件这么特殊呢?)
- 因为文件实际是由操作系统从硬盘读取的,文件的大小我们无法预估,如果一下子就全部读入内存,很可能会导致内存溢出。所以它必须是迭代器对象,读取方式是(next)一行一行的读,这样每次内存中只有一行数据,不会过多占用资源。
-
补充:类型转换的的底层原理,就是for循环。
list("hello nida") # 原理:循环可迭代对象,循环取值后放入列表中。 # 结果:['h', 'e', 'l', 'l', 'o', ' ', 'n', 'i', 'd', 'a']
迭代器总结
- 迭代器优点:
- 统一了一种迭代取值的方式,可应用于各种可迭代数据类型。
- 更加节省内存,每循环一次产生一个值
- 迭代器缺点:
- 无法利用索引进行取值,所以无法指定位置取值了,只能遍历全部找到想要的值
- 迭代器只能取一次值,要想再取就要再造一个迭代器
二、生成器(自定义迭代器)
思考:如果我们现在需要造一个可以循环取值千万次的迭代器,该怎么做?
已知,迭代器是由可迭代对象.iter,转换而来的,我们了解的可迭代对象,(元组/列表/集合 等等。。)如果想存放千万个值,几乎是不可能的。那千万个值的迭代器该怎么造?
现在需要我们自己自定义一个不同于所有固定模式的迭代器了:
-
造一个能返回多次值的函数:
# 以下包含 yield 的函数,已经不是普通的函数了,它是一个生成器(自定义迭代器) def func(): print("我是第一个被打印的") yield 1 print("我是第二个被打印的") yield 2 print("我是第三个被打印的") yield 3 #func() # 这个调用已经不是以前你以为的调用了() a = func() # 把调用这个动作赋值给变量a,发现就是一个生成器 print(a) # 结果:<generator(生成器) object func at 0x0000022FE1AEDAC0> print(a.__next__()) # 结果: 我是第一个被打印的 1 print(a.__next__()) # 结果: 我是第二个被打印的 2 print(a.__next__()) # 结果: 我是第三个被打印的 3 print(a.__next__()) # 结果:next超出yield数量,出现 StopIteration 错误 # 每调一次生成器的next方法,代码运行指针就会运行到它所遇到的第一个 yield 后方。 # 然后返回紧跟 yield 后方的值(有点像return的意思)
综上所述:在一个函数中加上yield关键字,并且对其函数进行调用之后,就会得到一个生成器。然后每调用一次next方法,代码才开始运行到一个yield后方。并返回紧跟后方的值,直到函数体代码结束。
由此可见:yield 可以保存函数的运行状态挂起函数,用来返回多个值
补充知识: len("nida") 其实就是 "nida".__len__()
那么: next(生成器对象) 其实就是 生成器对象.__next__()
iter(可迭代对象) 其实就是 可迭代对象.__iter__()
所以: 方法(对象) = 对象.方法()
-
生成器应用案例
def my_range(start,stop,step=1): while start<stop: yield start start += step # 自定义了一个生成器,可以随意指定要迭代的长度与步长 for x in my_range(1,9,2): # 其实python自带的 range 就是这种方式写出来的 print(x)
三、yield表达式形式
def dog():
print("我准备好接收东西了")
while True: # 已知 运行指针遇到 yield 会停止。其实我们还可以给yield赋值。
x = yield 5 # 现在 yield 把接收到的值给了x
print(f"我已经接收到{x}")
g = dog() # 创建了一个生成器
g.send(None) # g.send(None)相当于next(g) 就是为了把运行指针移动到,yield的后方。相当于初始化
x = g.send("一朵小红花") # 现在运行指针已经到yield的后方,可以接收到我们传送的值了
print(x) # 给yield赋值,并不会影响它后方的返回值
# 结果:
# 我准备好接收东西了
# 我已经接收到一朵小红花
# 5
g.close() #关闭生成器,关闭之后就无法继续传值了
在生成器中,有多少个yield,就可以.send("给予的内容")赋多少个值
结论:yield 可以当return 放回值,也可当一个变量,接受给它的值。
四、三元表达式
三元表达式其实是一种 if 判断语句的简写形式
def fun(x,y): 如左侧取两个值中最大值的代码,如果使用三元表达式则一行代码搞定:
if x < y: 三元: y if x < y else x
return y (中间放置条件,如果条件成立返回左侧的值,不成立返回右侧的值)
else:
return x
五、生成式
快速生成想要的数据的一种简写模式
列表生成式
# 需求,把列表中带有_mm的数据提取出来
# 没有学生成式之前
list1 = ["nida_mm","yaoyao_mm","lili_n"]
list2 = []
for x in list1:
if x.endswith("_mm"):
list2.append(x)
print(list2)
# 学了生成式之后
list2 = [x for x in list1 if x.endswith("_mm")]
print(list2)
list3 = [x.upper() for x in list1] # 所有字符全部大写
print(list3)
list4 = [x.replace("_mm","") for x in list1] # 去掉所有"_mm"
print(list4)
列表生成式:[ 每次符合条件后要放入新列表的数据 for x in 被循环数据 if 判断循环出的数据是否符合条件]
备注:如果for循环之后没有条件,默认条件为true
字典生成式
#简单:把列表当做键传入字典
list1 = ["nida","yaoyao","lili"]
dic1 = {key:None for key in list1}
print(dic1)
# 结果:{'nida': None, 'yaoyao': None, 'lili': None}
#复杂:除lili外 其他内容放入字典
list1 = [("nida",15),("yaoyao",16),("lili",17)]
dic1 = {k:v for k,v in list1 if k !="lili"}
print(dic1)
# 结果:{'nida': 15, 'yaoyao': 16}
集合生成式
list1 = ["nida","yaoyao","lili"]
set1 = {x for x in list1}
print(set1)
生成器表达式
list1 = ["nida","yaoyao","lili"]
i = (x for x in list1) # 注意:在表达式外面加()并不是转换为元组。而是生成器(generator)
print(i) # 此刻,生成器 i 内部没有一个值
结果:<generator object <genexpr> at 0x000001FB5B67DAC0>
i.__next__() # 每调用一次next方法,查到一个值
实例(统计一个文件有多少字符)
# 以前统计文件字符长度的方法
with open("name",mode="rt",encoding="utf-8") as f:
x = 0
for line in f:
x += len(line)
print(x)
# 现在统计文件字符的方法
g = (len(line) for line in f)
print(g) # <generator object <genexpr> at 0x000001CF967FDAC0>
print(sum(g)) # 130 有此可见苏sum直接帮生成器g 调用了next方法,拿到数据之后又持续累加
# 最终优化
sum(len(line) for line in f)
六、函数的递归与调用
函数的递归调用是函数嵌套调用的特殊形式,是直接或者间接的调用到了自身。(递归的本质就是循环)
但是如果是循环函数,每次都会在内存开辟新的空间,所以python不允许无限循环,默认最多1000次,如果需要改变:sys.setrecursionlimit("设定最多次数")
思考:如果递归就是循环,那已经有了for循环 while循环 ,为什么还需要一个递归呢?
- 回溯:递归开始,一层一层的往下调用自身
- 递推:满足某种条件后,递归结束调用,一层层返回
递归实例
-
算出除小明所有人的年龄
小赤:我比小橙大5岁 小赤 = 30+5
小橙:我比小绿大5岁 小橙 = 25+5
小紫:我比小蓝大5岁 小紫 =20+5
小蓝:我比小明大5岁 小蓝 = 15+5
小明:我15岁.
# 没有用递归之前(计算年龄) def age(n): m = 15 if n == 1: return m for x in range(1,n): m +=5 return m print(age(5)) # 使用递归之后(计算年龄) def age(n): # 把需要查询的总层级当参数传入 if n == 1: # 如果层级是1的话,把我们已知的层级1的值返回。 return 15 return age(n-1)+5 # 每次把层级减一,加上(每个层级多的值)返回给调用者 print(age(5))以上递归实例具体逻辑分析:(拿层级(3)举例)
开始调用:
回溯:age(3)>运行发现要调用>age(2)>运行发现要调用>age(1)>至此不需要继续调用,拿到返回值>15
递推:age(3)结束运行,return 20+5 <== age(2)结束运行,return 15+5 <== age(1)结束运行return15
- 从无数嵌套列表中,提取住最里面的值
list1 = [5,2,[8,5,[2,3,[2,[5],6],6]]] def f1() for l in list1: if type(l) == list: for l1 in l: if type(l1) == list: ..."无限查找下去" # 使用递归解决 list1 = [5,2,[8,5,[2,3,[2,[5],6],6]]] def f1(list1): for l in list1: if type(l) == list: f1() else: return l print(f1(list1))
作业:
# 把列表内的所有数字全部换成对应数字的平方 list1 = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] list2 = [x*list1[x-1] for x in list1]
二分法
二分法是一种简单的算法,算法就是为了更加高效的解决问题。它本身跟编程没有任何关系。
list1 = [7, 8, 9, 10, 11, 12, 13, 14, 15, 21, 22, 31, 43, 51, 65] # 从以上列表中,找到自己指定的值,之前我们的方法是for循环列表的每一个值进行对比,直到找到指定值为止,效率 def two(find_n,l): # 现在采用二分法进行查找 print(l) # 为了方便查看每次的查找结果 if len(l) == 0: # 当最后列表为空,说明找完了已经,但是没有合适的值 print("您找的值不存在") return len_n = len(l)//2 # 取拿到列表的中间值索引,//双斜杠是 对商取整。 if find_n > l[len_n]: # 对比要找的值 大于 中间值 l = l[len_n+1:] # 就取列表的右半部分(不包括中间值,已经对比过了)重新赋值给列表。 return two(find_n,l) # 走到这里说明之前都没有找到,所以要继续掉自身继续查找 elif find_n < l[len_n]: # 先对比要找的值 小于 中间值 l = l[:len_n] # 就取列表的左半部分(不包括中间值,已经对比过了)重新赋值给列表。 return two(find_n,l) # 走到这里说明之前都没有找到,所以要继续掉自身继续查找 else: print("找到了",l[len_n]) # 先对比要找的值 等于 中间值,找到了 two(12,list1) # 结果: [7, 8, 9, 10, 11, 12, 13, 14, 15, 21, 22, 31, 43, 51, 65] # 第一次对比了这个的中间值14 [15, 21, 22, 31, 43, 51, 65] # 第二次对比了这个的中间值31 [43, 51, 65] # 第三次对比了这个的中间值51 [65] # 第四次对比了这个的中间值65 找到了 65 # 之前如果要查询的是65用for需要对比15次,而二分法四次搞定了。这种方法,对数据越多的列表,优势越大

浙公网安备 33010602011771号