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

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),""))

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容