zoukankan      html  css  js  c++  java
  • Python开发【第五篇】迭代器、生成器、递归函数、二分法

    阅读目录

    一.迭代器

    1. 迭代的概念

    #迭代器即迭代的工具(自定义的函数),那什么是迭代呢?
    #迭代:指一个重复的过程,每次重复都可以称之为一次迭代,并且每一次重复的结果是下一个迭代的初始值(例如:罚写作业100遍)
    
    while True: #只是单纯地重复,因而不是迭代 print('===>') l=[1,2,3] count=0 while count < len(l): #迭代 print(l[count]) count+=1

    2.为何要有迭代器? 什么是可迭代对象? 什么是迭代器对象?

    #1、为何要有迭代器?
    对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器
    
    #2、什么是可迭代对象?
    可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__,如下
    'hello'.__iter__
    (1,2,3).__iter__
    [1,2,3].__iter__
    {'a':1}.__iter__
    {'a','b'}.__iter__
    open('a.txt').__iter__
    
    #3、什么是迭代器对象?
    可迭代对象执行obj.__iter__()得到的结果就是迭代器对象
    而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象
    
    文件类型是迭代器对象
    open('a.txt').__iter__()
    open('a.txt').__next__()
    
    
    #4、注意:
    迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象
    为何要有迭代器?什么是可迭代对象?什么是迭代器对象?

     3.迭代器对象的使用

    dic={'a':1,'b':2,'c':3}
    iter_dic=dic.__iter__() #得到迭代器对象,迭代器对象即有__iter__又有__next__,但是:迭代器.__iter__()得到的仍然是迭代器本身
    iter_dic.__iter__() is iter_dic #True
    
    print(iter_dic.__next__()) #等同于next(iter_dic)
    print(iter_dic.__next__()) #等同于next(iter_dic)
    print(iter_dic.__next__()) #等同于next(iter_dic)
    # print(iter_dic.__next__()) #抛出异常StopIteration,或者说结束标志
    
    #有了迭代器,我们就可以不依赖索引迭代取值了
    iter_dic=dic.__iter__()
    while 1:
        try:
            k=next(iter_dic)
            print(dic[k])
        except StopIteration:
            break
            
    #这么写太丑陋了,需要我们自己捕捉异常,控制next,python这么牛逼,能不能帮我解决呢?能,请看for循环
    迭代器对象的使用

    4. for循环原理

    #基于for循环,我们可以完全不再依赖索引去取值了
    dic={'a':1,'b':2,'c':3}
    for k in dic:
        print(dic[k])
    
    #for循环的工作原理
    #1:执行in后对象的dic.__iter__()方法,得到一个迭代器对象iter_dic
    #2: 执行next(iter_dic),将得到的值赋值给k,然后执行循环体代码
    #3: 重复过程2,直到捕捉到异常StopIteration,结束循环
    

    5. 迭代器的优缺点

    #优点:
      - 提供一种统一的、不依赖于索引的迭代方式
      - 惰性计算,节省内存
    #缺点:
      - 无法获取长度(只有在next完毕才知道到底有几个值)
      - 一次性的,只能往后走,不能往前退

    二. 生成器

      1. 什么是生成器

       生成器:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行

       生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表

    #只要函数内部包含有yield关键字,那么函数名()的到的结果就是生成器,并且不会执行函数内部代码
    def func():
        print('====>first')
        yield 1
        print('====>second')
        yield 2
        print('====>third')
        yield 3
        print('====>end')
    g=func()
    print(g) #<generator object func at 0x0000000002184360>

      2.生成器就是迭代器

    g.__iter__
    g.__next__
    #所以生成器就是迭代器,因此可以这么取值
    res=next(g)
    print(res)

      3.生成器Generator总结:

        本质:迭代器(所以自带了__iter__方法和__next__方法,不需要我们去实现)

        特点:惰性运算,开发者自定义

      4.生成器函数

        一个包含yield关键字的函数就是一个生成器函数。yield可以为我们从函数中返回值,但是yield又不同于return,return的执行意味着程序的结束,调用生成器函数不会得到返回的具体的值,而是得到一个可迭代的对象。每一次获取这个可迭代对象的值,就能推动函数的执行,获取新的返回值。直到函数执行结束。

    import time
    def generator_fun1():
        a = 1
        print('现在定义了a变量')
        yield a
        b = 2
        print('现在又定义了b变量')
        yield b
    
    g1 = generator_fun1()
    print('g1 : ',g1)       #打印g1可以发现g1就是一个生成器
    print('-'*20)   #我是华丽的分割线
    print(next(g1))
    time.sleep(1)   #sleep一秒看清执行过程
    print(next(g1))

      5.生成器有什么好处呢?

        1.延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。

    # 假如我想让工厂给学生做校服,生产2000000件衣服,我和工厂一说,工厂应该是先答应下来,然后再去生产,我可以一件一件的要,也可以根据学生一批一批的找工厂拿。
    # 而不能是一说要生产2000000件衣服,工厂就先去做生产2000000件衣服,等回来做好了,学生都毕业了。。。
    
    def produce():
        """生产衣服"""
        for i in range(2000000):
            yield "生产了第%s件衣服"%i
    
    product_g = produce()
    print(product_g.__next__()) #要一件衣服
    print(product_g.__next__()) #再要一件衣服
    print(product_g.__next__()) #再要一件衣服
    num = 0
    for i in product_g:         #要一批衣服,比如5件
        print(i)
        num +=1
        if num == 5:
            break
    
    #到这里我们找工厂拿了8件衣服,我一共让生产函数(也就是produce生成器函数)生产2000000件衣服。
    #剩下的还有很多衣服,我们可以一直拿,也可以放着等想拿的时候再拿
    示例

      6.练习

    #1、自定义函数模拟range(1,7,2)
    
    #2、模拟管道,实现时时获取文件中最新内容
    # 1、自定义函数模拟range(1,7,2)
    def myRange(start,stop,step=1):
        while start < stop:
            yield start
            start += step
    
    obj = myRange(1,7,2)
    print(next(obj))
    print(next(obj))
    print(next(obj))
    print(next(obj)) #StopIteration
    
    #2、模拟管道,实现时时获取文件中最新内容
    import time
    def tail(filename):
        with open(filename,'r',encoding='utf-8')as f:
            f.seek(0,2) #从文件末尾开始读取
            while True:
                line = f.readline()
                if not line:
                    time.sleep(0.5)
                    continue
                yield line
    
    obj = tail('a.txt')
    for i in obj:
        print(i)
    代码示例

        7.协程函数

      什么是协程:

      协程是一个无优先级的子程序调度组件,允许子程序在特点的地方挂起恢复。(类似于看电影时的暂停播放)

      线程包含于进程,协程包含于线程。只要内存足够,一个线程中可以有任意多个协程,但某一时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源。

    #yield关键字的另外一种使用形式:表达式形式的yield
    def eater(name):
        print('%s 准备开始吃饭啦' % name)
        food_list=[]
        while True:
            food = yield food_list
            print('%s 吃了 %s' % (name, food))
            food_list.append(food)
    
    e=eater('钢蛋')
    print(e.send(None))  #初始化,对于表达式形式的yield,在使用时,第一次必须传None,e.send(None)等同于next(e)
    print(e.send('包子'))  
    print(e.send('韭菜馅包子'))
    print(e.send('大蒜包子'))
    e.close() #关闭
    print(e.send('大麻花'))
    #send 获取下一个值的效果和next基本一致 #只是在获取下一个值的时候,给上一yield的位置传递一个数据 #使用send的注意事项 # 第一次使用生成器的时候 是用send(None)或者 next进行初始化

     8.练习:

      1、编写装饰器,实现初始化协程函数的功能

    def init(func):
        def inner(*args,**kwargs):
            res = func(*args,**kwargs)
            next(res) #在装饰器中执行初始化方法
            return res
        return  inner
    
    @init
    def eater(name):
        print('%s 准备开始吃饭啦' % name)
        food_list=[]
        while True:
            food = yield food_list
            print('%s 吃了 %s' % (name, food))
            food_list.append(food)
    
    e=eater('钢蛋')
    # e.send(None)
    print(e.send('包子'))
    print(e.send('韭菜馅包子'))
    print(e.send('大蒜包子'))
    装饰器实现初始化协程方法

     9. yield 关键字 总结

    1、把函数做成迭代器
    2、对比return,可以返回多次值,可以挂起/保存函数的运行状态

    三. 列表推导式、生成器表达式

      1.列表推导式

    #1、示例
    egg_list=[]
    for i in range(10):
        egg_list.append('鸡蛋%s' %i)
    
    print(egg_list)
    
    #列表推导式1
    egg_list = ['臭鸡蛋%s' %i for i in range(0,10) ]
    print(egg_list)
    
    #列表推导式2
    egg_list = ['臭鸡蛋%s' %i for i in range(0,10) if i>6]
    print(egg_list)
    
    #2、语法
    [expression for item1 in iterable1 if condition1
    for item2 in iterable2 if condition2
    ...
    for itemN in iterableN if conditionN
    ]
    类似于
    res=[]
    for item1 in iterable1:
        if condition1:
            for item2 in iterable2:
                if condition2
                    ...
                    for itemN in iterableN:
                        if conditionN:
                            res.append(expression)
    
    #3、优点:方便,改变了编程习惯,可称之为声明式编程

      2.生成器表达式

    #生成器表达式
    #1、把列表推导式的[]换成()就是生成器表达式
    
    #2、示例:生一筐鸡蛋变成给你一只老母鸡,用的时候就下蛋,这也是生成器的特性
    chicken=('鸡蛋%s' %i for i in range(5))
    
    print(chicken) # generator object <genexpr> at 0x10143f200>
    
    print(next(chicken)) #'鸡蛋0'
    
    print(list(chicken)) #因chicken可迭代,因而可以转成列表 ['鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4',]
    
    
    #3、优点:省内存,一次只产生一个值在内存中

      总结:

        1.把列表解析的[]换成()得到的就是生成器表达式

        2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存

     4.声明式编程练习题

    1、将names=['egon','alex_sb','wupeiqi','yuanhao']中的名字全部变大写
    
    2、将names=['egon','alex_sb','wupeiqi','yuanhao']中以sb结尾的名字过滤掉,然后保存剩下的名字长度
    
    3、求文件a.txt中最长的行的长度(长度按字符个数算,需要使用max函数)
    
    4、求文件a.txt中总共包含的字符个数?思考为何在第一次之后的n次sum求和得到的结果为0?(需要使用sum函数)
    
    5、思考题
    with open('a.txt') as f:
        g=(len(line) for line in f)
    print(sum(g)) #为何报错?
    # 1、将names=['egon','alex_sb','wupeiqi','yuanhao']中的名字全部变大写
    names=['egon','alex_sb','wupeiqi','yuanhao']
    names = [name.upper() for name in names]
    print([name.upper() for name in names])
    
    # 2、将names=['egon','alex_sb','wupeiqi','yuanhao']中以sb结尾的名字过滤掉,然后保存剩下的名字长度
    names=['egon','alex_sb','wupeiqi','yuanhao']
    names = [len(name) for name in names if not name.endswith("sb") ]
    print(names)
    # 3、求文件a.txt中最长的行的长度(长度按字符个数算,需要使用max函数)
    with open('a.txt','r',encoding='utf-8')as f:
        print(max(len(i) for i in f)) #最后有一个换行符
    
    # 4、求文件a.txt中总共包含的字符个数?思考为何在第一次之后的n次sum求和得到的结果为0?(需要使用sum函数)
    with open('a.txt','r',encoding='utf-8') as f:
        print(sum(len(i) for i in f))
        print(sum(len(i) for i in f))  #原因读取文件的指针已经到文件的末尾了
    # 5、思考题
     with open('a.txt') as f:
         g=(len(line) for line in f)
     print(sum(g)) #为何报错?
    #答: with open 在执行完文件操作后,会自动关闭掉此文件,所以再使用文件内容时会报错
    代码示例

     四.递归函数

      1. 递归调用的定义

    #递归调用是函数嵌套调用的一种特殊形式,函数在调用时,直接或间接调用了自身,就是递归调用

      2.递归分为两个阶段:递推,回溯

    #问年龄游戏
    #图解。。。
    # age(4) = age(3) + 2
    # age(3) = age(2) + 2
    # age(2) = age(1) + 2
    # age(1) = 40
     
    def age(n):
        if n == 1:
            return 40
        else:
            return age(n-1)+2
     
    print(age(4))

      3.递归的使用

    #总结递归的使用:
    1. 必须有一个明确的结束条件
    
    2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
    
    3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,
      每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
    
    4.递归默认调用的最大深度为 ---997

     4.设置递归最大深度

    #设置递归最大深度
    #注意:实际上可以达到的深度 取决于计算机的性能了
    import sys
    sys.setrecursionlimit(10000) #递归最大深度
    
    def func1(n):
        print(">>>>>>",n)
        n+=1
        func1(n)
    
    func1(0)
    

      5.练习题 

      1.使用递归函数完成三级菜单

    menu = {
        '北京': {
            '海淀': {
                '五道口': {
                    'soho': {},
                    '网易': {},
                    'google': {}
                },
                '中关村': {
                    '爱奇艺': {},
                    '汽车之家': {},
                    'youku': {},
                },
                '上地': {
                    '百度': {},
                },
            },
            '昌平': {
                '沙河': {
                    '老男孩': {},
                    '北航': {},
                },
                '天通苑': {},
                '回龙观': {},
            },
            '朝阳': {},
            '东城': {},
        },
        '上海': {
            '闵行': {
                "人民广场": {
                    '炸鸡店': {}
                }
            },
            '闸北': {
                '火车战': {
                    '携程': {}
                }
            },
            '浦东': {},
        },
        '山东': {},
    }
    menu
    def threeLM(dic):
        while True:
            for k in dic:print(k)
    
            key = input('input>>').strip()
    
            if key == 'b' or key == 'q':return key
    
            elif key in dic.keys() and dic[key]:
                ret = threeLM(dic[key])
                if ret == 'q': return 'q'
    
    threeLM(menu)
    #递归函数实现三级菜单
    递归函数实现三级菜单

     五. 二分查找法

      想从一个按照从小到大排列的数字列表中找到指定的数字,遍历的效率太低,用二分法(算法的一种,算法是解决问题的方法)可以极大低缩小问题规模

      二分查找法 简单版
    #二分查找法
    l=[1,2,10,30,33,99,101,200,301,402] #从小到大排列的数字列表
    count = 0 #
    def search(num,l):
        global count
        count+=1
        if l:
            print(l)  #查看列表的变化
            mid = (len(l)-1)//2
            if num > l[mid]:
                l=l[mid+1:] # 取列表右边数据
            elif num < l[mid]:
                l =l[:mid]  # 取列表左边数据
            else:
                print(l[mid])
                return  #通过return 终止递归
            search(num,l)   #递归循环调用
        else:
            print("没有找到指定数字")
    
    search(10,l)
    print(count)

     二分查找法升级版  

    #二分查找法 升级版
    l=[1,2,10,30,33,99,101,200,301,402]
    
    def search(num,l,start=0,stop=len(l)-1):
        if start <= stop:
            mid=start+(stop-start)//2
            print('start:[%s] stop:[%s] mid:[%s] mid_val:[%s]' %(start,stop,mid,l[mid]))
            if num > l[mid]:
                start=mid+1
            elif num < l[mid]:
                stop=mid-1
            else:
                print('find it',mid)
                return
            search(num,l,start,stop)
        else: #如果stop > start则意味着列表实际上已经全部切完,即切为空
            print('not exists')
            return
    
    search(101,l)

    六 练习 

    #1.使用递归打印斐波那契数列(前两个数的和得到第三个数,如:0 1 1 2 3 4 7...)
    
    #2.一个嵌套很多层的列表,如l=[1,2,[3,[4,5,6,[7,8,[9,10,[11,12,13,[14,15]]]]]]],用递归取出所有的值
    #1.使用递归打印斐波那契数列(前两个数的和得到第三个数,如:0 1 1 2 3 4 7...)
    
    #非递归
    def fib(n):
        a,b=0,1
        while a < n:
            print(a,end=' ')
            a,b=b,a+b
        print()
    
    fib(10)
    #递归
    def fib(a,b,stop):
        if  a > stop:
            return
        print(a,end=' ')
        fib(b,a+b,stop)
    
    fib(0,1,10)
    
    #2. 一个嵌套很多层的列表,如l=[1,2,[3,[4,5,6,[7,8,[9,10,[11,12,13,[14,15]]]]]]],用递归取出所有的值
    
    l=[1,2,[3,[4,5,6,[7,8,[9,10,[11,12,13,[14,15]]]]]]]
    
    def get(seq):
        for item in seq:
            if type(item) is list:
                get(item)
            else:
                print(item)
    get(l)
    代码示例
  • 相关阅读:
    SQL最小 最大时间 查询
    Linq Except,Distinct,Left Join
    Js 刷新页面
    olgaInteractive Shape Modeling(0)
    olgaInteractive Shape Modeling(1):classmaterials
    olgaInteractive Shape Modeling(1):classmaterials:Shape Creation and Deformation
    olgaInteractive Shape Modeling(2):related papers
    计算机内部如何存储数据,关于源码、补码的问题!
    sprintf用法解析
    堆与栈有什么区别?
  • 原文地址:https://www.cnblogs.com/wangfengming/p/8352835.html
Copyright © 2011-2022 走看看