Python函数式介绍二 - 链式调用

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

上一篇文章中我们发现我们的代码越来越长了,而且都挤在一行,代码越长,越不易读。括号一层嵌一层,真的容易绕晕。我一直觉得代码要写给人看,一直追求代码即注释这种程度的简单。

那有什么办法来简化这个问题?答案在标题,链式调用。我们仿照C#LINQ的链式接口。直接上代码,这分代码是我自己写的,很简单。有兴趣可以自己研究。里面封装了我觉得比较重要的几个高阶函数。如果不够的话也可以简单地再封装一下。如下,

#文件名FT.py
from functools import reduce
from collections import Iterator
from itertools import chain,groupby,product

class From:
    src=None
    def __init__(self,src):
        self.src=src

    def toList(self):
        return list(self.src)

    def toSet(self):
        return set(self.src)

    def toTuple(self):
        return tuple(self.src)

    def getSource(self):
        return self.src

    def map(self,func):
        return From(map(func,self.src))

    def filter(self,predicate):
        return From(filter(predicate,self.src))

    def reduce(self,func,identity=None):
        if identity is None:
            return reduce(func,self.src)
        else:
            return reduce(func,self.src,identity)

    def chain(self):
        return From(chain.from_iterable(self.src))

    def groupby(self,func=None):
        return From(map(lambda it:(it[0],list(it[1])),groupby(self.src,func)))

    def product(self,tag):
        return From(product(self.src,tag))

    def all(self,predicate):
        return all(map(lambda it:predicate(it),self.src))

    def any(self,predicate):
        return any(map(lambda it:predicate(it),self.src))

    def first(self,predicate=None):
        if predicate is None:
            if isinstance(self.src,Iterator):
                return next(self.src)
            return next(iter(self.src))
        else :
            return next(filter(predicate,self.src))

    def firstOrNone(self,predicate=None):
        try:
            if predicate is None:
                if isinstance(self.src,Iterator):
                    return next(self.src)
                return next(iter(self.src))
            else :
                return next(filter(predicate,self.src))
        except StopIteration:
            return None

以上代码我写了一个From类,后面的代码会反反复复地用到这个类。

复习map/filter/reduce

例子1 ,过滤出列表中的偶数,再乘2

from FT import From

print(From((1,2,3,4))
    .filter(lambda it:it % 2==0)
    .map(lambda it:it*2).toList())
'''
结果:
[4,8]
'''

例子2,过滤出列表中地偶数,结果乘2,最后再求和。

from FT import From

print(From((1,2,3,4))
    .filter(lambda it:it % 2==0)
    .map(lambda it:it*2).reduce(lambda acc,it:acc+it))
#加幺元
print(From((1,2,3,4))
    .filter(lambda it:it % 2==0)
    .map(lambda it:it*2).reduce(lambda acc,it:acc+it))
'''
结果:
12
12
'''

对比上篇文章,有没有觉得清晰很多,清晰地看到数据流一步一步地往下一个函数流。这个也是函数式地特点之一。python本不提供这个流式接口,没关系,我们自己造一个,而且没有花几行代码。居然全部代码都能在一篇文章中显示出来,不过这个跟python自己动态语言有关,若换成静态语言的话那应该要花多不少功夫。下次找机会我用C++实现一遍。

下面我们学多几个高阶函数,丰富我们的武器库。

groupby

分组是一个很常见的需求,需要的代码其实也不少,封装成高阶函数后那方便太多了。请看下面的例子

testgroupdata=[{"id":1,"name":"wwb"},{"id":1,"name":"wxa"},{"id":1,"name":"wxb"},{"id":2,"name":"wxc"},{"id":2,"name":"wxd"}]

这个是数据集。是我随便造的,没什么特殊意义。

例子1,根据id分组,显示分组

print(From(testgroupdata).groupby(lambda it:it['id']).toList())
'''
结果:
[
    (
        1,
        [
            {
                'id': 1,
                'name': 'wwb'
            },
            {
                'id': 1,
                'name': 'wxa'
            },
            {
                'id': 1,
                'name': 'wxb'
            }
        ]
    ),
    (
        2,
        [
            {
                'id': 2,
                'name': 'wxc'
            },
            {
                'id': 2,
                'name': 'wxd'
            }
        ]
    )
]
'''

当然groupby后可以自己接其他高阶函数,如下例子
例子2,根据id分组后,过滤出分组的KEY为偶数的数据,也就是id为偶数的数据

print(From(testgroupdata).groupby(lambda it:it['id']).filter(lambda it:it[0]%2==0).toList())
'''
结果:
[
    (
        2,
        [
            {
                'id': 2,
                'name': 'wxc'
            },
            {
                'id': 2,
                'name': 'wxd'
            }
        ]
    )
]
'''

笛卡尔积

笛卡儿积太重要了,数据库两种表连接或join都可以解释为笛卡儿积,看下面的例子。

例子1,观察执行结果

print(From((1,2,3)).product(('a','b','c')).toList())

现在我们考虑一个图书馆管理系统,先观察以下数据集

students = [
    {
        "name":"wwb",
        "book":[1,2,5]
    },
    {
        "name":"wxa",
        "book":[1,2,3]
    },
    {
        "name":"wxb",
        "book":[2,3,4]
    }
]
books = [
    {
        "id":1,
        "name":"C++ Primer"
    },
    {
        "id":2,
        "name":"Effecitve C++"
    },
    {
        "id":3,
        "name":"语文"
    },{
        "id":4,
        "name":"数学"
    },
    {
        "id":5,
        "name":"英语"
    }
]

students是图书馆借了数的同学,一个人可以借多本书,其中book记录的是图书的id;books是图书馆里面的书。现在可以看例子了。

例子2,求学生具体借了什么书

print(From(students).map(lambda s:
{
    "name":s["name"],
    "book":From(s["book"]).product(books).filter(lambda it:it[0]==it[1]["id"])
        .map(lambda it:it[1]["name"]).toList()
}).toList())
'''
结果:
[
    {
        'name': 'wwb',
        'book': [
            'C++ Primer',
            'Effecitve C++',
            '英语'
        ]
    },
    {
        'name': 'wxa',
        'book': [
            'C++ Primer',
            'Effecitve C++',
            '语文'
        ]
    },
    {
        'name': 'wxb',
        'book': [
            'Effecitve C++',
            '语文',
            '数学'
        ]
    }
]
'''

first/firstOrNone

这两个函数简单,就是找出符合条件的第一条数据。我常常需要这样的需求。直接看例子

#找出名字为wwb的同学,找不到的话则会抛出异常
print(From(students).first(lambda it:it["name"]=="wwb"))
#找出名字为wxx的同学,找不到的话返回None
print(From(students).firstOrNone(lambda it:it["name"]=="wxx"))
'''
结果:
{'name': 'wwb', 'book': [1, 2, 5]}
None
'''

first当找不到满足条件的数据会抛异常,而firstOrNone则会返回None。很简单。

chain

今天解释最后一个函数,chain。这个是一个非常重要的函数。我自己经常叫它扁平,用来变平数据。如
((a,b,c),(d,e,f),(g,h,i)) => (a,b,c,d,e,f,g,h,i)经过chain后,你会发现括号少了一层,原来'尖'的数据现在变'平'了,因为少了一层括号。看例子

print(From(((1,2,3),(4,5,6),(1,2))).chain().toList())
'''
结果:
[1, 2, 3, 4, 5, 6, 1, 2]
'''

回到上一篇文章统计选修数学的同学的平均分。数据集如下

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":92
            },
            {
                "name":"Music",
                "score":80
            }
        ]
    },
]

求选修数学同学的平均分

studentmaths = From(students).map(lambda s: From(s["course"]).map(lambda c:
{
    "name":s["name"],
    "sex":s["sex"],
    "course":c["name"],
    "score":c["score"]
}).toList()).chain().filter(lambda it:it["course"]=="Math").toList()

#先打印出来看看
print(From(studentmaths).reduce(lambda acc,s:acc+s["score"],0)/len(studentmaths))
'''
结果:
[{'score': 90, 'name': 'wwb', 'sex': '1', 'course': 'Math'}, {'score': 92, 'name': 'wxb', 'sex': '1', 'course': 'Math'}]
91.0
'''

总结

高阶函数和链式调用终于讲完了,有没有发现这些例子都好简洁,基本上多复杂的需求都只用一条链子,一直连击。而且不失可读性。高阶函数需要练习才会熟悉,像sql语句一样,既简单又复杂。预告下篇文章讲组合,就用我一年前写的玩具模板引擎为例子,200行左右。

测试代码

同样我自己把测试代码贴出来吧,当然需要和文章开篇的FT.py放在一起才可以执行。

from FT import From

print(From((1,2,3,4))
    .filter(lambda it:it % 2==0)
    .map(lambda it:it*2).toList())

print(From((1,2,3,4))
    .filter(lambda it:it % 2==0)
    .map(lambda it:it*2).reduce(lambda acc,it:acc+it))

print(From((1,2,3,4))
    .filter(lambda it:it % 2==0)
    .map(lambda it:it*2).reduce(lambda acc,it:acc+it,0))


testgroupdata=[{"id":1,"name":"wwb"},{"id":1,"name":"wxa"},{"id":1,"name":"wxb"},{"id":2,"name":"wxc"},{"id":2,"name":"wxd"}]
print(From(testgroupdata).groupby(lambda it:it['id']).toList())
print(From(testgroupdata).groupby(lambda it:it['id']).filter(lambda it:it[0]%2==0).toList())

#笛卡尔积
print(From((1,2,3)).product(('a','b','c')).toList())
students = [
    {
        "name":"wwb",
        "book":[1,2,5]
    },
    {
        "name":"wxa",
        "book":[1,2,3]
    },
    {
        "name":"wxb",
        "book":[2,3,4]
    }
]
books = [
    {
        "id":1,
        "name":"C++ Primer"
    },
    {
        "id":2,
        "name":"Effecitve C++"
    },
    {
        "id":3,
        "name":"语文"
    },{
        "id":4,
        "name":"数学"
    },
    {
        "id":5,
        "name":"英语"
    }
]

print(From(students).map(lambda s:
{
    "name":s["name"],
    "book":From(s["book"]).product(books).filter(lambda it:it[0]==it[1]["id"])
        .map(lambda it:it[1]["name"]).toList()
}).toList())

#只找一个
print(From(students).first(lambda it:it["name"]=="wwb"))
print(From(students).firstOrNone(lambda it:it["name"]=="wxx"))

#chain是一个很重要的函数
# 目的 ((a,b,c),(d,e,f),(g,h,i)) => (a,b,c,d,e,f,g,h,i),去掉一层括号
print(From(((1,2,3),(4,5,6),(1,2))).chain().toList())
#回到上次的例子

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":92
            },
            {
                "name":"Music",
                "score":80
            }
        ]
    },
]

#求选修数学同学的平均分

print(From(students).map(lambda s: From(s["course"]).map(lambda c:
{
    "name":s["name"],
    "sex":s["sex"],
    "course":c["name"],
    "score":c["score"]
}).toList()).chain().toList())

studentmaths = From(students).map(lambda s: From(s["course"]).map(lambda c:
{
    "name":s["name"],
    "sex":s["sex"],
    "course":c["name"],
    "score":c["score"]
}).toList()).chain().filter(lambda it:it["course"]=="Math").toList()

print(studentmaths)

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

推荐阅读更多精彩内容