Python函数式编程

所需积分/C币:30 2013-09-27 13:19:18 392KB PDF

网上找到了Python函数式编程的优秀博文一篇,为了避免遗失及熟悉xelatex排版,所以全文整理,我只负责整理,版权归作者所有。
abstract 网上找到了 Python函数式编程的优秀博文一篇’为了避免遗失及熟悉 xelatex排版’所以全文整理,我只负责整理’版权归作者所有 Chapter 1 概述 这大概算是 Python最难啃的一块骨头吧。在我 Python生涯的这一年 里,我遇到了一些 Pythoner,他们毫无例外地完全不会使用函数式编程 (有些人喜欢称为 Pythonic),比如,从来不会传递函数,不知道 lambda 是什么意思’知道列表展开但从来不知道用在哪里,对 Python不提供经 典for循环感到无所适从·言谈之中表现出对函数式风格的一种抗拒甚至 厌恶。 我尝试剖析这个冋题’最终总结了这么两个原因:1、不想改变,认为 现有的知识可以完成任务;2、对小众语言的歧视, Python目前在国内市 场份额仍然很小很小,熟悉 Python凤格用处不大。 然而我认为学习使用一种截然不同的风格可以颠覆整个编程的思想σ 我会慢慢总结一个系列共4篇文字’篇幅都不大’轻松就能看完’希望对 喜欢 Python的人们有所帮助,因为我个人硝实从中受益匪浅 还是那句老话·尊重作者的劳动,转载请注明原作者和原地址:)原文 fttf:http://www.cnblogs.com/huxi/archive/2011/07/15/2107536.htm 1.1什么是函数式编程 函数式编程使用一系列的函数解决冋题σ函数仅接受输入并产生输出 不包含任何能影响产生输出的内部状态。任何情况下,使用相同的参数调 用函数始终能产生同样的结果。 在一个函数式的裎序中’输入的数据“流过”一系列的函数,每一个 函数根据它的输入产生输出。函数式风格避免编写有“边界效应”(side effects的函数:修改内部状态’或者是其他无法反应在输岀上的变化。完 全没有边界效应的函数被称为“纯函数式的”( purely functional)避免边 界效应意味着不使用在程序运行时可变的数据结构’输岀只依赖于输入 可以认为函数式编程刚妤站在了面向对象编程的对立面。对象通常包含 內部状态(字段)’和许多能修改这些状态的函数’程序则由不断修改状态 构成;函数式编程则极力避免状态改动’并通过在函数间传递数据流进行 工作。但这并不是说无法同时使用函数式编程和面向对象编程’事实上 复杂的系统一般会采用面向对象技术建模’但混合使用函数式风格还能让 你额外享受函数式风格的优点。 1.2为什么使用函数式编程? 舀数式的风格通常被认为有如下优点: 半逻辑可证 这是一个学术上的优点∶没有边界效应使得更容易从逻辑上证明程序 是正确的(而不是通过测试)。 模块化 函数式编程推崇简单原’一个函数只做一件事情,将大的功能拆分 成尽可能小的模块。小的函数更易于闼读和检查错误。 *组件化 小的函数更容易加以组合形成新的功能 *易于调试 细饣的丶定义清晰的函数使得调武更加简单。当程序不正常运行时, 每一个函数都是检查数据是否正确的接口’能更快速地排除没有问题 的代码,定位到出现问题的地方 易于测试 不依赖于系统状态的函数无须在测试前枃造测试桩’使得编写单元测 试更加容易 来更高的生产率 函数式编程产生的代码比其他技术更少(往往是其他技术的一半左 右)’并且更容易闶读和维护 1.3如何辨认函数式风格? 支持函数式编程的语言通常具有如下特征’大量使用这些特征的代码即 可被认为是函数式的 来函数是一等公民 函数能作为参数传递’或者是作为返回值返回。这个特性使得模板方 法模式非常易于编写·这也促使了这个模式被更频繁地使用。以 个简单的集合排序为例,假设lst是一个数集’并拥有一个排序方法 sort需要将如何硝定顺序作为参数。如果函数不能作为参数,那么lst 的sot方法只能接受普通对象作为参数。这样一来我们需要首先定 义一个接口·然后定义一个实现该接口的类,最后将该类的一个实例 传给sort方法;由sort调用这个实例的 compare方法,就像这样 伪代码 interface Comparator i compare(ol, o2) Ist = list(range(5)) Ist sort( Comparator()i compare(ol, 02) rcturn o2-01#逆序 可见,我们定义了一个新的接口、新的类型(这里是一个匿名类), 并new了一个新的对象只为了调用一个方法。如果这个方法可以直 接作为参数传递会怎样呢?看起来应该像这样 def compare(o1, 02 return o2-o1#逆序 Ist= list(range(5)) Ist sort(compare) 请注意·前一段代码已经使用了匿名类技巧从而省下了不少代码·但 仍然不如直接传递函数简单丶自然 匿名函数( Clambda) ambda提供了快速编写简单函数的能力。对于偶尔为之的行为, lambda让你不再需要在编码时跳转到其他位置去编写函数。 lambda 表达式定义一个匿名的函数如果这个函数仅在编码的位置使用到, 你可以现场定义丶、直接使用 Ist sort(lambda ol, 02: ol compareTo(o2)) 相信从这个小小的例子你也能感受到强大的生产效率:) *封装控制结构的内置模板函数 为了避开边界效应,函数式风格尽量避免使用变量’而仅仅为了控制 流程而定义的循环变量和流程中产生的临时变量无疑是最需要避免 的。假如我们需要对刚才的数集进行过滤得到所有的正数,使用指令 式风格的代码应该像是这样: Ist2= list() for i in range(len(1st)):#模拟经典循环for if lst[i>0 Ist2 append(lst[i) 这段代码把从创建新列表丶循环丶取出元素丶判断丶添加至新列表的 整个流程完整的展示了岀来,俨然把解释器当成了需要手把手指导的 傻瓜。然而,“过滤”这个动作是很常见的,为什么解释器不能掌握 过滤的流程,而我们只需要告诉它过滤规则呢?在 Python里’过滤 由一个名为 filter的内置函数实现。有了这个函数,解释器就学会了 如何“过滤”,而我们只需要把规则告诉它 Ist2= filter(lambda n: n>0, Ist) 这个函数带来的好处不仅仅是少写了几行代码这么简单。封装控制结 构后’代码中就只需要挡遽功能而不是做法’这样的代码更清晰,更 可读。因为避开了控制结构的千扰’第二段代码显然能让你更容易了 解它的意图。另外,因为避开了索引,使得代码中不太可能触发下标 越界这种异常’除非你手动制造一个。函数式编程语言通常封装了数 个类似“过滤”这样的常见动作作为模板函数。唯一的缺点是这些函 数需要少量的学习成本’但这绝对不能掩盖使用它们带来的好处 闭包( closure) 闭包是绑定了外部作用域的变量(但不是全局变量)的函数。大部分 情况下外部作用域指的是外部函数。闭包包含了自身函数体和所需外 部函数中的“变量名的引用”。引用变量名意味着绑定的是变量名 而不是变量实际指向的对象;如果给变量重新赋值’闭包中能访冋到 的将是新的值。闭包使函数更加灵活和强大即使程序运行至离开外 部函数·如果闭包仍然可见·则被绑定的变量仍然有效;每次运行至 外部函数,都会重新创建闭包,绑定的变量是不同的,不需要担心在 旧的闭包中绑定的变量会被新的值覆盖。回到刚才过滤数集的例子。 假设过滤条件中的0这个边界值不再是固定的,而是由用户控制。如 釆没有闭包’那么代码必须修改为: class grcatcr than helper def init(self, minval) self. minval= minval def is greater than(self, val) return val self. minval def my filter(Isl, minval) helper- greater than helper (minval) return filter(helper is_greater_ than, Ist) 请注意我们现在已经为过滤功能编写了一个函数 my filter。如你所 见’我们需要在别的地方(此例中是类 greater than helper)持有另 个操作数 minval。如果支持闭包,因为闭包可以接使用外部作用 域的变量’我们就不再需要 greater than helper了 def my filter (lst, minval) return filter (lambda n: n>minval, Ist) 可见,闭包在不影响可读性的同时也省下了不少代码量。函数式编程 语言都提供了对闭包的不同程度的支持。在 Python2x中,闭包无 法修改绑定变量的值·所有修改绑定变量的行为都被看成新建了一个 同名的局部变量并将绑定变量隐藏。 Python3x中新加入了一个关键 字 onlocal以支持修改绑定变量。但不管攴持程度如何’你始终可以 访问(读取)绑定变量 *内置的不可变数据结构 为了避开边界效应·不可变的数据结构是函薮式编程中不可或缺的部 分。不可变的数据结构保证数据的一致性’极大地降低了排查问题的 难度。例如, Python中的元组( tuple)就是不可变的,所有对元组的 操作都不能改变元组的内容·所有试图修改元组内容的操作都会产生 个异常。函数式编程语言一般会提供数据结构的两种版本(可变和 不可变),并推荐使用不可变的版本 递归 递归是另一种取代循环的方法。递归其实是函数式编程很常见的形 式’经常可以在一些算法中见到。但之所以放到最后,是因为实际上 我们一般很少用到递归。如果一个递归无法被编译器或解释器优化, 很容易就会产生栈溢出;另一方面复杂的递归往往让人感觉迷惑,不 如循环清晰’所以众多最佳实践均指岀使用循环而非递归。 Chapter 2 从函数开始 这是此系列的第二篇,试图说明在 Python中如何更好地使用函数并引 导诸位使用函数式的思维进行思考。掌握并应用这些内容,就已经是至少 形似的函数式风格的代码了,至于思维么,这个真冀自己 2.1定义一个函数 如下定义了一个求和函数 def add(x, y) return x t 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 opera tor = add print add a number to another one by using plus operator(1, P 既然函数可以被变量引用’那么将凶数作为参数和返回值就是很寻常的 做法了 2.3闭包 闭包是一类特殊的函数。如杲一个函数定义在另一个函数的作用域中, 并且函数中引用了外部函数的局部变量’那么这个函数就是一个闭包0下 面的代码定义了一个闭勺 def f() n def inner( print Inner n Inner 函数 inner定义在f的作用域中,并且在iner中使用了f中的局部变 量n’这就杓成了一个闭包。闭包绑定了外部的变量,所以调用函数f的 结果是打卬1和x。这类似于普通的模块函数和模块中定义的全局变量的 关系:修改外部变量能影响内部作用堿中的值’而在内部作用城中定义冋同 名变量则将遮蔽(隐藏)外部变量。 如果需要在函数中修改全局变量’可以使用关键字 global修饰变量名 Python2ⅹ中没有关键字为在闭包中修改外部变量提供支持,在3,x中, 关键字 nonlocal可以做到这一点: +Python 3. x supports " nonloca def f() n=1 der inner() nonlocal n n rint(n Inner

...展开详情
img
yougukepp

关注 私信 TA的资源

上传资源赚积分,得勋章
相关内容推荐