首页 后端开发 Python教程 进一步理解Python中的函数编程

进一步理解Python中的函数编程

Jun 10, 2016 pm 03:15 PM
python

我们最好从最难的问题开始:“到底什么是函数编程 (FP)?”一个答案可能会说 FP 就是您在使用例如 Lisp、Scheme、Haskell、ML、OCAML、Clean、Mercury、Erlang(或其它一些)语言进行编程时所做的。这是一个稳妥的答案,但不能很确切地阐明问题。不幸的是,即使是函数程序员他们自己也很难对 FP 究竟是什么有个一致的认识。“盲人摸象”的故事用来形容这一情况似乎很合适。还可以放心地将 FP 与“命令编程”(使用例如 C、Pascal、C 、Java、Perl、Awk、TCL 以及其它大多数语言所执行的操作,至少是在很大程度上)进行对比。

从个人角度来说,我会将函数编程粗略地描绘为至少具有以下几个特征。称得上函数性的语言使这些事情变得简单,而使其它事情变得困难或不可能:

  •     函数是第一类(对象)。即,可以对“数据”进行的每样操作都可以使用函数本身做到(例如将一个函数传递给另一个函数)。
  •     将递归用作主要的控制结构。在某些语言中,不存在其它“循环”构造。
  •     重点集中在列表 LISt 处理(例如,名称 Lisp )。列表经常和子列表的递归一起使用以替代循环。
  •     “纯”函数语言能够避免副作用。这不包括在命令语言中最普遍的模式,即指定第一个,然后将另一个值指定给同一个变量来跟踪程序状态。
  •     FP 不鼓励或根本不允许出现 语句,取而代之是使用表达式求值(换句话说,即函数加上自变量)。在很纯粹的情况下,一个程序就是一个表达式(加上支持的定义)。
  •     FP 关心的是计算 什么而不是 如何计算。
  •     许多 FP 利用了“更高等级”函数(换句话说,就是函数对一些函数操作,而这些函数又对其它函数操作)。

函数编程的提倡者认为所有这些特征都导致更快速的开发更短以及错误更少的代码。而且,计算机科学、逻辑和数学领域的高级理论学家发现证明函数语言和程序的正式性能比命令语言和程序容易得多。
固有的 Python 函数能力

自从 Python 1.0 以来,Python 具有上面列出的大多数 FP 特征。但对于大多数 Python 特性,它们以一种非常混合的语言呈现。很大程度上是因为 Python 的 OOP 特性,您可以使用希望使用的部分而忽略其余部分(直到在稍后需要它为止)。使用 Python 2.0, 列表内涵添加了一些 非常棒的“句法上的粉饰”。虽然列表内涵没有添加什么新的能力,但它们使许多旧的能力看起来好了 许多。

Python 中 FP 的基本元素是函数 map() 、 reduce() 和 filter() ,以及运算符 lambda 。在 Python 1.x 中, apply() 函数对于将一个函数的列表返回值直接应用于另一个函数也很方便。Python 2.0 为这一目的提供了改进的语法。可能让人吃惊,但很少的这几个函数(以及基本运算符)几乎足以编写任何 Python程序;特别是,所有的流控制语句( if 、 elif 、 else 、 assert 、 try 、 except 、 finally 、 for 、 break 、 continue 、 while 、 def )可以只使用 FP 函数和运算符以函数风格处理。虽然实际上消除程序中的所有流控制命令可能只对加入“混乱的 Python”竞争(与看上去非常象 Lisp 的代码)有用,但是理解 FP 是如何使用函数和递归来表示流控制是值得的。


消除流控制语句

在我们执行消除联系时要考虑的第一件事是 Python “短路”了布尔表达式的求值这一事实。这样就提供了表达式版本的 if / elif / else 块(假设每块都调用一个函数,通常总有可能这样安排)。下面是具体方法:
清单 1. Python 中的“短路”条件调用

# Normal statement-based flow control 
  
if
   <cond1>: func1() 
  elif
   <cond2>: func2() 
  else
  :   func3() 
 
  # Equivalent "short circuit" expression 
(<cond1> 
  and
   func1()) 
  or
   (<cond2> 
  and
   func2()) 
  or
   (func3()) 
 
  # Example "short circuit" expression 
>>> x = 3 
>>> 
  defpr
  (s): 
  return
   s 
>>> (x==1 
  and
   pr(
  'one')) 
  or
   (x==2 
  and
   pr(
  'two')) 
  or
   (pr(
  'other')) 
  'other' 
>>> x = 2 
>>> (x==1 
  and
   pr(
  'one')) 
  or
   (x==2 
  and
   pr(
  'two')) 
  or
   (pr(
  'other')) 
  'two'

登录后复制

表达式版本的条件性调用似乎不过是个位置诀窍;不过,如果我们注意到 lambda 运算符必须返回表达式时,就更有趣了。因为 -- 如前所示 -- 表达式可以通过短路来包含条件块,所以 lambda 表达式在表达条件返回值中非常普通。在我们的示例上构建:
清单 2. Python 中 Lambda 短路

>>> pr = 
  lambda
   s:s 
>>> namenum = 
  lambda
   x: (x==1 
  and
   pr(
  "one")) \ 
....     
  or
   (x==2 
  and
   pr(
  "two")) \ 
....     
  or
   (pr(
  "other")) 
>>> namenum(1) 
  'one' 
>>> namenum(2) 
  'two' 
>>> namenum(3) 
  'other'

登录后复制


函数作为第一类对象

上面的示例已经显示出函数在 Python 中所处的第一类的地位,但以很微妙的方式。在使用 lambda 操作创建 函数对象 时,我们有一些完全常规的事物。正是因为这样,我们可以将对象与名称 "pr" 和 "namenum" 绑定,使用的方法和将数字 23 或字符串 "spam" 与这些名称绑定的方法完全相同。但正如我们可以使用数字 23 而无需将它与任何名称绑定一样(换句话说,象函数自变量一样),我们可以使用用 lambda 创建的函数对象而不用将它与任何名称绑定。一个函数只是我们在 Python 中对其执行某些操作的另一个值。

我们对第一类对象所执行的主要操作是将它们传递给 FP 内置函数 map() 、 reduce() 和 filter() 。这些函数中的每一个都接受函数对象作为其第一个自变量。

map() 对指定列表中每个对应的项执行传递的函数,并返回结果列表。
reduce() 对每个后续项执行传递的函数,返回的是最终结果的内部累加;例如 reduce(lambda n,m:n*m, range(1,10)) 意味着“10 的阶乘”(换句话说,用每一项乘上前一次相乘的乘积)。
filter() 使用传递的函数对列表中的每一项“求值”,然后返回经过甄别的,通过了传递函数测试的项的列表。

我们还经常将函数对象传递给自己的定制函数,但它们通常等同于上述内置函数的组合。

通过将这三种 FP 内置函数进行组合,可以执行惊人的一系列“流”操作(都不使用语句,而只使用表达式)。


Python 中的函数循环

替换循环与替换条件块一样简单。 for 可以直接转换成 map() 。对于我们的条件执行,我们需要将语句块简化成单一函数调用(我们正逐步接近通常的做法):
清单 3. Python 中的函数 'for' 循环

for
   e 
  in
   lst: func(e) 
  # statement-based loop
map(func,lst)  
  # map()-based loop

登录后复制

另外,对于连续程序流的函数方法有类似的技术。即,命令编程通常包含接近于“做这样,然后做那样,然后做其它事。”这样的语句。 map() 让我们正好做到这一点:
清单 4. Python 中的函数连续操作

# let's create an execution utility function
do_it = 
  lambda
   f: f()
  # let f1, f2, f3 (etc) be functions that perform actions
map(do_it, [f1,f2,f3]) 
  # map()-based action sequence

登录后复制

通常,我们的整个 main 程序可以是 map() 表达式和一系列完成程序所需要执行的函数。第一类函数的另一个方便的特性就是可以将它们放在一个列表中。

while 的转换稍微复杂了一些,但仍然可以直接进行:
清单 5. Python 中的函数 'while' 循环

# statement-based while loop
  
while
   <cond>:
 <pre-suite>
 
  if
   <break_condition>:
 
  break
 else
  :
 <suite>
  # FP-style recursive while loopp
  
defwhile_block
  ():
 <pre-suite>
 
  if
   <break_condition>:
 
  return
   1
 
  else
  :
 <suite>
 
  return
   0
while_FP = 
  lambda
  : (<cond> 
  and
   while_block()) 
  or
   while_FP()
while_FP()

登录后复制

while 的转换仍需要 while_block() 函数,它本身包含语句而不仅仅是表达式。但我们需要对该函数做进一步的消除(例如对模板中的 if/else 进行短路)。另外,因为循环主体(按设计)无法更改任何变量值,所以 很难用在一般的测试中,例如 while myvar==7 (那么,将在 while_block() 中修改全部内容)。添加更有用条件的一个方法是让 while_block() 返回一个更有趣的值,然后将这个返回值与终止条件进行比较。有必要看一下这些消除语句的具体示例:
清单 6. Python 中的函数 'echo' 循环

# imperative version of "echo()"
  
defecho_IMP
  ():
 
  while
   1:
 x = raw_input(
  "IMP -- ")
 
  if
   x == 
  'quit':
  
  break
 else
  print
   x
echo_IMP()
  # utility function for "identity with side-effect"
  
defmonadic_print
  (x):
 
  print
   x
 
  return
   x
  # FP version of "echo()"
echo_FP = 
  lambda
  : monadic_print(raw_input(
  "FP -- "))==
  'quit' 
  or
   echo_FP()
echo_FP()

登录后复制

我们所完成的是设法将涉及 I/O、循环和条件语句的小程序表示成一个带有递归的纯表达式(实际上,如果需要,可以表示成能传递到任何其它地方的函数对象)。我们 的确 仍然利用了实用程序函数 monadic_print() ,但这个函数是完全一般性的,可以在我们以后创建的每个函数程序表达式中重用(它是一次性成本)。请注意,任何包含 monadic_print(x) 的表达式所 求值 的结果都是相同的,就象它只包含 x 一样。FP(特别是 Haskell)对于“不执行任何操作,在进程中有副作用”的函数具有“单一体”意思。


消除副作用

在除去完美的、有意义的语句不用而代之以晦涩的、嵌套的表达式的工作后,一个很自然的问题是:“为什么?!”我对 FP 的所有描述都是使用 Python 做到的。但最重要的特性 -- 可能也是具体情况中最有用的特性 -- 是它消除了副作用(或者至少对一些特殊领域,例如单一体,有一些牵制作用)。绝大部分程序错误 -- 和促使程序员求助于调试来解决的问题 -- 之所以会发生,是因为在程序执行过程期间,变量包含了意外的值。函数程序只不过根本就不为变量分配值,从而避免了这一特殊问题。

让我们看一段相当普通的命令代码。它的目的是打印出乘积大于 25 的几对数字的列表。组成各对的数字本身是从另外两个列表中挑选出的。这种操作与程序员在他们程序段中实际执行的操作差不多。实现这一目的的命令方法如下:
清单 7. “打印大乘积”的命令 Python 代码

# Nested loop procedural style for finding big products
xs = (1,2,3,4)
ys = (10,15,3,22)
bigmuls = []
  # ...more stuff...
  
for
   x 
  in
   xs:
 
  for
   y 
  in
   ys:
 
  # ...more stuff...
  
   if
   x*y > 25:
  bigmuls.append((x,y))
  
  # ...more stuff...
# ...more stuff...
  
print
   bigmuls

登录后复制

这个项目太小,以至于没有什么可能出错。但我们的目的可能嵌在要同时实现许多其它目的的代码中。用 "more stuff" 注释的那些部分是副作用可能导致错误发生的地方。在这些地方中的任何一处,变量 xs 、 ys 、 bigmuls 、 x 、 y 有可能获得假设节略代码中的意外值。而且,在执行完这一段代码后,所有变量都可能具有稍后代码可能需要也可能不需要的一些值。很明显,可以使用函数/实例形式的封装和有关作用域的考虑来防止出现这种类型的错误。而且,您总是可以在执行完变量后 del 它们。但在实际中,这些指出类型的错误非常普遍。

目标的函数方法完全消除了这些副作用错误。以下是可能的一段代码:
清单 8. “打印大乘积”的函数 Python 代码

bigmuls = 
  lambda
   xs,ys: filter(
  lambda
   (x,y):x*y > 25, combine(xs,ys))
combine = 
  lambda
   xs,ys: map(None, xs*len(ys), dupelms(ys,len(xs)))
dupelms = 
  lambda
   lst,n: reduce(
  lambda
   s,t:s+t, map(
  lambda
   l,n=n: [l]*n, lst))
  print
   bigmuls((1,2,3,4),(10,15,3,22))

登录后复制

在示例中,我们将匿名 ( lambda ) 函数对象与名称进行绑定,但这不是一定必要的。我们可以只嵌套定义。这样做是出于可读性目的;但也是因为 combine() 是一种随处可得的很好实用程序函数(从两个输入列表中产生所有元素对的列表)。随后的 dupelms() 主要只是帮助 combine() 发挥作用的一种方法。即使这一函数示例比命令示例更冗长,但一旦考虑到实用程序函数可以重用,那么 bigmuls() 中的新代码本身可能比命令版本中的代码数量还要少一些。

这种函数示例真正的优势在于绝对不会有变量更改其中的任何值。稍后的代码中没有 可能的不曾预料到的副作用(较早的代码中也不会有)。很明显,它本身没有副作用并不能保证代码 正确,但即使这样,这也是个优点。不过请注意,Python(与许多函数语言不同) 不能 防止名称 bigmuls 、 combine 和 dupelms 的重新绑定。如果 combine() 在程序的稍后部分中开始有其它意义,则所有努力都前功尽弃。您可以逐步建立一个 Singleton 类来包含这种类型的不可变绑定(例如 s.bigmuls 等);但本专栏并不涉及这一内容。

特别值得注意的一个问题是我们的特定目的是对 Python 2 中的新特性进行定制。最好的(也是函数的)技术既不是上面提供的命令示例,也不是函数实例,而是:
清单 9. "bigmuls" 的列表内涵 Python 代码

print
   [(x,y) 
  for
   x 
  in
   (1,2,3,4) 
  for
   y 
  in
   (10,15,3,22) 
  if
   x*y > 25]

登录后复制


结束语

我已介绍了使用函数等价物替换每个 Python 流控制构造所使用的方法(在过程中消除了副作用)。对特定程序进行有效转换将带来一些额外的考虑,但我们已经知道内置函数是常规而完整的。在稍后的专栏中,我们将考虑一些更高级的函数编程技术;希望能够探索函数风格的更多利弊。

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

PHP和Python:解释了不同的范例 PHP和Python:解释了不同的范例 Apr 18, 2025 am 12:26 AM

PHP主要是过程式编程,但也支持面向对象编程(OOP);Python支持多种范式,包括OOP、函数式和过程式编程。PHP适合web开发,Python适用于多种应用,如数据分析和机器学习。

在PHP和Python之间进行选择:指南 在PHP和Python之间进行选择:指南 Apr 18, 2025 am 12:24 AM

PHP适合网页开发和快速原型开发,Python适用于数据科学和机器学习。1.PHP用于动态网页开发,语法简单,适合快速开发。2.Python语法简洁,适用于多领域,库生态系统强大。

Python vs. JavaScript:学习曲线和易用性 Python vs. JavaScript:学习曲线和易用性 Apr 16, 2025 am 12:12 AM

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

PHP和Python:深入了解他们的历史 PHP和Python:深入了解他们的历史 Apr 18, 2025 am 12:25 AM

PHP起源于1994年,由RasmusLerdorf开发,最初用于跟踪网站访问者,逐渐演变为服务器端脚本语言,广泛应用于网页开发。Python由GuidovanRossum于1980年代末开发,1991年首次发布,强调代码可读性和简洁性,适用于科学计算、数据分析等领域。

vs code 可以在 Windows 8 中运行吗 vs code 可以在 Windows 8 中运行吗 Apr 15, 2025 pm 07:24 PM

VS Code可以在Windows 8上运行,但体验可能不佳。首先确保系统已更新到最新补丁,然后下载与系统架构匹配的VS Code安装包,按照提示安装。安装后,注意某些扩展程序可能与Windows 8不兼容,需要寻找替代扩展或在虚拟机中使用更新的Windows系统。安装必要的扩展,检查是否正常工作。尽管VS Code在Windows 8上可行,但建议升级到更新的Windows系统以获得更好的开发体验和安全保障。

visual studio code 可以用于 python 吗 visual studio code 可以用于 python 吗 Apr 15, 2025 pm 08:18 PM

VS Code 可用于编写 Python,并提供许多功能,使其成为开发 Python 应用程序的理想工具。它允许用户:安装 Python 扩展,以获得代码补全、语法高亮和调试等功能。使用调试器逐步跟踪代码,查找和修复错误。集成 Git,进行版本控制。使用代码格式化工具,保持代码一致性。使用 Linting 工具,提前发现潜在问题。

vscode怎么在终端运行程序 vscode怎么在终端运行程序 Apr 15, 2025 pm 06:42 PM

在 VS Code 中,可以通过以下步骤在终端运行程序:准备代码和打开集成终端确保代码目录与终端工作目录一致根据编程语言选择运行命令(如 Python 的 python your_file_name.py)检查是否成功运行并解决错误利用调试器提升调试效率

vscode 扩展是否是恶意的 vscode 扩展是否是恶意的 Apr 15, 2025 pm 07:57 PM

VS Code 扩展存在恶意风险,例如隐藏恶意代码、利用漏洞、伪装成合法扩展。识别恶意扩展的方法包括:检查发布者、阅读评论、检查代码、谨慎安装。安全措施还包括:安全意识、良好习惯、定期更新和杀毒软件。

See all articles