Python函数式介绍一 - 高阶函数

作者: 文彬0L0 | 来源:发表于2017-09-27 16:59 被阅读0次

    Python函数式介绍一 - 高阶函数
    Python函数式介绍二 - 链式调用

    最近为了给朋友推广Python函数式编程,特意准备写一系列文章,当然我不敢说自己已经把函数式琢磨透了,我觉得在我在函数式编程这道路上还有很长的路需要走。

    我们还是从高阶函数入手吧,比较容易突破。

    在这里我们会用到lambda函数也就是匿名函数,我们就理解成是没有名字的函数把。

    map

    students = [
        {
            "name":"wwb",
            "sex":"1",
            "course":[
                {
                    "name":"Math",
                    "score":90
                },
                {
                    "name":"English",
                    "score":80
                }
            ]
        },
        {
            "name":"wxa",
            "sex":"1",
            "course":[
                {
                    "name":"Music",
                    "score":90
                },
                {
                    "name":"English",
                    "score":80
                }
            ]
        },
        {
            "name":"wxb",
            "sex":"1",
            "course":[
                {
                    "name":"Math",
                    "score":90
                },
                {
                    "name":"Music",
                    "score":80
                }
            ]
        },
    ]
    

    以上是我们需要测试的数据,我们做一个稍微简单的需求,求选修数学的学生。

    先一步一步来,把问题分解,看以下数据

    _course = [
        {
            "name":"Math",
            "score":90
        },
        {
            "name":"Music",
            "score":80
        }
    ]
    

    求_course各个课程是否为Math,只需要一行

    print(list(map(lambda it:it["name"]=="Math",_course)))
    '''
    结果:
    [True,False]
    '''
    

    any,all

    先看结果

    print(any(map(lambda it:it["name"]=="Math",_course)))
    print(all(map(lambda it:it["name"]=="Math",_course)))
    print(any((True,True,True,False)))
    print(any((False,False)))
    print(all((True,True,True)))
    print(all((True,False)))
    '''
    结果:
    True
    False
    True
    False
    True
    False
    '''
    

    any函数传入的集合中一个为True则any结果为Ture,若集合全为Flase则结果为False
    all函数传入的集合必须所有都为True结果才为True,否则为False

    求_course中是否包含Math,只需要一行

    print(any(map(lambda it:it["name"]=="Math",_course)))
    '''
    结果:
    True
    '''
    

    惰性求值

    请看以下例子

    
    print("test1")
    print(map(lambda it:it["name"]=="Math",_course))
    
    print("test2")
    print(list(map(lambda it:it["name"]=="Math",_course)))
    
    def fun0(it):
        print(it)
        return it["name"]=="Math"
    
    print("test3")
    "执行所有操作"
    print(any(list(map(fun0,_course))))
    
    print("test4")
    "map并没有执行完所有元素,遇到一个true直接返回"
    print(any(map(fun0,_course)))
    
    '''
    结果:
    test1
    <map object at 0x0000000004CD4400>
    test2
    [True, False]
    test3
    {'name': 'Math', 'score': 90}
    {'name': 'Music', 'score': 80}
    True
    test4
    {'name': 'Math', 'score': 90}
    True
    '''
    

    我们直接把map结果打印出来会发现是一个map对象,事实上这个对象还没有做任何操作,如果我们对map对象执行list操作,这个时候map处理集合的数据。test4可以发现any一旦遇到True就直接返回了,剩下的元素map不会去处理。

    这个特性叫惰性求值,当我们真的需要map object的结果的时候,map才会开始对元素进行加工处理。而且是需要map object一个元素,map就处理一个元素,一个都不会处理多。这个就保证了不会浪费cpu的资源,当我map一个1000个元素的集合,实际上我只取其中的一个返回值,则python是不会浪费cpu去处理剩下的999个元素的。

    如果你list一个map对象,则map会操作集合的所有元素,返回一个结果集合。看这个是不是就是数学映射的概念?一个集合转化成另一个一一对应的集合。所以python把这个操作取名为map也就是映射的意思。

    filter

    filter函数和它的名字一样做的是过滤的操作,在一个集合中筛选出符合条件的数据

    求选修数学的同学

    def findmath(courses):
        return any(map(lambda it:it["name"]=="Math",courses))
    
    print(list(filter(lambda it:findmath(it["course"]),students)))
    '''
    结果:
    [
        {
            'name': 'wwb', 
            'sex': '1', 
            'course': 
            [
                {'name': 'Math', 'score': 90}, 
                {'name':'English', 'score': 80}
            ]
        }, 
        {
            'name': 'wxb', 
            'sex': '1', 
            'course': [
                {'name': 'Math', 'score': 90 },
                {'name': 'Music','score': 80 }
            ]
        }
    ]
    '''
    

    reduce

    先看一个简单的例子

    from functools import reduce
    ilist = [1,2,3,4,5,6,7,8,9,10]
    print(reduce(lambda acc,it:acc+it,ilist,0))
    print(reduce(lambda acc,it:acc+it,ilist))
    '''
    结果:
    55
    55
    '''
    

    reduce(lambda acc,it:acc+it,ilist,0)
    一开始acc为0,it为列表第一项ilist[0],
    接着 acc为 (ilist[0] + 0) ,it 为 ilist[1]
    接着 acc为 ((ilist[0] + 0) + ilist[1]),it 为 ilist[2]
    接着 acc为 (((ilist[0] + 0) + ilist[1])+ilist[2]),it 为 ilist[3]
    ...
    直到最后一项,返回最后一次运行的结果。

    一般我称acc为累计值,0为幺元,幺元是一个数学概念,是相对运算而来的,比如

        0是加减法的幺元,任何元素(+或-) 0 都等于本身
        1是乘除法的幺元,任何元素(*或/) 1 都等于本身
        ""是字符串加法的幺元,任何字符串(除了NULL)加 "" 都等于本身
    

    一般我都习惯在reduce函数加上幺元,reduce也支持不加幺元的情况,不加幺元则,acc初始值为集合的第一个元素

    reduce(lambda acc,it:acc+it,ilist)
    一开始acc为ilist[0],it为列表第一项ilist[1],
    接着 acc为 (ilist[0] + ilist[1]),it 为 ilist[2]
    ...
    直到最后一项,返回最后一次运行的结果。

    如果不传幺元则当列表ilist有0个元素的时候,reduce函数会报错

    统计选修数学平均分

    我们简化下问题,不然又再学几个新函数,我们就用上面学过的map/filter/any/reduce函数

    以下是选修数学的学生的数学成绩

    studentmaths=[
        {
            "name":"wwb",
            "sex":1,
            "score":90,
        },
        {
            "name":"wxa",
            "sex":1,
            "score":91,
        },
        {
            "name":"wxb",
            "sex":0,
            "score":92,
        },
        {
            "name":"wxc",
            "sex":0,
            "score":93,
        },
        {
            "name":"wxd",
            "sex":0,
            "score":94,
        },
    ]
    

    求总分

    print(reduce(lambda acc,it:acc+it["score"],studentmaths,0))
    '''
    结果:
    460
    '''
    

    求平均分

    print(reduce(lambda acc,it:acc+it["score"],studentmaths,0)/len(studentmaths))
    '''
    结果:
    92.0
    '''
    

    打印班上所有同学的成绩

    print(reduce(lambda acc,it:acc + "name:%s,score:%d\r\n"%(it["name"],it["score"]),studentmaths,""))
    '''
    结果:
    name:wwb,score:90
    name:wxa,score:91
    name:wxb,score:92
    name:wxc,score:93
    name:wxd,score:94
    '''
    

    我们来看一个复杂的幺元,求女生平均分

    temp = reduce(lambda acc,it: { "sum":acc["sum"]+it["score"],"count":acc["count"]+1},filter(lambda s:s["sex"]==0,studentmaths),{"sum":0,"count":0})
    print(temp)
    print(temp["sum"]/temp["count"])
    '''
    结果:
    {'sum': 279, 'count': 3}
    93.0
    '''
    

    当然可以更简单些,以上写法是为了解释幺元,展示reduce的复杂写法,其实就这个例子而论,没什么必要这么写,可以写成以下简单的写法

    fstudentmaths = list(filter(lambda s:s["sex"]==0,studentmaths))
    print(reduce(lambda acc,it:acc+it["score"],fstudentmaths,0)/len(fstudentmaths))
    '''
    结果:
    93.0
    '''
    

    打印男生成绩单

    print(reduce(lambda acc,it:acc + "name:%s,score:%d\r\n"%(it["name"],it["score"]),filter(lambda s:s["sex"]==1,studentmaths),""))
    '''
    结果:
    name:wwb,score:90
    name:wxa,score:91
    '''
    

    总结

    利用python自带的map/filter/reduce/all/any函数我们可以改写大部分for/while循环。我们利用python的这个高级函数你会发现你的思维方式会发生改变,你会习惯这种集合转化来转化去的思维。

    而for/while则是串行的,逐个处理的思想。自己写for/while其实很容易出错的,用高级函数都是一些简单的lambda函数组合在一起,想出错也不太容易。

    我个人认为程序结构,顺序、条件、循环,循环是最复杂的结构,如果一段程序中嵌套三四个循环,每个循环几百行,那这段代码基本不用看了。高阶函数是用集合的观点来处理数据的,化循环为顺序结构,能让程序更容易理解

    另外,大家有没有发现最后面函数写得很长很长,而且很不好阅读,没关系,下节我们稍微加工一下,变成链式调用方式

    测试代码

    我直接把上面的测试代码贴出来吧

    from functools import reduce
    
    students = [
        {
            "name":"wwb",
            "sex":"1",
            "course":[
                {
                    "name":"Math",
                    "score":90
                },
                {
                    "name":"English",
                    "score":80
                }
            ]
        },
        {
            "name":"wxa",
            "sex":"1",
            "course":[
                {
                    "name":"Music",
                    "score":90
                },
                {
                    "name":"English",
                    "score":80
                }
            ]
        },
        {
            "name":"wxb",
            "sex":"1",
            "course":[
                {
                    "name":"Math",
                    "score":90
                },
                {
                    "name":"Music",
                    "score":80
                }
            ]
        },
    ]
    
    _course = [
        {
            "name":"Math",
            "score":90
        },
        {
            "name":"Music",
            "score":80
        }
    ]
    "是否选修数学课"
    print(map(lambda it:it["name"]=="Math",_course))
    
    "显示结果"
    print(list(map(lambda it:it["name"]=="Math",_course)))
    
    "all,any"
    print(any(map(lambda it:it["name"]=="Math",_course)))
    print(all(map(lambda it:it["name"]=="Math",_course)))
    
    "惰性求值测试"
    def fun0(it):
        print(it)
        return it["name"]=="Math"
    
    "执行所有操作"
    print(any(list(map(fun0,_course))))
    
    "map并没有执行完所有元素,遇到一个true直接返回"
    print(any(map(fun0,_course)))
    
    "filter"
    "找出选修数学的所有学生"
    def findmath(courses):
        return any(map(lambda it:it["name"]=="Math",courses))
    
    print(list(filter(lambda it:findmath(it["course"]),students)))
    
    "reduce"
    "统计选修数学平均分"
    "以下是选修数学的学生的数学成绩"
    
    studentmaths=[
        {
            "name":"wwb",
            "sex":1,
            "score":90,
        },
        {
            "name":"wxa",
            "sex":1,
            "score":91,
        },
        {
            "name":"wxb",
            "sex":0,
            "score":92,
        },
        {
            "name":"wxc",
            "sex":0,
            "score":93,
        },
        {
            "name":"wxd",
            "sex":0,
            "score":94,
        },
    ]
    
    "reduce简单例子"
    ilist = [1,2,3,4,5,6,7,8,9,10]
    print(reduce(lambda acc,it:acc+it,ilist,0))
    print(reduce(lambda acc,it:acc+it,ilist))
    
    "总分"
    print(reduce(lambda acc,it:acc+it["score"],studentmaths,0))
    "平均分"
    print(reduce(lambda acc,it:acc+it["score"],studentmaths,0)/len(studentmaths))
    "把班上同学的成绩打印出来"
    print(reduce(lambda acc,it:acc + "name:%s,score:%d\r\n"%(it["name"],it["score"]),studentmaths,""))
    "女生平均分"
    temp = reduce(lambda acc,it: { "sum":acc["sum"]+it["score"],"count":acc["count"]+1},filter(lambda s:s["sex"]==0,studentmaths),{"sum":0,"count":0})
    print(temp)
    print(temp["sum"]/temp["count"])
    "更简单些写法"
    fstudentmaths = list(filter(lambda s:s["sex"]==0,studentmaths))
    print(reduce(lambda acc,it:acc+it["score"],fstudentmaths,0)/len(fstudentmaths))
    
    "男生成绩单"
    print(reduce(lambda acc,it:acc + "name:%s,score:%d\r\n"%(it["name"],it["score"]),filter(lambda s:s["sex"]==1,studentmaths),""))
    
    

    相关文章

      网友评论

        本文标题:Python函数式介绍一 - 高阶函数

        本文链接:https://www.haomeiwen.com/subject/wuwuextx.html