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']
    

迭代器总结

  • 迭代器优点:
    1. 统一了一种迭代取值的方式,可应用于各种可迭代数据类型。
    2. 更加节省内存,每循环一次产生一个值
  • 迭代器缺点:
    1. 无法利用索引进行取值,所以无法指定位置取值了,只能遍历全部找到想要的值
    2. 迭代器只能取一次值,要想再取就要再造一个迭代器

二、生成器(自定义迭代器)

思考:如果我们现在需要造一个可以循环取值千万次的迭代器,该怎么做?

已知,迭代器是由可迭代对象.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次,而二分法四次搞定了。这种方法,对数据越多的列表,优势越大
posted @ 2021-04-29 10:35  nida  阅读(100)  评论(0)    收藏  举报