• Python编程-函数进阶二


    一、生成器补充

    1.什么是生成器?
    可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他的数据类型需要调用自己内置的__iter__方法),所以生成器就是可迭代对象。

    2.生成器分类
    (1)生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行。
    (2)生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表。

    3.为何使用生成器之生成器的优点
    Python使用生成器对延迟操作提供了支持。
    所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。

    4.生成器函数

    def lay_eggs(num):
        for egg in range(num):
            res='蛋%s' %egg
            yield res
            print('下完一个蛋')
    
    laomuji=lay_eggs(10)
    print(laomuji)               #打印生成器在内存中的地址
    print(laomuji.__next__())    #执行生成器函数
    print(laomuji.__next__())
    print(laomuji.__next__())
    egg_l=list(laomuji)
    print(egg_l)
    
    运行结果:
    <generator object lay_eggs at 0x0000000000A320F8>
    蛋0
    下完一个蛋
    蛋1
    下完一个蛋
    蛋2
    下完一个蛋
    下完一个蛋
    下完一个蛋
    下完一个蛋
    下完一个蛋
    下完一个蛋
    下完一个蛋
    下完一个蛋
    ['蛋3', '蛋4', '蛋5', '蛋6', '蛋7', '蛋8', '蛋9']
    

    5.生成器表达式和列表解析

    egg_list=['鸡蛋%s' %i for i in range(10)]   #列表解析
    laomuji=('鸡蛋%s' %i for i in range(10))#生成器表达式
    print(laomuji)
    print(next(laomuji)) #next本质就是调用__next__
    print(laomuji.__next__())
    print(next(laomuji))
    
    运行结果:
    <generator object <genexpr> at 0x0000000000D820A0>
    鸡蛋0
    鸡蛋1
    鸡蛋2
    

    总结:

    (1)把列表解析的[]换成()得到的就是生成器表达式
    (2)列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
    (3)Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。
    例如,sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:

    sum(x ** 2 for x in xrange(4))
    

    6.注意
    人口信息.txt文件的内容
    {'name':'北京','population':10}
    {'name':'南京','population':100000}
    {'name':'山东','population':10000}
    {'name':'山西','population':19999}

    def get_provice_population(filename):
        with open(filename) as f:
            for line in f:
                p=eval(line)              #将字符串转换成字典
                yield p['population']
    gen=get_provice_population('人口信息')
    
    all_population=sum(gen)
    print(all_population)
    for p in gen:
        print(p/all_population)
    
    运行结果:
    130009
    

    为什么只有1个运行结果呢?

    执行上面这段代码,将不会有任何输出,这是因为,生成器只能遍历一次。在我们执行sum语句的时候,就遍历了我们的生成器,当我们再次遍历我们的生成器的时候,将不会有任何记录。所以,上面的代码不会有任何输出。

    示例:

    def test():
        for i in range(4):
            yield i
    
    g=test()
    
    g1=(i for i in g)
    g2=(i for i in g1)
    
    print(list(g1))
    print(list(g2))
    
    运行结果:
    [0, 1, 2, 3]
    []
    

    7.协程函数

    (1)协程函数就是使用了yield表达式形式的生成器

    def eater(name):
        print("%s eat food" %name)
        while True:
            food = yield
        print("done")
    
    g = eater("gangdan")
    print(g)
    
    <generator object eater at 0x00000000011B80A0>
    

    (2)协程函数赋值过程
    要先运行next(),让函数初始化并停在yield,相当于初始化函数,然后再send(),send会给yield传一个值

    next()和send()都是让函数在上次暂停的位置继续运行,

    next是让函数初始化

    send在触发下一次代码的执行时,会给yield赋值

    def eater(name):
        print('%s start to eat food' %name)
        food_list=[]
        while True:
            food=yield food_list
            print('%s get %s ,to start eat' %(name,food))
            food_list.append(food)
    
    
    e=eater('钢蛋')  
    print(e)
    print(next(e))     # 现在是运行函数,让函数初始化
    print(e.send('包子'))  #
    print(e.send('韭菜馅包子'))
    print(e.send('大蒜包子'))
    
    运行结果:
    <generator object eater at 0x00000000011C8150>
    钢蛋 start to eat food
    []
    钢蛋 get 包子 ,to start eat
    ['包子']
    钢蛋 get 韭菜馅包子 ,to start eat
    ['包子', '韭菜馅包子']
    钢蛋 get 大蒜包子 ,to start eat
    ['包子', '韭菜馅包子', '大蒜包子']
    

    用装饰器修饰协程函数

    def deco(func):
        def wrapper(*args,**kwargs):
            res=func(*args,**kwargs)
            next(res)
            return res
        return wrapper
    
    @deco    #g=deco(g)
    def eater(name):
        print('%s ready to eat' %name)
        food_list=[]
        while True:
            food=yield food_list
            food_list.append(food)
            print('%s start to eat %s' %(name,food))
    
    g=eater('alex')     #wrapper('alex')
    print(g)
    next(g) #等同于 g.send(None)
    
    g.send('手指头')
    g.send('脚指头')
    g.send('别人的手指头')
    g.send('别人的脚指头')
    
    print(g)
    print(g.send('脚趾头1'))
    print(g.send('脚趾头2'))
    print(g.send('脚趾头3'))
    
    运行结果:
    alex ready to eat
    <generator object eater at 0x0000000000A12258>
    alex start to eat None
    alex start to eat 手指头
    alex start to eat 脚指头
    alex start to eat 别人的手指头
    alex start to eat 别人的脚指头
    <generator object eater at 0x0000000000A12258>
    alex start to eat 脚趾头1
    [None, '手指头', '脚指头', '别人的手指头', '别人的脚指头', '脚趾头1']
    alex start to eat 脚趾头2
    [None, '手指头', '脚指头', '别人的手指头', '别人的脚指头', '脚趾头1', '脚趾头2']
    alex start to eat 脚趾头3
    [None, '手指头', '脚指头', '别人的手指头', '别人的脚指头', '脚趾头1', '脚趾头2', '脚趾头3']
    

    应用场景

    grep -rl 'python' /root

    import os
    
    def send_none(func):
        '''
        装饰器函数:初始化表达式yield的生成器函数(send一个None)
        '''
        def wrapper(*args,**kwargs):
            res = func(*args,**kwargs)
            res.send(None)    # next(res)
            return res
        return wrapper
    
    @send_none
    def get_abs_path(open_file):
        '''
        获取目录下所有文件的绝对路径函数,并send给open_file 
        '''
        while True:
            walk_path = yield
            w = os.walk(walk_path)
            for path,_,files in w:
                for file in files:
                    file_abs_path = r'%s\%s' % (path, file)
                    open_file.send(file_abs_path)    # 将得到的绝对路径通过send方式传给open_file这个生成器函数
    
    @send_none
    def open_file(match_line):
        '''
        获得文件句柄函数,并将文件句柄和文件路径以元祖的形式send给match_line
        '''
        while True:
            file_abs_path = yield
            with open(file_abs_path,encoding='utf-8') as f:
                match_line.send((file_abs_path,f))    # send可以将多个参数打包成元祖传递给生成器函数
    
    @send_none
    def match_line(word):
        '''
        遍历通过yield获取到的文件里的每一行,找到关键字是否存在于改行,存在关键字的文件,打印输出
        (使用标签位位,避免重复打印)
        '''
        while True:
            file_abs_path,f = yield
            Flag = False    # 定义一个标志位
            for line in f:
                if Flag:    # 如果标志位被更改为True则跳出循环(为了防止一个文件有多行关键字,print时存在重复)
                    break
                if word in line :
                    Flag = True    # 当匹配到关键字所在的行,将标志位改为Ture
                    print(file_abs_path)
    
    walk_path = r'E:s17day05a'
    g = get_abs_path(open_file(match_line('python')))    # 获得get_abs_path()生成器对象
    g.send(walk_path)
    
    运行结果:
    E:s17day05aa1
    E:s17day05acc1
    E:s17day05acdd1
    

    总结:

    面向过程的程序设计:是一种流水线式的编程思路,是机械式
    优点:
    程序的结构清晰,可以把复杂的问题简单

    缺点:
    扩展性差

    应用场景:
    linux内核,git,httpd

    二、递归

    1.递归调用
    在函数调用过程中,直接或间接地调用了函数本身,这就是函数的递归调用。

    def f1():
        print('f1')
        f2()
    
    def f2():
        f1()
    
    f1()
    

    使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

    使用以下方式查询:

    import sys
    print(sys.getrecursionlimit())
    
    运行结果:
    1000
    

    2.尾递归
    尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
    尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

    遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化。

    3.应用场景:二分法

    逻辑是将列表取中间值做比较,然后取左边或右边再比较

    l = [1, 2, 10,33,53,71,73,75,77,85,101,201,202,999,11111]
    
    def search(find_num,seq):
        if len(seq) == 0:
            print('not exists')
            return
        mid_index=len(seq)//2
        mid_num=seq[mid_index]
        print(seq,mid_num)
        if find_num > mid_num:
            #in the right
            seq=seq[mid_index+1:]
            search(find_num,seq)
        elif find_num < mid_num:
            #in the left
            seq=seq[:mid_index]
            search(find_num,seq)
        else:
            print('find it')
    
    search(77,l)
    search(72,l)
    search(-100000,l)
    
    运行结果:
    [1, 2, 10, 33, 53, 71, 73, 75, 77, 85, 101, 201, 202, 999, 11111] 75
    [77, 85, 101, 201, 202, 999, 11111] 201
    [77, 85, 101] 85
    [77] 77
    find it
    [1, 2, 10, 33, 53, 71, 73, 75, 77, 85, 101, 201, 202, 999, 11111] 75
    [1, 2, 10, 33, 53, 71, 73] 33
    [53, 71, 73] 71
    [73] 73
    not exists
    [1, 2, 10, 33, 53, 71, 73, 75, 77, 85, 101, 201, 202, 999, 11111] 75
    [1, 2, 10, 33, 53, 71, 73] 33
    [1, 2, 10] 2
    [1] 1
    not exists
    

    三、匿名函数

    1.lambda
    当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。

    f=lambda x,y:x+y
    print(f)
    
    print(f(1,2))
    
    运行结果:
    <function <lambda> at 0x0000000000B40268>
    3
    
    map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
    
    运行结果:
    [1, 4, 9, 16, 25, 36, 49, 64, 81]
    

    匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

    2.lambda用法
    (1)把匿名函数赋值给一个变量,再利用变量来调用该函数

    f = lambda x: x * x
    print(f)
    f(5)
    
    运行结果:
    <function <lambda> at 0x10453d7d0>
    25
    

    (2)匿名函数作为返回值返回

    def build(x, y):
        return lambda: x * x + y * y
    t=build(4,3)
    print(t())
    

    (3)max,min,zip,sorted的用法

    salaries={
    'egon':3000,
    'alex':100000000,
    'wupeiqi':10000,
    'yuanhao':2000
    }
    
    print(max(salaries))    #只根据字典的key进行比较
    res=zip(salaries.values(),salaries.keys())  #接收任意个参数,返回一个元组列表
    print(res)
    print(list(res))
    #匿名函数用法
    print(max(salaries,key=lambda k:salaries[k]))
    print(min(salaries,key=lambda k:salaries[k]))
    print(sorted(salaries))      #默认的排序结果是从小到到
    print(sorted(salaries,key=lambda x:salaries[x]))        #默认的排序结果是从小到大
    print(sorted(salaries,key=lambda x:salaries[x],reverse=True))     #默认的排序结果是从小到大
    
    运行结果:
    yuanhao
    <zip object at 0x0000000000A17A88>
    [(3000, 'egon'), (100000000, 'alex'), (2000, 'yuanhao'), (10000, 'wupeiqi')]
    
    alex
    yuanhao
    ['alex', 'egon', 'wupeiqi', 'yuanhao']
    ['yuanhao', 'egon', 'wupeiqi', 'alex']
    ['alex', 'wupeiqi', 'egon', 'yuanhao']
    

    3.global用法
    如果你想要为一个定义在函数外的变量赋值,那么你就得告诉Python这个变量名不是局部的,而是全局的。
    我们使用global语句完成这一功能。没有global语句,是不可能为定义在函数外的变量赋值的。

    注意:应该尽量避免这样做,因为这使得程序的读者会不清楚这个变量是在哪里定义的。

    x=1000
    def f1():
        x=0     #只在局部生效
    
    f1()
    print(x)    #打印的是全局变量x
    
    运行结果:
    1000
    
    x=1000
    def f1():
        global x   #调用全局变量x
        x=0
    
    f1()
    print(x)
    
    运行结果:
    0
    

    4.内置函数

    • map函数
      Map接受一个方法和一个集合作为参数。它创建一个新的空集合,以每一个集合中的元素作为参数调用这个传入的方法,然后把返回值插入到新创建的集合中。
    l=['alex','wupeiqi','yuanhao']
    res=map(lambda x:x+'_SB',l)
    print(res)
    print(list(res))
    
    运行结果:
    <map object at 0x00000000007FB9B0>
    ['alex_SB', 'wupeiqi_SB', 'yuanhao_SB']
    
    nums=(2,4,9,10)
    res1=map(lambda x:x**2,nums)
    print(list(res1))
    
    运行结果:
    [4, 16, 81, 100]
    
    • reduce函数
      reduce()传入的函数必须接收两个参数,reduce()对list的每个元素反复调用函数,并返回最终结果值。
    from functools import reduce
    
    l=[1,2,3,4,5]
    print(reduce(lambda x,y:x+y,l,10))    #reduce(f,[1,2,3,4],10) #1+2+3+4+10
    
    运行结果:
    25
    
    • filter函数
      对每个元素进行判断,返回True或False,filter()根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新list。
    l=['alex_SB','wupeiqi_SB','yuanhao_SB','egon']
    
    res=filter(lambda x:x.endswith('SB'),l)
    print(res)
    print(list(res))
    
    运行结果:
    <filter object at 0x000000000108BA58>
    ['alex_SB', 'wupeiqi_SB', 'yuanhao_SB']
    

    四、面向过程编程与函数编程

    1.概念
    函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。

    而函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。

    我们首先要搞明白计算机(Computer)和计算(Compute)的概念。

    在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。

    而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。

    对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。

    函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

    函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

    Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

    2.总结
    面向过程解释:
    函数的参数传入,是函数吃进去的食物,而函数return的返回值,是函数拉出来的结果,面向过程的思路就是,把程序的执行当做一串首尾相连的函数,一个函数吃,拉出的东西给另外一个函数吃,另外一个函数吃了再继续拉给下一个函数吃。。。

    例如:
    用户登录流程:前端接收处理用户请求-》将用户信息传给逻辑层,逻辑词处理用户信息-》将用户信息写入数据库
    验证用户登录流程:数据库查询/处理用户信息-》交给逻辑层,逻辑层处理用户信息-》用户信息交给前端,前端显示用户信息

    3.高阶函数

    • map函数应用
    array=[1,3,4,71,2]
    
    ret=[]
    for i in array:
        ret.append(i**2)
    print(ret)
    
    #如果我们有一万个列表,那么你只能把上面的逻辑定义成函数
    def map_test(array):
        ret=[]
        for i in array:
            ret.append(i**2)
        return ret
    
    print(map_test(array))
    
    #如果我们的需求变了,不是把列表中每个元素都平方,还有加1,减一,那么可以这样
    def add_num(x):
        return x+1
    def map_test(func,array):
        ret=[]
        for i in array:
            ret.append(func(i))
        return ret
    
    print(map_test(add_num,array))
    #可以使用匿名函数
    print(map_test(lambda x:x-1,array))
    
    #上面就是map函数的功能,map得到的结果是可迭代对象
    print(map(lambda x:x-1,range(5)))
    
    运行结果:
    [1, 9, 16, 5041, 4]
    [1, 9, 16, 5041, 4]
    [2, 4, 5, 72, 3]
    [0, 2, 3, 70, 1]
    <map object at 0x0000000001503A20>
    
    • reduce函数应用
    from functools import reduce
    #合并,得一个合并的结果
    array_test=[1,2,3,4,5,6,7]
    array=range(100)
    
    #报错啊,res没有指定初始值
    def reduce_test(func,array):
        l=list(array)
        for i in l:
            res=func(res,i)
        return res
    
    # print(reduce_test(lambda x,y:x+y,array))
    
    #可以从列表左边弹出第一个值
    def reduce_test(func,array):
        l=list(array)
        res=l.pop(0)
        for i in l:
            res=func(res,i)
        return res
    
    print(reduce_test(lambda x,y:x+y,array))
    
    #我们应该支持用户自己传入初始值
    def reduce_test(func,array,init=None):
        l=list(array)
        if init is None:
            res=l.pop(0)
        else:
            res=init
        for i in l:
            res=func(res,i)
        return res
    
    # print(reduce_test(lambda x,y:x+y,array))
    print(reduce_test(lambda x,y:x+y,array,50))
    
    运行结果:
    4950
    5000
    
    • filter函数应用
    movie_people=['alex','wupeiqi','yuanhao','sb_alex','sb_wupeiqi','sb_yuanhao']
    
    def tell_sb(x):
        return x.startswith('sb')
    
    def filter_test(func,array):
        ret=[]
        for i in array:
            if func(i):
                ret.append(i)
        return ret
    
    print(filter_test(tell_sb,movie_people))
    
    #函数filter,返回可迭代对象
    print(filter(lambda x:x.startswith('sb'),movie_people))
    
    运行结果:
    ['sb_alex', 'sb_wupeiqi', 'sb_yuanhao']
    <filter object at 0x0000000001487C18>
    
  • 相关阅读:
    ASP.NET MVC Ajax下载文件(使用NPOI向现有的excel模板文件里面添加数据)
    Devexpress MVC DateEdit 设置默认的Time
    SQL 行转列(列的值不规则的数目)
    靶机Cyberry
    PHP-Audit-Labs-Day1
    DASCTF七月赛两道Web题复现
    靶机BlackMarket
    靶机CH4INRULZ_v1.0.1
    Kali中John的使用方法
    虚拟机中桥接模式和NAT模式以及仅主机模式的区别
  • 原文地址:https://www.cnblogs.com/tongxiaoda/p/7595847.html
Copyright © 2020-2023  润新知