深入Python函数编程的一些特性

绑定

细心的读者可能记得我在 第 1 部分的函数技术中指出的限制。特别在 Python 中不能避免表示函数表达式的名称的重新绑定。在 FP 中,名称通常被理解为较长表达式的缩写,但这一说法暗示着“同一表达式总是求出相同的值”。如果标记的名称重新被绑定,这一暗示便不成立。例如,让我们定义一些在函数编程中要用到的快捷表达式,比如:
清单 1. 以下 Python FP 部分的重新绑定要造成故障

>>> car = 

    lambda

     lst: lst[0]
>>> cdr = 

    lambda

     lst: lst[1:]
>>> sum2 = 

    lambda

     lst: car(lst)+car(cdr(lst))
>>> sum2(range(10))
1
>>> car = 

    lambda

     lst: lst[2]
>>> sum2(range(10))
5

不幸的是,完全相同的表达式 sum2(range(10)) 在程序中的两处求得两个不同的值,即使该表达式自身并没有在其参数中使用任何可变变量。

幸运的是, functional 模块提供了称为 Bindings 的类(向 Keller 提议)来防止这样的重新绑定(至少在偶然情况下,Python 不会阻止一心想要解除绑定的程序员)。然而使用 Bindings 需要一些额外的语法,这样意外就不太容易发生。在 functional 模块的示例中,Keller 将 Bindings 实例命名为 let (我假定在 ML 家族语言的 let 关键词的后面)。 例如,我们会这样做:
清单 2. 具有安全重新绑定的 Python FP 部分

>>> 

    from

     functional 

    import

     *
>>> let = Bindings()
>>> let.car = 

    lambda

     lst: lst[0]
>>> let.car = 

    lambda

     lst: lst[2]
Traceback (innermost last):
 File "<stdin>", line 1, 

    in

     ?
 File "d:\tools\functional.py", line 976, 

    in

     __setattr__

    raise

     BindingError, "Binding '%s' cannot be modified." % name
functional.BindingError: Binding 'car' cannot be modified.
>>> car(range(10))
0

很明显,真正的程序必须做一些设置来捕获“绑定错误”,而且他们被抛出也避免了一类问题的出现。

与 Bindings 一起, functional 提供 namespace 函数从 Bindings 实例中获取命名空间(实际上是个字典)。如果希望在 Bindings 中定义的(不可变)命名空间中运算一个表达式,这非常容易实现。Python 的 eval() 函数允许在命名空间中进行运算。 让我们通过一个示例来弄清楚:
清单 3. 使用不可变命名空间的 Python FP 部分

>>> let = Bindings()   

    # "Real world" function names
>>> let.r10 = range(10)
>>> let.car = 

    lambda

     lst: lst[0]
>>> let.cdr = 

    lambda

     lst: lst[1:]
>>> eval('car(r10)+car(cdr(r10))', namespace(let))
>>> inv = Bindings()   

    # "Inverted list" function names
>>> inv.r10 = let.r10
>>> inv.car = 

    lambda

     lst: lst[-1]
>>> inv.cdr = 

    lambda

     lst: lst[:-1]
>>> eval('car(r10)+car(cdr(r10))', namespace(inv))
17

闭包

FP 中有个有趣的概念 -- 闭包。实际上,闭包对许多开发人员都非常有趣,即使在如 Perl 和 Ruby 这样的无函数语言中也都包括闭包这一功能。而且,Python 2.1 目前正想加入词汇范围限制功能,这一功能将提供闭包的大部分功能。

什么 是闭包呢? Steve Majewski 最近在 Python 新闻组提供了对这一概念的很好描述:

对象是附带过程的数据……闭包是附带数据的过程。

闭包就象是 FP 的 Jekyll 对于 OOP 的 Hyde (角色或者也可能对调)。闭包类似对象示例,是一种将一大批数据和功能封装在一起的一种方式。

让我们回到先前的地方了解对象和闭包解决什么问题,同时了解一下问题如果没有这两样是如何解决的。函数返回的结果往往是由其计算中使用的上下文决定的。最常见的 -- 也可能是最明显的 -- 指定上下文的方法是向函数传递某些参数,通知函数处理什么值。但有时候“背景”和“前景”参数有着本质的区别 -- 在这特定时刻函数正在处理的和函数为多段潜在调用而“配置”之间的区别。

当把重点放在前景的时候,有许多处理背景的方法。其中一种是简单“咬出子弹”的方法,在每次调用的时候传递函数需要的每一个参数。这种方法通常在调用链中,只要在某些地方有可能需要值,就会传递一些值(或带有多成员的结构)。以下是一个小示例:
清单 4. 显示 cargo 变量的 Python 部分

>>> 

    defa

    (n):
...   add7 = b(n)
...   

    return

     add7
...
>>> 

    defb

    (n):
...   i = 7
...   j = c(i,n)
...   

    return

     j
...
>>> 

    defc

    (i,n):
...   

    return

     i+n
...
>>> a(10)   

    # Pass cargo value for use downstream
17

在 cargo 示例的 b() 中, n 除了起到传递到 c() 的作用外并无其他作用。另一种方法将使用全局变量:
清单 5. 显示全局变量的 Python 部分

>>> N = 10
>>> 

    defaddN

    (i):
...   

    global

     N
...   

    return

     i+N
...
>>> addN(7)  

    # Add global N to argument
17
>>> N = 20
>>> addN(6)  

    # Add global N to argument
26

全局变量 N 在任何希望调用 addN() 的时候起作用,但没有必要明确地传递全局背景“上下文”。另一个更 Python 专用的技术是将一个变量在定义时“冻结”入一个使用默认参数的函数:
清单 6. 显示冻结变量的 Python 部分

>>> N = 10
>>> 

    defaddN

    (i, n=N):
...   

    return

     i+n
...
>>> addN(5)  

    # Add 10
15
>>> N = 20
>>> addN(6)  

    # Add 10 (current N doesn't matter)
16

冻结变量本质上就是闭包。某些数据被“隶属”于 addN() 函数。对于完整的闭包,当定义 addN() 的时候,所有的数据在调用的时候都将可用。然而,在这个示例(或者许多更健壮的示例)中,使用默认的参数就能简单的够用了。 addN() 从未使用的变量并不会对其计算造成影响。

接着让我们来看一个更接近真实问题的 OOP 方法。年份的时间是我想起了那些“会见”风格的收集各种数据的税收程序 -- 不必有特定的顺序 -- 最终使用全部数据来计算。让我们创建一个简单的版本:
清单 7. Python 风格的税收计算类/示例

class

     TaxCalc:

    deftaxdue

    (self):

    return

     (self.income-self.deduct)*self.rate
taxclass = TaxCalc()
taxclass.income = 50000
taxclass.rate = 0.30
taxclass.deduct = 10000

    print

     "Pythonic OOP taxes due =", taxclass.taxdue()

在 TaxCalc 类(或其实例)中,能收集一些数据 -- 可以以任意顺序 -- 一旦获得了所需的所有元素,就能调用这一对象的方法来完成这一大批数据的计算。所有一切都在实例中,而且,不同示例携带不同的数据。创建多示例和区别它们的数据的可能性不可能存在于"全局变量"或"冻结变量"方法中。"cargo" 方法能处理这个问题,但对于扩展的示例来说,我们看到它可能是开始传递各种值的必要条件了。既然我们已讲到这,注意传递消息的 OPP 风格是如何处理的也非常有趣(Smalltalk 或 Self 与此类似,一些我使用的 OOP xBase 变量也是如此):
清单 8. Smalltalk 风格 (Python) 的税收计算

class

     TaxCalc:

    deftaxdue

    (self):

    return

     (self.income-self.deduct)*self.rate

    defsetIncome

    (self,income):
    self.income = income

    return

     self

    defsetDeduct

    (self,deduct):
    self.deduct = deduct

    return

     self

    defsetRate

    (self,rate):
    self.rate = rate

    return

     self

    print

     "Smalltalk-style taxes due =", \
   TaxCalc().setIncome(50000).setRate(0.30).setDeduct(10000).taxdue()

用每个 "setter" 来返回 self 使我们能把“现有的”东西看作是每个方法应用的结果。这与 FP 闭包方法有许多有趣的相似点。

有了 Xoltar 工具包,我们就能创建具有所期望的合并数据与函数特性的完整的闭包,同时还允许多段闭包(nee 对象)来包含不同的包:
清单 9. Python 函数风格的税收计算

from

     functional 

    import

     *
taxdue    = 

    lambda

    : (income-deduct)*rate
incomeClosure = 

    lambda

     income,taxdue: closure(taxdue)
deductClosure = 

    lambda

     deduct,taxdue: closure(taxdue)
rateClosure  = 

    lambda

     rate,taxdue: closure(taxdue)
taxFP = taxdue
taxFP = incomeClosure(50000,taxFP)
taxFP = rateClosure(0.30,taxFP)
taxFP = deductClosure(10000,taxFP)

    print

     "Functional taxes due =",taxFP()

    print

     "Lisp-style taxes due =", \
   incomeClosure(50000,
     rateClosure(0.30,
       deductClosure(10000, taxdue)))()

我们定义的每一个闭包函数都携带了函数范围内定义的任何值,然后将这些值绑定到函数对象的全局范围。然而,函数的全局范围看上去不必与实际模块的全局范围相同,同时与不同闭包的“全局”范围也不相同。闭包只是简单地“携带数据”。

在示例中,我们使用了一些特殊函数在闭包范围 (income、deduct、rate) 内放入了特定绑定。修改设计以在范围内放入任何绑定也非常简单。我们还可以在示例中使用具有细微差别的不同函数风格,当然这只是为了好玩。第一个成功的将附加值绑定入闭包范围内;使 taxFP 成为可变,这些“加入到闭包”的行可以任意顺序出现。然而,如果要使用如 tax_with_Income 这样的不可变名称,就必须将绑定行按照一定顺序排列,然后将前面的绑定传递到下一个。无论如何,一旦必需的一切被绑定入闭包的范围内,我们就调用 "seeded" 函数。

第二种风格看上去更接近 Lisp,(对我来说更像圆括号)。如果不考虑美观,第二种风格中发生了二件有趣的事情。第一件是名称绑定完全被避免了。第二种风格是一个单一表达式而不使用语句(请参阅 第 1 部分,讨论为什么这样会有问题)。

其它有关“Lisp 风格”闭包使用的有趣例子是其与上文提到的“Smalltalk 风格”消息传递方法有多少类似。两者累积了值和调用 taxdue() 函数/方法(如果没有正确的数据,两者在这些原始版本中都将报错)。“Smalltalk 风格”在每一步之间传递对象,而“Lisp 风格”传递一个连续。但若是更深一层理解,函数和面向对象编程大部分都是这样。

(0)

相关推荐

  • 17个Python小技巧分享

    1.交换变量 复制代码 代码如下: x = 6 y = 5 x, y = y, x print x >>> 5 print y >>> 6 2.if 语句在行内 复制代码 代码如下: print "Hello" if True else "World" >>> Hello 3.连接 下面的最后一种方式在绑定两个不同类型的对象时显得很酷. 复制代码 代码如下: nfc = ["Packers",

  • Python的批量远程管理和部署工具Fabric用法实例

    本文实例讲述了Python的批量远程管理和部署工具Fabric用法.分享给大家供大家参考.具体如下: Fabric是Python中一个非常强大的批量远程管理和部署工具,常用于在多个远程PC上批量执行SSH任务. 常见的使用方法大概总结如下: 1. 首先,要将批量执行的任务写入到一个fabfile.py中, 复制代码 代码如下: # -*- coding:utf-8 -*-    from fabric.api import run, local, roles, env, cd  env.host

  • 推荐11个实用Python库

    1) delorean 非常酷的日期/时间库 复制代码 代码如下: from delorean import Delorean EST = "US/Eastern" d = Delorean(timezone=EST) 2) prettytable 可以在浏览器或终端构建很不错的输出 复制代码 代码如下: from prettytable import PrettyTable table = PrettyTable(["animal", "ferocity

  • 深入Python函数编程的一些特性

    绑定 细心的读者可能记得我在 第 1 部分的函数技术中指出的限制.特别在 Python 中不能避免表示函数表达式的名称的重新绑定.在 FP 中,名称通常被理解为较长表达式的缩写,但这一说法暗示着"同一表达式总是求出相同的值".如果标记的名称重新被绑定,这一暗示便不成立.例如,让我们定义一些在函数编程中要用到的快捷表达式,比如: 清单 1. 以下 Python FP 部分的重新绑定要造成故障 >>> car = lambda lst: lst[0] >>&g

  • python装饰器的特性原理详解

    这篇文章主要介绍了python装饰器的特性原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 今天发现了装饰器的另一种用法,下面就先上代码: data_list = [] def data_item(func): data_list.append(func) return func @data_item def foo(): return 1 @data_item def foo1(): return 2 @data_item def fo

  • 用 Python 元类的特性实现 ORM 框架

    ORM是什么 O是 object,也就 类对象 的意思,R是 relation,翻译成中文是 关系,也就是关系数据库中 数据表 的意思,M是 mapping,是映射的意思.在ORM框架中,它帮我们把类和数据表进行了一个映射,可以让我们通过类和类对象就能操作它所对应的表格中的数据.ORM框架还有一个功能,它可以根据我们设计的类自动帮我们生成数据库中的表,省去了我们自己建表的过程. 一个句话理解就是:创建一个实例对象,用创建它的类名当做数据表名,用创建它的类属性对应数据表的字段,当对这个实例对象操作

  • Python全面解读高级特性切片

    目录 1.切片的基础用法 2.切片的高级用法 3.自定义对象实现切片功能 3.1.魔术方法:`getitem()` 3.2.自定义序列实现切片功能 3.3.自定义字典实现切片功能 4.迭代器实现切片功能 4.1.迭代与迭代器 4.2.迭代器切片 5.小结 前言: 众所周知,我们可以通过索引值(或称下标)来查找序列类型(如字符串.列表.元组-)中的单个元素,那么,如果要获取一个索引区间的元素该怎么办呢? 切片(slice)就是一种截取索引片段的技术,借助切片技术,我们可以十分灵活地处理序列类型的对

  • 一起来看看五条Python中的隐含特性

    目录 1. 引言 2. 函数属性 3. For-else循环 4. int型分隔符 5. eval() and exec() 6. 省略号 6.1 占位符 6.2 numpy数组中用于切片 7.总结 1. 引言 最近闲暇之余,我会去阅读一些Python文档,有时候会注意到一些有趣的Python特性,这些特性不禁让人惊呼:“哇,Python原来还可以这么写”. 闲话少说,我们直接开始吧. :) 2. 函数属性 和设置类和对象的属性类似,我们在Python中也可以为函数设置属性.样例代码如下: de

  • Python面向对象的三大特性封装、继承、多态

    Python是一门面向对象的语言.面向对象都有三大特性:封装.继承.多态. 下面分别来说说这三大特性: 1.封装 隐藏对象的属性和实现细节,仅对外提供公共访问方式.在python中用双下划线开头的方式将属性设置成私有的 . 好处: 1. 将变化隔离: 2. 便于使用: 3. 提高复用性: 4. 提高安全性. 2.继承 继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类. 即一个派生类继承基类的字段和方法.继承也允许把一个派

  • Python 3.x 新特性及10大变化

    Python 3.x 起始版本是Python 3.0,目前的最新版本是 3.3.3 Python之父Guido van Rossum谈到了Python 3.0的构思: 一直以来,除非要打破向后兼容性,否则很多缺陷和错误都无法修复.因此,Python 3000将会作为第一个放弃向后兼容性的Python版本,目的就是要让Python向着最好的语言前进. Python的3.0版本,常被称为Python 3000,或简称Py3k.相对于Python的早期版本,这是一个较大的升级.为了不带入过多的累赘,P

  • Python 3.10 中 6 个兴奋的新特性

    新的 Python 版本推出了有趣的新功能. Python 是当今最流行的编程语言之一.它有广泛的领域和应用,从学习计算机科学的基础,到执行复杂或者直接的科学计算任务来创建游戏.它的高级应用甚至包含数据科学和量子计算. Python的流行有很多原因.其中最主要的原因是Python和其他语言相比,通用性强,简单易学.除此之外,Python的开发和维护者--Python软件基金会--一直致力于用新的方法改进Python. 一周之前(2021年10月4日),一个新的Python版本发布了,Python

  • python入门学习关于for else的特殊特性讲解

    目录 For-Else 特性的基础知识 使用 For-Else 特性的三个场景 1. 迭代并查找没有标志变量的项 2. 帮助打破嵌套循环 3. 帮助处理异常 总结 不管我们使用哪种编程语言,我们都会写"if-else"语句,但是"for-else"怎么样呢? 对于许多语言,例如 c. c + + 和 Java,如果在循环之后使用"else",那是完全错误的.然而,Python 作为一种优雅的语言,有这种奇怪但有用的特性.如果我们正确地使用它,我们

  • Python 十大特性

    目录 1.Python 2.Python 编程语言的特性 3.开源 4.Python 中的 GUI 编程支持 5.Python 支持高级语言 6.可扩展性 7.可移植性 8.大型标准库 9.解释性语言 10.面向对象程序设计语言 11.表达力 12.常见问题 前言: 在了解 Python 的特性之前,我们首先要了解 Python 编程语言是什么.Python 编程语言是世界上发展最快的编程语言.这一高级通用编程语言提供了广泛的实际应用,并且是一种非常流行的认证. Python 可以让程序员更加高

随机推荐