函数式编程

  • 不可变数据结构
  • 三个基本函数
    • filter()
      • 再做一些练习来熟悉filter()
      • 再做一些思考:
    • map()
      • 再做一些思考:
      • 再再做一些思考:
    • reduce()
      • 再次注意的是:
    • 小结:
  • 并行计算
    • 进一步探索:
    • concurrent.futures
    • 多说一点点:
  • 总结:
    • 最后的最后:

关于什么是函数式编程,百度一下可以找到很多解释,这里放上我在Wiki上找的一段:

functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.

简单点理解,函数式编程和面向对象编程、面向过程编程一样,是一种编程方式,它反映了一种映射思想,又有点类似建模思想。从输入到输出,输入数据不可被更改,最终返回输出结果。它非常非常的依赖不可变的数据结构,这使得使用这种编程方式可以减少错误的可能性,并确保程序更易于维护。

之前在知乎上看见一篇不错的帖子,各抒己见,有兴趣可以去看看:什么是函数式编程思维?

以下通过简单的示例代码,说一下函数式编程:

不可变数据结构

函数式编程操作的是不可变数据,在基于数据构建数据结构时,可以使用Python的内置模块collections中的namedtulple()函数来创建不可变的数据结构。

import collectionsScientist = collections.namedtuple('Scientist', ['name','field','born','nobel'
])__scientists = (Scientist(name = 'Ada Lovelace', field = 'math', born = 1815, nobel = False),Scientist(name = 'Emmy Noether', field = 'math', born = 1882, nobel = False),Scientist(name = 'Marie Curie', field = 'math', born = 1867, nobel = True),Scientist(name = 'Tu Youyou', field = 'physics', born = 1930, nobel = True),Scientist(name = 'Ada Yonath', field = 'chemistry', born = 1939, nobel = True),Scientist(name = 'Vera Rubin', field = 'astronomy', born = 1928, nobel = False),Scientist(name = 'Sally Ride', field = 'physics', born = 1951, nobel = False)
)def getData():return __scientists

上述代码创建了一个不可变的数据结构并实例化,并提供getData()函数供外部程序调用。这种数据结构在并行计算中很重要,因为它不能被修改,所以,会避免因为数据被更改导致被锁。
再仔细观察一下代码,看看整个创建过程是如何实现的。
首先利用namedtuple创建一个tuple的子类,从namedtuple字面翻译可以理解为具名元组,该子类的名字为Scientist,子类里具有的字段为‘name’,‘field’,‘born’,‘nobel’。

Scientist = collections.namedtuple('Scientist', ['name','field','born','nobel'
])

然后创建元组__scientists,元组中的每个元素,均为实例化的Scientist类。

__scientists = (Scientist(name = 'Ada Lovelace', field = 'math', born = 1815, nobel = False),Scientist(name = 'Emmy Noether', field = 'math', born = 1882, nobel = False),Scientist(name = 'Marie Curie', field = 'math', born = 1867, nobel = True),Scientist(name = 'Tu Youyou', field = 'physics', born = 1930, nobel = True),Scientist(name = 'Ada Yonath', field = 'chemistry', born = 1939, nobel = True),Scientist(name = 'Vera Rubin', field = 'astronomy', born = 1928, nobel = False),Scientist(name = 'Sally Ride', field = 'physics', born = 1951, nobel = False)
)

这么做的目的或者好处,之前已经反复提到,就是为了创建一个不可变的数据结构。为了对比,我们可以在Python自带的IDLE里进行简单的测试:
在以下的测试中,我将分别使用上述方法创建不可变数据类型Scientist和字典类型的Scientist_dict,并对其中的元素进行更改操作。
首先是Scientist类:

# 创建Scientist类
>>> Scientist = namedtuple('Scientist',['name','field','born','nobel'])
>>> Scientist
<class '__main__.Scientist'>
#创建实例:
>>> ada =  Scientist('Ada Lovelace','math',1815,False)
#获取实例特定字段数据
>>> ada.name
'Ada Lovelace'
>>> ada.field
'math'
#更改实例变量数据,会提示错误信息,数据不可更改。
>>> ada.name = 'Ed Lovelace'
Traceback (most recent call last):File "<pyshell#8>", line 1, in <module>ada.name = 'Ed Lovelace'
AttributeError: can't set attribute

创建字典:Scientists_dict_ada

>>> Scientists_dict_ada={'name':'Ada Lovelace','field':'math','born':1815,'nobel':False}
#获取特定字段的数据
>>> Scientists_dict_ada['name']
'Ada Lovelace'
#更改特定字段的数据
>>> Scientists_dict_ada['name']='Ed Lovelace'
>>> Scientists_dict_ada['name']
'Ed Lovelace'

可以看到,如果定义字典,则数据可以很容易被更改。

上述测试是针对一组数据,如果多组数据,通常我们会将数据放入元组,因为元组本身是不可变数据类型,但是如果元组内的元素是可变的数据类型,则元组内的某个元素的数据是可以被更改的。基于此,我们继续进行测试,我们再分别创建一组数据,并把它们分别放入元组,代码如下:

#创建Scientist实例构成的元组
>>> __Scientists = (Scientist('Ada Lovelace','math',1815,False),Scientist('Ada1 Lovelace','math',1815,True))
>>> __Scientists
(Scientist(name='Ada Lovelace', field='math', born=1815, nobel=False), Scientist(name='Ada1 Lovelace', field='math', born=1815, nobel=True))
#创建由字典构成的元组
>>> Scientists_dict = ({'name':'Ada Lovelace','field':'math','born':1815,'nobel':False},{'nime':'Ada1 Lovelace','field':'math','born':1815,'nobel':True})
>>> Scientists_dict
({'name': 'Ada Lovelace', 'field': 'math', 'born': 1815, 'nobel': False}, {'nime': 'Ada1 Lovelace', 'field': 'math', 'born': 1815, 'nobel': True})

注意在第二个字典数据里,我将字段’name’改写成了’nime’,但是程序并没有识别出问题,依然正常录入。这种情况在上边的Scientist的实例里是不会出现的。
继续对元组里的数据进行更改操作:

#对__Scientists元素进行更改
>>> __Scientists[0]
Scientist(name='Ada Lovelace', field='math', born=1815, nobel=False)
>>> __Scientists[0]['name']='ada'
Traceback (most recent call last):File "<pyshell#38>", line 1, in <module>__Scientists[0]['name']='ada'
TypeError: 'Scientist' object does not support item assignment
#对Scientist_dict元素进行更改
>>> Scientists_dict[0]
{'name': 'Ada Lovelace', 'field': 'math', 'born': 1815, 'nobel': False}
>>> Scientists_dict[0]['name']
'Ada Lovelace'
>>> Scientists_dict[0]['name']='ada'
>>> Scientists_dict[0]['name']
'ada'

可以看见,上述操作,因为Scientist的原因无法对__Scientists进行更改,但是却可以对Scientist_dict里的元素进行更改。

那么再思考一个问题,如果我们不把数据放入元组,而是放入可变的数据类型中,会怎么样呢?
依然通过一个简单的示例说明,代码如下:

#创建一个带有Scientist元素的数组
>>> __Scientists_with_list = [Scientist('Ada Lovelace','math',1815,False),Scientist('Ada1 Lovelace','math',1815,True)]
>>> __Scientists_with_list
[Scientist(name='Ada Lovelace', field='math', born=1815, nobel=False), Scientist(name='Ada1 Lovelace', field='math', born=1815, nobel=True)]
#更改数组内元素的字段
>>> __Scientists_with_list[0]['name']='ada'
Traceback (most recent call last):File "<pyshell#47>", line 1, in <module>__Scientists_with_list[0]['name']='ada'
TypeError: 'Scientist' object does not support item assignment
#删除数组内元素
>>> del __Scientists_with_list[0]
>>> __Scientists_with_list
[Scientist(name='Ada1 Lovelace', field='math', born=1815, nobel=True)]

上述的测试可以看见,虽然数组内的元素的某个字段由于Scientist的原因依然不可以被更改,但是,因为数组本身的原因,数组内的元素可以被删除。

经过以上一系列的对比测试可以发现,元组+namedtuple()构建的不可变数据结构在保护数据安全上的优势。

回到我们的主题,这种不可变的数据结构也是函数式编程的重要组成部分。

三个基本函数

下边介绍函数式编程的三个基本函数:我们使用上一小节创建的不可变数据来进行接下来的测试

filter()

filter(function or None, iterable) --> filter object

filter()函数接收两个参数,第一个是函数,第二个是序列。通常情况下,可以通过Lambda表达式直接定义函数作为第一个参数。
我们先做一个过滤函数,筛选出获得过诺贝尔奖的人,即nobel = True:

#引入pprint,打印输出数据结构更加完
>>> from pprint import pprint
#打印输出目前__scientists数据,方便查看
>>> pprint(tuple(__scientists))
(Scientist(name='Ada Lovelace', field='math', born=1815, nobel=False),Scientist(name='Emmy Noether', field='math', born=1882, nobel=False),Scientist(name='Marie Curie', field='math', born=1867, nobel=True),Scientist(name='Tu Youyou', field='physics', born=1930, nobel=True),Scientist(name='Ada Yonath', field='chemistry', born=1939, nobel=True),Scientist(name='Vera Rubin', field='astronomy', born=1928, nobel=False),Scientist(name='Sally Ride', field='physics', born=1951, nobel=False))
#定义筛选函数
>>> def is_nobel(X):return X.nobel
#将函数作为参数,传给filter函数,结果返回给fs
>>> fs = filter(is_nobel, __scientists)
>>> pprint(tuple(fs))
(Scientist(name='Marie Curie', field='math', born=1867, nobel=True),Scientist(name='Tu Youyou', field='physics', born=1930, nobel=True),Scientist(name='Ada Yonath', field='chemistry', born=1939, nobel=True))

上述代码,可以看出,is_nobel作为参数传入filter,只传函数名,之后遍历__scientists()中的元素,当返回值为True时,存入fs。
很显然,这种单独定义一个简单函数的方式很麻烦。Lambda表达式可以很好的解决这个问题。
使用Lambda表达式完成上述过滤的代码如下:

>>> fs = tuple(filter(lambda x:x.nobel is True, __scientists))
>>> pprint(tuple(fs))
(Scientist(name='Marie Curie', field='math', born=1867, nobel=True),Scientist(name='Tu Youyou', field='physics', born=1930, nobel=True),Scientist(name='Ada Yonath', field='chemistry', born=1939, nobel=True))

再做一些练习来熟悉filter()

  1. 当条件恒为True时,返回全部元素:
>>> fs = tuple(filter(lambda x: True, __scientists))
>>> pprint(fs)
(Scientist(name='Ada Lovelace', field='math', born=1815, nobel=False),Scientist(name='Emmy Noether', field='math', born=1882, nobel=False),Scientist(name='Marie Curie', field='math', born=1867, nobel=True),Scientist(name='Tu Youyou', field='physics', born=1930, nobel=True),Scientist(name='Ada Yonath', field='chemistry', born=1939, nobel=True),Scientist(name='Vera Rubin', field='astronomy', born=1928, nobel=False),Scientist(name='Sally Ride', field='physics', born=1951, nobel=False))
  1. 当第一个参数即函数为None时,返回全部元素:
>>> fs = tuple(filter(None,__scientists))
>>> pprint(fs)
(Scientist(name='Ada Lovelace', field='math', born=1815, nobel=False),Scientist(name='Emmy Noether', field='math', born=1882, nobel=False),Scientist(name='Marie Curie', field='math', born=1867, nobel=True),Scientist(name='Tu Youyou', field='physics', born=1930, nobel=True),Scientist(name='Ada Yonath', field='chemistry', born=1939, nobel=True),Scientist(name='Vera Rubin', field='astronomy', born=1928, nobel=False),Scientist(name='Sally Ride', field='physics', born=1951, nobel=False))
  1. 过滤满足多个条件的元素:
>>> fs = tuple(filter(lambda x: x.field == 'physics' and x.nobel == True, __scientists))
>>> pprint(fs)
(Scientist(name='Tu Youyou', field='physics', born=1930, nobel=True),)

上述代码实现了field为’physics’,nobel为True的过滤。

再做一些思考:

我们通过了一系列的代码对filter()有了一定的了解,简单点说,我们之前用filter做了一件事,就是把符合条件的元素,从给定的一堆元素中筛选出来。这种需求,在python里,不要忘记另一种方法:列表解析式

>>> fs = tuple([x for x in __scientists if x.nobel is True])
>>> pprint(fs)
(Scientist(name='Marie Curie', field='math', born=1867, nobel=True),Scientist(name='Tu Youyou', field='physics', born=1930, nobel=True),Scientist(name='Ada Yonath', field='chemistry', born=1939, nobel=True))

可以看见,列表解析式也可以很方便的过滤出所求。因为主要是讲filter(),所以在这里只是提一下,就不再展开了。

map()

map(func, *iterables) --> map object

map()函数对指定的序列做映射处理。我认为,通俗点说,它的主要作用,是在原不可变数据结构的基础上,通过函数转化生成新的数据结构的数据。
map()接收两个参数,一个是函数,一个是可迭代对象。依然以之前的数据,举例说明map()的应用:
现在通过map(),创建一个新的数据结构的数据,其中只包含原数据里的名字,已经年龄(现在-出生),代码如下:

>>>res = tuple(map(lambda x: {'name':x.name,'age':2020-x.born},__scientists))
>>> pprint(res)
({'age': 205, 'name': 'Ada Lovelace'},{'age': 138, 'name': 'Emmy Noether'},{'age': 153, 'name': 'Marie Curie'},{'age': 90, 'name': 'Tu Youyou'},{'age': 81, 'name': 'Ada Yonath'},{'age': 92, 'name': 'Vera Rubin'},{'age': 69, 'name': 'Sally Ride'})

上述代码中,使用了Lambda表达式作为第一个参数,__scientists依次将数据传入Lambda表达式进行运算,并将结果映射到新的数据上。正如之前所说,它实际上就是完成了一个基于现有不可变数据,创建了一个新的数据结构的数据集。数据集具体有哪些数据,通过Lambda表达式得出。写成数学形式,就好像映射关系:
新数据 = f(旧数据)

再做一些思考:

和filter()函数类似,map()函数其实也可以使用列表解析式实现,当然,也可以使用生成器表达式。生成器表达式完成上述功能代码如下:

>>> res = tuple({'name':x.name,'age':2020-x.born} for x in __scientists)
>>> pprint(res)
({'age': 205, 'name': 'Ada Lovelace'},{'age': 138, 'name': 'Emmy Noether'},{'age': 153, 'name': 'Marie Curie'},{'age': 90, 'name': 'Tu Youyou'},{'age': 81, 'name': 'Ada Yonath'},{'age': 92, 'name': 'Vera Rubin'},{'age': 69, 'name': 'Sally Ride'})

其实一般情况下,这种使用生成器表达式的方式,更符合Python的编程思想。就是常说的Pythonic。

再再做一些思考:

既然列表解析式或者生成器表达式已经可以完成需求,为什么有的时候依然使用map()?个人理解:虽然列表解析式和生成器表达式更加的直观和Pythonic,但是,当不使用Lambda表达式而是使用同一个函数时,map()的效率会更高。举个简单例子:

>>> timeit.timeit('map(hex,xs)','xs=range(10)')
0.33299910000005184
>>> timeit.timeit('(hex(i) for i in xs)','xs=range(10)')
0.5141750999996475

同样的功能,map函数性能更高。这在处理海量数据,并行计算时,很重要。

reduce()

reduce(function, sequence[, initial]) -> value

reduce()函数可以接收三个参数,前两个是函数function和序列sequence,第三个是初始值initial(可选)。注意,reduce()函数接收含有两个参数的function,一个参数作为累加器,另一个是序列中的元素。也就是说,sequence中的元素依次作为function的第二个参数传入function进行计算。结果放入第一个参数,用做下一次计算。最终function将第一个参数作为reduce的返回值。用一个简单的例子说明:

>>> from functools import reduce
>>> res = reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])
>>> res
15

上述例子中,reduce执行了一个累加的操作。y值分别对应数组中的1,2,3,4,5。因为x没有指定初始值,python会自动识别,并附初值0。所以结果是15。
如果给x附初值3,则:

>>> res = reduce(lambda x, y: x+y, [1, 2, 3, 4, 5],3)
>>> res
18

再举一个例子,说明当reduce没有指定初始值时,python会自动识别的例子。我们将数组元素改为字符串。

>>> res = reduce(lambda x,y:x+y,['1','2','3','4','5'])
>>> res
'12345'

如果赋初始值’res’,则:

>>> res = reduce(lambda x,y:x+y,['1','2','3','4','5'],'res')
>>> res
'res12345'

上述代码可以更清晰的理解函数的运算过程。

再次注意的是:

如果我们将reduce()接收的三个参数分别命名为:function,sequence,initial(可选)
function接收两个参数分别为:acc,item。
则sequence只为item提供数据,acc初始值可以initial给定,也可以系统默认。在遍历完全部sequence后,最终acc作为reduce()的返回值返回。

理解了reduce()函数基本原理后,我们依然可以基于之前的数据进行操作。为了方便回忆,将之前的数据创建代码给出:

>>> import collections
>>> Scientist = collections.namedtuple('Scientist', ['name','field','born','nobel'
])
>>> __scientists = (Scientist(name = 'Ada Lovelace', field = 'math', born = 1815, nobel = False),Scientist(name = 'Emmy Noether', field = 'math', born = 1882, nobel = False),Scientist(name = 'Marie Curie', field = 'math', born = 1867, nobel = True),Scientist(name = 'Tu Youyou', field = 'physics', born = 1930, nobel = True),Scientist(name = 'Ada Yonath', field = 'chemistry', born = 1939, nobel = True),Scientist(name = 'Vera Rubin', field = 'astronomy', born = 1928, nobel = False),Scientist(name = 'Sally Ride', field = 'physics', born = 1951, nobel = False)
)

在map()函数的练习里,我们创建了一个新的带有名字和年龄的数据,代码如下:

>>> name_and_age = tuple({'name':x.name,'age':2020-x.born} for x in __scientists)
>>> pprint(name_and_age)
({'age': 205, 'name': 'Ada Lovelace'},{'age': 138, 'name': 'Emmy Noether'},{'age': 153, 'name': 'Marie Curie'},{'age': 90, 'name': 'Tu Youyou'},{'age': 81, 'name': 'Ada Yonath'},{'age': 92, 'name': 'Vera Rubin'},{'age': 69, 'name': 'Sally Ride'})

现在我们基于name_and_age数据,获取所有人的年龄总和。代码如下:

>>> sum_ages = reduce(lambda acc,item: acc+item['age'], name_and_age, 0)
>>> sum_ages
828

依然可以使用其他方法来实现上诉功能:

>>> sum_ages_ = sum(x['age'] for x in name_and_age)
>>> sum_ages_
828

当然,reduce()函数可以实现的功能远不止于此。看下边的代码:

>>> def reducer(acc,item):acc[item.field].append(item.name)return acc>>> scientists_by_field = reduce(reducer,__scientists,{'math':[], 'physics':[], 'chemistry':[], 'astronomy':[]})
>>> pprint(scientists_by_field)
{'astronomy': ['Vera Rubin'],'chemistry': ['Ada Yonath'],'math': ['Ada Lovelace', 'Emmy Noether', 'Marie Curie'],'physics': ['Tu Youyou', 'Sally Ride']}

以上代码实现了根据科学家领域来分类。从定义的reducer(acc, item)函数中可以看出,每一次的reducer返回的acc其实就是下一次输入的acc。初始值是{‘math’:[], ‘physics’:[], ‘chemistry’:[], ‘astronomy’:[]},这里需要注意的是,不能写错key。
这么赋初值很麻烦,对于上述代码,可以使用defaultdict():

>>> scientists_by_field = reduce(reducer,__scientists,defaultdict(list))
>>> pprint(scientists_by_field)
defaultdict(<class 'list'>,{'astronomy': ['Vera Rubin'],'chemistry': ['Ada Yonath'],'math': ['Ada Lovelace', 'Emmy Noether', 'Marie Curie'],'physics': ['Tu Youyou', 'Sally Ride']})

如果用lambda表达式,个人觉得这种写法很不好,真的没必要为了pythonic而pythonic:

>>> scientists_by_field = reduce(lambda acc, item: {**acc, **{item.field: acc[item.field]+[item.name]}},__scientists,{'math':[], 'physics':[], 'chemistry':[], 'astronomy':[]})
>>> pprint(scientists_by_field)
{'astronomy': ['Vera Rubin'],'chemistry': ['Ada Yonath'],'math': ['Ada Lovelace', 'Emmy Noether', 'Marie Curie'],'physics': ['Tu Youyou', 'Sally Ride']}

通过groupby函数可以实现类似功能:

>>> import itertools
>>> scientists_by_field = {item[0]: list(item[1])for item in itertools.groupby(__scientists, lambda x: x.field)}
>>> pprint(scientists_by_field)
{'astronomy': [Scientist(name='Vera Rubin', field='astronomy', born=1928, nobel=False)],'chemistry': [Scientist(name='Ada Yonath', field='chemistry', born=1939, nobel=True)],'math': [Scientist(name='Ada Lovelace', field='math', born=1815, nobel=False),Scientist(name='Emmy Noether', field='math', born=1882, nobel=False),Scientist(name='Marie Curie', field='math', born=1867, nobel=True)],'physics': [Scientist(name='Sally Ride', field='physics', born=1951, nobel=False)]}

小结:

以上介绍了函数式编程的三个基本函数,通过示例可以看出,其实每个函数实现的功能似乎都可以通过列表解析式和生成器表达式得出。但是不同的是,函数式编程在一些情况下处理的速度更快,特别是在并行计算过程中。下边利用函数式编程对并行计算做简单的介绍。

并行计算

先看一段代码:

#in parallel.py
import collections
from pprint import pprintScientist = collections.namedtuple('Scientist', ['name','field','born','nobel'
])__scientists = (Scientist(name = 'Ada Lovelace', field = 'math', born = 1815, nobel = False),Scientist(name = 'Emmy Noether', field = 'math', born = 1882, nobel = False),Scientist(name = 'Marie Curie', field = 'math', born = 1867, nobel = True),Scientist(name = 'Tu Youyou', field = 'physics', born = 1930, nobel = True),Scientist(name = 'Ada Yonath', field = 'chemistry', born = 1939, nobel = True),Scientist(name = 'Vera Rubin', field = 'astronomy', born = 1928, nobel = False),Scientist(name = 'Sally Ride', field = 'physics', born = 1951, nobel = False)
)pprint(__scientists)def transform(item):return {'name':item.name,'age':2020 - item.born}res = tuple(map(transform,__scientists
))pprint(res)

如果之前的内容认真看了的话,这代码应该不难理解,创建了一个__scientists不可变数据,并利用map()生成一个新的数据,数据内容只包含姓名和年龄。
这部分代码的运行结果:

(Scientist(name='Ada Lovelace', field='math', born=1815, nobel=False),Scientist(name='Emmy Noether', field='math', born=1882, nobel=False),Scientist(name='Marie Curie', field='math', born=1867, nobel=True),Scientist(name='Tu Youyou', field='physics', born=1930, nobel=True),Scientist(name='Ada Yonath', field='chemistry', born=1939, nobel=True),Scientist(name='Vera Rubin', field='astronomy', born=1928, nobel=False),Scientist(name='Sally Ride', field='physics', born=1951, nobel=False))
({'age': 205, 'name': 'Ada Lovelace'},{'age': 138, 'name': 'Emmy Noether'},{'age': 153, 'name': 'Marie Curie'},{'age': 90, 'name': 'Tu Youyou'},{'age': 81, 'name': 'Ada Yonath'},{'age': 92, 'name': 'Vera Rubin'},{'age': 69, 'name': 'Sally Ride'})

你可以尝试自己运行一下这段代码,你会发现结果很快被打印出来了。但是这种情况很理想话,实际的问题中远比这麻烦,比如:会遇到处理大量数据,或者需要连续获取网页端数据等情况。这时,上述代码的性能或处理时间会降低。还是以上边代码模拟这种情况。
修改transform()函数并使用time计时如下:

import collections
import time
from pprint import pprintScientist = collections.namedtuple('Scientist', ['name','field','born','nobel'
])__scientists = (Scientist(name = 'Ada Lovelace', field = 'math', born = 1815, nobel = False),Scientist(name = 'Emmy Noether', field = 'math', born = 1882, nobel = False),Scientist(name = 'Marie Curie', field = 'math', born = 1867, nobel = True),Scientist(name = 'Tu Youyou', field = 'physics', born = 1930, nobel = True),Scientist(name = 'Ada Yonath', field = 'chemistry', born = 1939, nobel = True),Scientist(name = 'Vera Rubin', field = 'astronomy', born = 1928, nobel = False),Scientist(name = 'Sally Ride', field = 'physics', born = 1951, nobel = False)
)
# pprint(__scientists)
def transform(item):print(f'processing record {item.name}')time.sleep(1)res = {'name':item.name,'age':2020 - item.born}print(f'Done processing record {item.name}')return resstart = time.time()
res = tuple(map(transform,__scientists
))
end = time.time()print(f'Time to complete: {end - start}')
# pprint(res)

再次运行,结果如下:

processing record Ada Lovelace
Done processing record Ada Lovelace
processing record Emmy Noether
Done processing record Emmy Noether
processing record Marie Curie
Done processing record Marie Curie
processing record Tu Youyou
Done processing record Tu Youyou
processing record Ada Yonath
Done processing record Ada Yonath
processing record Vera Rubin
Done processing record Vera Rubin
processing record Sally Ride
Done processing record Sally Ride
Time to complete: 7.009661436080933

从输出结果看,数据是一个一个进行处理,总时长7秒左右。可以想象,如果更多的数据需要处理,那么时间将会更长。这时,函数式编程的三个函数的优势就体现出来了。通过它们,可以使用并行计算,从而更好的利用cpu。

import collections
import time
from pprint import pprintimport multiprocessingScientist = collections.namedtuple('Scientist', ['name','field','born','nobel'
])__scientists = (Scientist(name = 'Ada Lovelace', field = 'math', born = 1815, nobel = False),Scientist(name = 'Emmy Noether', field = 'math', born = 1882, nobel = False),Scientist(name = 'Marie Curie', field = 'math', born = 1867, nobel = True),Scientist(name = 'Tu Youyou', field = 'physics', born = 1930, nobel = True),Scientist(name = 'Ada Yonath', field = 'chemistry', born = 1939, nobel = True),Scientist(name = 'Vera Rubin', field = 'astronomy', born = 1928, nobel = False),Scientist(name = 'Sally Ride', field = 'physics', born = 1951, nobel = False)
)# pprint(__scientists)def transform(item):print(f'processing record {item.name}')time.sleep(1)res = {'name':item.name,'age':2020 - item.born}print(f'Done processing record {item.name}')return resif __name__ == '__main__':start = time.time()pool = multiprocessing.Pool()res = pool.map(transform,__scientists)end = time.time()print(f'Time to complete: {end - start}')

注意 :

if __name__ == '__main__':

运行结果:

processing record Ada Lovelace
processing record Emmy Noether
processing record Marie Curie
processing record Tu Youyou
processing record Ada Yonath
processing record Vera Rubin
processing record Sally Ride
Done processing record Ada Lovelace
Done processing record Emmy Noether
Done processing record Marie Curie
Done processing record Tu Youyou
Done processing record Ada Yonath
Done processing record Vera Rubin
Done processing record Sally Ride
Time to complete: 1.498938798904419

通过结果可以明显看出,并行计算的效率之高,此时数据的处理不是一个接一个,而是多个数据并行处理。

进一步探索:

将transform()函数改成如下,让它运行过程中显示进程id

import osdef transform(item):print(f'Process {os.getpid()} working record {item.name}')time.sleep(1)res = {'name':item.name,'age':2020 - item.born}print(f'Process {os.getpid()} done processing record {item.name}')return res

输出结果:

Process 15812 working record Ada Lovelace
Process 22092 working record Emmy Noether
Process 12756 working record Marie Curie
Process 20052 working record Tu Youyou
Process 7324 working record Ada Yonath
Process 30060 working record Vera Rubin
Process 23028 working record Sally Ride
Process 15812 done processing record Ada Lovelace
Process 22092 done processing record Emmy Noether
Process 12756 done processing record Marie Curie
Process 20052 done processing record Tu Youyou
Process 7324 done processing record Ada Yonath
Process 30060 done processing record Vera Rubin
Process 23028 done processing record Sally Ride
Time to complete: 1.446131706237793

可以通过对Pool()函数传入参数明确要几个进程处理数据:

pool = multiprocessing.Pool(processes=2)

输出结果:

Process 14684 working record Ada Lovelace
Process 31848 working record Emmy Noether
Process 14684 done processing record Ada Lovelace
Process 14684 working record Marie Curie
Process 31848 done processing record Emmy Noether
Process 31848 working record Tu Youyou
Process 14684 done processing record Marie Curie
Process 14684 working record Ada Yonath
Process 31848 done processing record Tu Youyou
Process 31848 working record Vera Rubin
Process 14684 done processing record Ada Yonath
Process 14684 working record Sally Ride
Process 31848 done processing record Vera Rubin
Process 14684 done processing record Sally Ride
Time to complete: 4.218588829040527

结果显示,使用了两个进程处理数据。如果不限定,则是有几个空闲就用几个。

concurrent.futures

对于python3.x,concurrent.futures可以更好的实现并行计算。代码如下:

import concurrent.futuresif __name__ == '__main__':start = time.time()with concurrent.futures.ProcessPoolExecutor() as executor:res = executor.map(transform,__scientists)# pool = multiprocessing.Pool(processes=2)# res = pool.map(transform,__scientists)end = time.time()print(f'Time to complete: {end - start}')pprint(tuple(res))

上述代码同样实现了并行计算,与之前multiprocessing不同的是,concurrent.futures用到了with和上下文管理器。关于with和上下文管理器的用法这里不重点讨论。将原始代码的并行计算部分改为上述代码运行结果:

Process 13996 working record Ada Lovelace
Process 18116 working record Emmy Noether
Process 32556 working record Marie Curie
Process 10320 working record Tu Youyou
Process 31808 working record Ada Yonath
Process 9552 working record Vera Rubin
Process 30600 working record Sally Ride
Process 13996 done processing record Ada Lovelace
Process 18116 done processing record Emmy Noether
Process 32556 done processing record Marie Curie
Process 10320 done processing record Tu Youyou
Process 31808 done processing record Ada Yonath
Process 9552 done processing record Vera Rubin
Process 30600 done processing record Sally Ride
Time to complete: 1.6321463584899902
({'age': 205, 'name': 'Ada Lovelace'},{'age': 138, 'name': 'Emmy Noether'},{'age': 153, 'name': 'Marie Curie'},{'age': 90, 'name': 'Tu Youyou'},{'age': 81, 'name': 'Ada Yonath'},{'age': 92, 'name': 'Vera Rubin'},{'age': 69, 'name': 'Sally Ride'})

多说一点点:

concurrent.futures也可以执行多线程的运算,但是由于python的全局解释器锁定(GIL)的原因,事实上,只有一个线程可以执行python代码。因此,即使设置了多线程,但实际也只能有一个线程执行python代码。这个问题在这里不做深入讨论,以后会单独写这个问题。这里可以简单理解为,一般情况下,在python中,应该使用基于进程的并行计算。当然如果遇到像 i/o操作,当i/O操作造成cpu闲置的情况时,会释放GIL。

总结:

本文主要介绍了什么是函数式编程,函数式编程的特点以及三个基本函数,并且基于函数式编程的基本函数,对并行计算有了一点认识。函数式编程作为一种编程方式,给我们在编码过程中提供了新的思路,在并行计算上函数式编程有着很大的优势。

最后的最后:

您的点赞留言,是对我最大的鼓励
欢迎交流讨论,谢谢~

Python笔记---一脚踏进函数式编程相关推荐

  1. python函数式编程读取数据时出现错误_写 Python 代码不可不知的函数式编程技术...

    原标题:写 Python 代码不可不知的函数式编程技术 选自 Medium 作者:Raivat Shah 参与:魔王.Jamin 本文对 Python 中的函数式编程技术进行了简单的入门介绍. 近来, ...

  2. python 函数式编程 库_使用Python的toolz库开始函数式编程的方法

    在这个由两部分组成的系列文章的第二部分中,我们将继续探索如何将函数式编程方法中的好想法引入到 Python中,以实现两全其美. 在上一篇文章中,我们介绍了不可变数据结构 . 这些数据结构使得我们可以编 ...

  3. Python基础(六)—函数式编程(内部函数、闭包、lambda、filter/map/reduce/sorce、偏函数)

    内部函数 Python中函数的作用域由def关键字界定,函数内的代码访问变量的方式是从其所在层级由内向外,若往外直至全局作用域都查找不到的话代码会抛异常. 主要看以下代码的差别~~ "&qu ...

  4. python 的高级特性:函数式编程,lambda表达式,装饰器

    一.Python语言的高级特性 函数式编程 基于Lambda演算的一种编程方式 程序中只有函数 函数可以作为参数,同样可以作为返回值 纯函数式编程语言:LISP,Haaskell Python函数式编 ...

  5. python函数编程实战_(转)函数式编程实战教程(Python版)

    许多函数式文章讲述的是组合,流水线和高阶函数这样的抽象函数式技术.本文不同,它展示了人们每天编写的命令式,非函数式代码示例,以及将这些示例转换为函数式风格. 文章的第一部分将一些短小的数据转换循环重写 ...

  6. 写 Python 代码不可不知的函数式编程技术

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自|深度学习这件小事 本文对 Python 中的函数式编程技 ...

  7. 函数式编程语言python-写 Python 代码不可不知的函数式编程技术

    近来,越来越多人使用函数式编程(functional programming).因此,很多传统的命令式语言(如 Java 和 Python)开始支持函数式编程技术.本文对 Python 中的函数式编程 ...

  8. Python学习札记(二十) 函数式编程1 介绍 高阶函数介绍

    参考: 函数式编程 高阶函数 Note A.函数式编程(Functional Programming)介绍 1.函数是Python内建支持的一种封装,我们通过一层一层的函数调用把复杂任务分解成简单的任 ...

  9. python命令式编程的概念,【Python】十分钟学会函数式编程

    点击上方"小白学视觉",选择加"星标"或"置顶"重磅干货,第一时间送达 本文转自:深度学习这点小事 函数式编程到底是什么?本文将详解其概念, ...

最新文章

  1. Zabbix监控系统部署:配置详解
  2. linux命令查看cpu负载,怎么使用Linux命令查看CPU使用率
  3. echarts用法配置
  4. boost::process::throw_on_error相关的测试程序
  5. 【CSDN】设置图片大小
  6. 关于H5工程师那些日常必需工具
  7. 【Tomcat】Tomcat下设置项目为默认项目
  8. 2018 开源分布式中间件 DBLE 年报
  9. [渝粤教育] 中国地质大学 国际贸易理论 复习题
  10. 苹果公布 macOS Monterey 兼容机型列表
  11. LeetCode——remove-duplicates-from-sorted-list
  12. JDK1.8帮助文档chm格式中英文
  13. 宏基 Acer 4741G ubuntu10.10 GT 330M显卡驱动
  14. stm32+esp8266+app inventor简单小制作
  15. 中职一年级计算机学情分析,一年级学情分析.doc
  16. Default interface methods are only supported starting with Android N (--min-api 24):
  17. iWatch 页面导航
  18. 如何把PDF中的一页内容替换掉
  19. 微信小程序动态点赞php,微信小程序小组件基于Canvas实现直播点赞气泡效果
  20. pip更换源,换成国内镜像

热门文章

  1. make m mm mmm 的区别
  2. solr自动增量更新
  3. 动物专家识别系统(Java)
  4. PPT | ​“京享超清”技术在复杂网络下应用与视频体验保障
  5. html5登录页面自动记住密码,html5超简单的localStorage实现记住密码的功能实现
  6. led灯串怎么摆造型_LED发光铜线灯串
  7. 【已解决】(uni-app)IOS 端 H5 应用无法发起请求,请求状态码为0,错误描述为request:fail
  8. 视频剪辑app软件开发需要实现哪些功能
  9. 曾经甩我30条街的技术大佬同学,最近我竟然和他成为同事了!
  10. OpenHarmony与HarmonyOS实现中文交流(二)