2. 从函数开始

2.1. 定义一个函数

如下定义了一个求和函数:

def add(x, y):

return x + y

关于参数和返回值的语法细节可以参考其他文档,这里就略过了。

使用lambda可以定义简单的单行匿名函数。lambda的语法是:

lambda args: expression

参数(args)的语法与普通函数一样,同时表达式(expression)的值就是匿名函数调用的返回值;而lambda表达式返回这个匿名函数。如果我们给匿名函数取个名字,就像这样:

lambda_add = lambda x, y: x + y

这与使用def定义的求和函数完全一样,可以使用lambda_add作为函数名进行调用。然而,提供lambda的目的是为了编写偶尔为之的、简单的、可预见不会被修改的匿名函数。这种风格虽然看起来很酷,但并不是一个好主意,特别是当某一天需要对它进行扩充,再也无法用一个表达式写完时。如果一开始就需要给函数命名,应该始终使用def关键字。

2.2. 使用函数赋值

事实上你已经见过了,上一节中我们将lambda表达式赋值给了add。同样,使用def定义的函数也可以赋值,相当于为函数取了一个别名,并且可以使用这个别名调用函数:

add_a_number_to_another_one_by_using_plus_operator = add

print add_a_number_to_another_one_by_using_plus_operator(1, 2)

既然函数可以被变量引用,那么将函数作为参数和返回值就是很寻常的做法了。

2.3. 闭包

闭包是一类特殊的函数。如果一个函数定义在另一个函数的作用域中,并且函数中引用了外部函数的局部变量,那么这个函数就是一个闭包。下面的代码定义了一个闭包:

def f():

n = 1

def inner():

print n

inner()

n = "x"

inner()

函数inner定义在f的作用域中,并且在inner中使用了f中的局部变量n,这就构成了一个闭包。闭包绑定了外部的变量,所以调用函数f的结果是打印1和"x"。这类似于普通的模块函数和模块中定义的全局变量的关系:修改外部变量能影响内部作用域中的值,而在内部作用域中定义同名变量则将遮蔽(隐藏)外部变量。

如果需要在函数中修改全局变量,可以使用关键字global修饰变量名。Python 2.x中没有关键字为在闭包中修改外部变量提供支持,在3.x中,关键字nonlocal可以做到这一点:

#Python 3.x supports `nonlocal"

def f():

n = 1

def inner():

nonlocal n

n = "x"

print(n)

inner()

print(n)

调用这个函数的结果是打印1和"x",如果你有一个Python 3.x的解释器,可以试着运行一下。

由于使用了函数体外定义的变量,看起来闭包似乎违反了函数式风格的规则即不依赖外部状态。但是由于闭包绑定的是外部函数的局部变量,而一旦离开外部函数作用域,这些局部变量将无法再从外部访问;另外闭包还有一个重要的特性,每次执行至闭包定义处时都会构造一个新的闭包,这个特性使得旧的闭包绑定的变量不会随第二次调用外部函数而更改。所以闭包实际上不会被外部状态影响,完全符合函数式风格的要求。(这里有一个特例,Python 3.x中,如果同一个作用域中定义了两个闭包,由于可以修改外部变量,他们可以相互影响。)

虽然闭包只有在作为参数和返回值时才能发挥它的真正威力,但闭包的支持仍然大大提升了生产率。

2.4. 作为参数

如果你对OOP的模板方法模式很熟悉,相信你能很快速地学会将函数当作参数传递。两者大体是一致的,只是在这里,我们传递的是函数本身而不再是实现了某个接口的对象。

我们先来给前面定义的求和函数add热热身:

print add("三角形的树", "北极")

与加法运算符不同,你一定很惊讶于答案是"三角函数"。这是一个内置的彩蛋...bazinga!

言归正传。我们的客户有一个从0到4的列表:

lst = range(5) #[0, 1, 2, 3, 4]

虽然我们在上一小节里给了他一个加法器,但现在他仍然在为如何计算这个列表所有元素的和而苦恼。当然,对我们而言这个任务轻松极了:

amount = 0

for num in lst:

amount = add(amount, num)

这是一段典型的指令式风格的代码,一点问题都没有,肯定可以得到正确的结果。现在,让我们试着用函数式的风格重构一下。

首先可以预见的是求和这个动作是非常常见的,如果我们把这个动作抽象成一个单独的函数,以后需要对另一个列表求和时,就不必再写一遍这个套路了:

def sum_(lst):

amount = 0

for num in lst:

amount = add(amount, num)

return amount

print sum_(lst)

还能继续。sum_函数定义了这样一种流程:

1. 使用初始值与列表的第一个元素相加;

2. 使用上一次相加的结果与列表的下一个元素相加;

3. 重复第二步,直到列表中没有更多元素;

4. 将最后一次相加的结果返回。

如果现在需要求乘积,我们可以写出类似的流程――只需要把相加换成相乘就可以了:

def multiply(lst):

product = 1

for num in lst:

product = product * num

return product

除了初始值换成了1以及函数add换成了乘法运算符,其他的代码全部都是冗余的。我们为什么不把这个流程抽象出来,而将加法、乘法或者其他的函数作为参数传入呢?

def reduce_(function, lst, initial):

result = initial

for num in lst:

result = function(result, num)

return result

print reduce_(add, lst, 0)

现在,想要算出乘积,可以这样做:

print reduce_(lambda x, y: x * y, lst, 1)

那么,如果想要利用reduce_找出列表中的最大值,应该怎么做呢?请自行思考:)

虽然有模板方法这样的设计模式,但那样的复杂度往往使人们更情愿到处编写循环。将函数作为参数完全避开了模板方法的复杂度。

Python有一个内建函数reduce,完整实现并扩展了reduce_的功能。本文稍后的部分包含了有用的内建函数的介绍。请注意我们的目的是没有循环,使用函数替代循环是函数式风格区别于指令式风格的最显而易见的特征。

*像Python这样构建于类C语言之上的函数式语言,由于语言本身提供了编写循环代码的能力,内置函数虽然提供函数式编程的接口,但一般在内部还是使用循环实现的。同样的,如果发现内建函数无法满足你的循环需求,不妨也封装它,并提供一个接口。

2.5. 作为返回值

将函数返回通常需要与闭包一起使用(即返回一个闭包)才能发挥威力。我们先看一个函数的定义:

def map_(function, lst):

result = []

for item in lst:

result.append(function(item))

return result

函数map_封装了最常见的一种迭代:对列表中的每个元素调用一个函数。map_需要一个函数参数,并将每次调用的结果保存在一个列表中返回。这是指令式的做法,当你知道了列表解析(list comprehension)后,会有更好的实现。

这里我们先略过map_的蹩脚实现而只关注它的功能。对于上一节中的lst,你可能发现最后求乘积结果始终是0,因为lst中包含了0。为了让结果看起来足够大,我们来使用map_为lst中的每个元素加1:

lst = map_(lambda x: add(1, x), lst)

print reduce_(lambda x, y: x * y, lst, 1)

答案是120,这还远远不够大。再来:

lst = map_(lambda x: add(10, x), lst)

print reduce_(lambda x, y: x * y, lst, 1)

�澹�事实上我真的没有想到答案会是360360,我发誓没有收周鸿�t任何好处。

现在回头看看我们写的两个lambda表达式:相似度超过90%,绝对可以使用抄袭来形容。而问题不在于抄袭,在于多写了很多字符有木有?如果有一个函数,根据你指定的左操作数,能生成一个加法函数,用起来就像这样:

lst = map_(add_to(10), lst) #add_to(10)返回一个函数,这个函数接受一个参数并加上10后返回

写起来应该会舒服不少。下面是函数add_to的实现:

def add_to(n):

return lambda x: add(n, x)

通过为已经存在的某个函数指定数个参数,生成一个新的函数,这个函数只需要传入剩余未指定的参数就能实现原函数的全部功能,这被称为偏函数。Python内置的functools模块提供了一个函数partial,可以为任意函数生成偏函数:

functools.partial(func[, *args][, **keywords])

你需要指定要生成偏函数的函数、并且指定数个参数或者命名参数,然后partial将返回这个偏函数;不过严格的说partial返回的不是函数,而是一个像函数一样可直接调用的对象,当然,这不会影响它的功能。

另外一个特殊的例子是装饰器。装饰器用于增强甚至干脆改变原函数的功能,我曾写过一篇文档介绍装饰器,地址在这里:https://www.jb51.net/article/59867.htm。

*题外话,单就例子中的这个功能而言,在一些其他的函数式语言中(例如Scala)可以使用名为柯里化(Currying)的技术实现得更优雅。柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。如下的伪代码所示:

#不是真实的代码

def add(x)(y): #柯里化

return x + y

lst = map_(add(10), lst)

通过将add函数柯里化,使得add接受第一个参数x,并返回一个接受第二个参数y的函数,调用该函数与前文中的add_to完全相同(返回x + y),且不再需要定义add_to。看上去是不是更加清爽呢?遗憾的是Python并不支持柯里化。

2.6. 部分内建函数介绍

reduce(function, iterable[, initializer])

这个函数的主要功能与我们定义的reduce_相同。需要补充两点:

它的第二个参数可以是任何可迭代的对象(实现了__iter__()方法的对象);

如果不指定第三个参数,则第一次调用function将使用iterable的前两个元素作为参数。

由reduce和一些常见的function组合成了下面列出来的内置函数:

all(iterable) == reduce(lambda x, y: bool(x and y), iterable)

any(iterable) == reduce(lambda x, y: bool(x or y), iterable)

max(iterable[, args...][, key]) == reduce(lambda x, y: x if key(x) > key(y) else y, iterable_and_args)

min(iterable[, args...][, key]) == reduce(lambda x, y: x if key(x) < key(y) else y, iterable_and_args)

sum(iterable[, start]) == reduce(lambda x, y: x + y, iterable, start)

map(function, iterable, ...)

这个函数的主要功能与我们定义的map_相同。需要补充一点:

map还可以接受多个iterable作为参数,在第n次调用function时,将使用iterable1[n], iterable2[n], ...作为参数。

filter(function, iterable)

这个函数的功能是过滤出iterable中所有以元素自身作为参数调用function时返回True或bool(返回值)为True的元素并以列表返回,与系列第一篇中的my_filter函数相同。

zip(iterable1, iterable2, ...)

这个函数返回一个列表,每个元素都是一个元组,包含(iterable1[n], iterable2[n], ...)。

例如:zip([1, 2], [3, 4]) --> [(1, 3), (2, 4)]

如果参数的长度不一致,将在最短的序列结束时结束;如果不提供参数,将返回空列表。

除此之外,你还可以使用本文2.5节中提到的functools.partial()为这些内置函数创建常用的偏函数。

另外,pypi上有一个名为functional的模块,除了这些内建函数外,还额外提供了更多的有意思的函数。但由于使用的场合并不多,并且需要额外安装,在本文中就不介绍了。但我仍然推荐大家下载这个模块的纯Python实现的源代码看看,开阔思维嘛。里面的函数都非常短,源文件总共只有300行不到,地址在这里:http://pypi.python.org/pypi/functional

此篇结束:)

python函数式编程 pdf-Python函数式编程指南(二):从函数开始相关推荐

  1. python html 制作pdf,python将html转成PDF的实现代码(包含中文) -电脑资料

    前提: 安 html2pdf https://pypi.python.org/pypi/xhtml2pdf/ 下载字体:微软雅黑:给个地址:http://www.jb51.net/fonts/8481 ...

  2. python数据生成pdf,Python生成pdf文件的方法

    摘要:这篇Python开发技术栏目下的"Python生成pdf文件的方法",介绍的技术点是"python生成pdf文件.python生成pdf.生成pdf文件.Pytho ...

  3. python数学编程 pdf,Python数学编程

    商品参数 Python数学编程 定价 59.00 出版社 人民邮电出版社 版次 1 出版时间 2020年01月 开本 16开 作者 阿米特·萨哈 装帧 平装-胶订 页数 字数 ISBN编码 97871 ...

  4. python unicode 编程 pdf,Python | Python学习之unicode和utf8

    中文乱码.unicode和utf8 http://openskill.cn/article/448 https://www.liaoxuefeng.com/wiki/001374738125095c9 ...

  5. 简明python教程 --C++程序员的视角(二):函数及作用域

    函数通过def关键字定义.def关键字后跟一个函数的标识符名称,然后跟一对圆括号.圆括号之中可以包括一些变量名,该行以冒号结尾.接下来是一块语句,它们是函数体. 函数的基本概念 1 位置:从左往右进行 ...

  6. Python自动化运维开发----基础(十二)函数

    1.函数的定义 函数是对代码块起了一个名字,在需要执行代码块的时候,直接调用函数 >>> def test(): ... print(test) ... >>> t ...

  7. 第4章 Python 数字图像处理(DIP) - 频率域滤波5 - 二变量函数的傅里叶变换、图像中的混叠、二维离散傅里叶变换及其反变换

    目录 二变量函数的傅里叶变换 二维冲激及其取样性质 二维连续傅里叶变换对 二维取样和二维取样定理 图像中的混叠 二维离散傅里叶变换及其反变换 二变量函数的傅里叶变换 二维冲激及其取样性质 两个连续变量 ...

  8. python web 开发从入门到精通 pdf,Python编程从入门到精通 PDF包下载

    Python开发学习合集 用Python做科学计算pdf 像计算机科学家一样思考python.pdf 利用python进行数据分析.pdf Python科学计算-张若愚.pdf Python标准库Do ...

  9. 计算机辅助制造杂志,计算机辅助制造编程.PDF

    计算机辅助制造编程.PDF 计算机辅助制造编程 数控加工中的自动编程等技术 主讲人:CAXA 产品经理 -- 谢小星 时 间: 年 月 日 11 19 2017 8 15 下午 时 分 目录  自动 ...

  10. python百度云资源-python学习资源--百度云

    资源已汇总,访问 资源汇总贴 检索~~不定期更新.整理资源~ 1 2019-3-3创建python资源列表 python进阶 Head First Python, 2英文版 4.pdf Python编 ...

最新文章

  1. SQL Server 索引结构及其使用(二)(转)
  2. 常用git命令思维图
  3. C++类中成员变量的初始化有两种方式
  4. mysql数据库如何创建冗余小的表_mysql – Hibernate创建冗余的多对多表
  5. N^N最左边和最右边的数(数学)
  6. python逆序数的程序_计算逆序数(归并法)程序问题 (Python)
  7. mysql移动文件后打不开_Windows端MySQL data目录迁移(貌似会启动不了)
  8. Linux网络管理实 验 指 导
  9. loj2537 「PKUWC2018」Minimax 【概率 + 线段树合并】
  10. echarts判断x轴是否展示的全_vue+echart前端可视化操作(装X必备)
  11. python玩王者荣耀皮肤碎片怎么获得_大神教你用Python爬取王者荣耀英雄皮肤,不充钱也能入手!...
  12. 微信发红包的测试点有哪些? 评论/点赞/分享/收藏/收索/上传/下载
  13. 【游戏】python小游戏制作教程
  14. 【寒江雪】Go实现工厂模式
  15. 移动端/嵌入式-CV模型-2018:MobelNets-v2【Inverted Residuals(中间胖两头瘦)、Linear Bottlenecks(每个倒残差的最后一个卷积层使用线性激活函数)】
  16. Hinton论文系列-Reducing the dimensionality of data with neural networks
  17. [GBase 8s 教程]GBase 8s 事务(TRANSACTION)
  18. chevkv基于成对ANI的快速基因组聚类
  19. 关于vue告警:Closing curly brace does not appear on the same line as the subsequent block
  20. 图的一些基本知识:关联矩阵、拉普拉斯矩阵

热门文章

  1. 第三,四章 数据库和数据表的(增删改查,重命名等)基本操作
  2. robotframework 测试工具添加PDF文件内容匹配插件
  3. 获取设备和 App 信息
  4. 获取页面元素的滚动位置,兼容各浏览器
  5. 如何让你的数据库定时自动备份(2000SQL)
  6. SQL Server2005创建新数据库时不允许创建新数据库的问题
  7. css之max-width属性
  8. CodeForces - 1013B And 与运算暴力
  9. 缓存(之一) 使用Apache Httpd实现http缓存
  10. javascript学习笔记之入门