wordpress怎么精简,奉化云优化seo,在线crm视频,如何上传文件到网站个人总结难免疏漏#xff0c;请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。
本文主要介绍函数相关的高级概念#xff1a;递归函数、函数注解、lambda表达式函数#xff0c;常用函数工具如map、filter、reduce#xff0c;以及通用的函数设…个人总结难免疏漏请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。
本文主要介绍函数相关的高级概念递归函数、函数注解、lambda表达式函数常用函数工具如map、filter、reduce以及通用的函数设计思想。整体不是特别难如果你从之前每个笔记看过来的话部分观念是很容易接受的可能稍难点的就是递归了理解了难度其实不高。以及强调了不要过多lambda表达式重复嵌套代码可能晦涩难懂了。 介绍一系列更高级的与函数相关的话题递归函数、函数属性和注解、lambda表达式、如map和filter这样的函数式编程工具。可能在日常的工作中不会碰到它们。然而由于它们在某些领域中有用有必要对它们有个基本的理解。例如lambda在GUI中是很常用的。
函数设计概念
当你开始使用函数时就开始面对如何将组件聚合在一起的选择了。
例如如何将任务分解成为更有针对性的函数导致了聚合性、函数将如何通信耦合性等。需要深入考虑函数的大小等概念因为它们直接影响到代码的可用性。其中的一些属于结构分析和设计的范畴但是它们和其他概念一样也适用于Python代码。
在第17笔记中介绍过了关于函数和模块耦合性的观念这里是一个对Python初学者的一些通用的指导方针的复习。
·耦合性对于输入使用参数并且对于输出使用return语句。一般来讲你需要力求让函数独立于它外部的东西。参数和return语句通常就是隔离对代码中少数醒目位置的外部的依赖关系的最好办法。·耦合性只有在真正必要的情况下使用全局变量。全局变量也就是说在整个模块中的变量名通常是一种蹩脚的函数间进行通信的办法。它们引发了依赖关系和计时的问题会导致程序调试和修改的困难。·耦合性不要改变可变类型的参数除非调用者希望这样做。函数会改变传入的可变类型对象但是就像全局变量一样这会导致很多调用者和被调用者之间的耦合性这种耦合性会导致一个函数过于特殊和不友好。·聚合性每一个函数都应该有一个单一的、统一的目标。在设计完美的情况下每一个函数中都应该做一件事这件事可以用一个简单说明句来总结。如果这个句子很宽泛例如“这个函数实现了整个程序”或者包含了很多的排比例如“这个函数让员工产生并提交了一个比萨订单”你也许就应该想想是不是要将它分解成多个更简单的函数了。否则是无法重用在一个函数中把所有步骤都混合在一起的代码。·大小每一个函数应该相对较小。从前面的目标延伸而来这就比较自然但是如果函数在显示器上需要翻几页才能看完也许就到了应该把它分开的时候了。特别是Python代码是以简单明了而著称一个过长或者有着深层嵌套的函数往往就成为设计缺陷的征兆。保持简单保持简短。·耦合避免直接改变在另一个模块文件中的变量。在第17笔记中介绍过了这个概念下面将会在下一部分学习模块时重新复习它。作为参考记住在文件间改变变量会导致模块文件间的耦合性就像全局变量产生了函数间的耦合一样模块难于理解和重用。在可能的时候使用读取函数而不是直接进行赋值语句。
图19-1总结了函数与外部世界通信的方法。输入可能来自于左侧的元素而结果能以右侧的任意一种形式输出。很多函数设计者倾向于只使用参数作为输入return语句作为输出。 函数执行环境。函数可以通过多种办法获得输入产生输出尽管使用参数作为输入return语句并配合可变参数的改变作为输出时函数往往更容易理解和维护。输出也可能采取存在于一个封闭的函数作用域中的声明的nonlocal名称的形式。 前面设计的法则有很多特例包括一些与Python的OOP支持相关的内容。后面的OPP内容将会讲到Python的类依赖于修改传入的可变对象类的函数会自动设置传入参数self的属性从而修改每个对象的状态信息例如self.namebob。另外如果没有使用类全局变量通常是模块中函数保留调用中状态的最佳方式。如果都在预料之中副作用就没什么危险。
通常来讲我们应该竭力使函数和其他编程组件中的外部依赖性最小化。函数的自包含性越好它越容易被理解、复用和修改。 递归函数
第17笔记讨论作用域规则的时候简短地提及Python支持递归函数——即直接或间接地调用自身以进行循环的函数。递归是颇为高级的话题并且它在Python中相对少见。
然而它是一项应该了解的有用的技术因为它允许程序遍历拥有任意的、不可预知的形状的结构。递归甚至是简单循环和迭代的替换尽管它不一定是最简单的或最高效的一种。
递归求和
要对一个数字列表或者其他序列求和我们可以使用内置的sum函数或者自己编写一个更加定制化的版本。这里是用递归编写的一个定制求和函数的示例
def mysum(l):if not l:return 0else:return l[0] mysum(l[1:])mysum([1,2,3,4])
10
在每一层这个函数都递归地调用自己来计算列表剩余的值的和这个和随后加到前面的一项中。当列表变为空的时候递归循环结束并返回0。当像这样使用递归的时候对函数调用的每一个打开的层级在运行时调用堆栈上都有自己的一个函数本地作用域的副本也就是说这意味着L在每个层级都是不同的。
如果这很难理解并且对于新程序员来说它常常是难以理解尝试给函数添加一个L的打印并再次运行它从而在每个调用层级记录下当前的列表
def mysum(l):print(l)if not l:return 0else:return l[0] mysum(l[1:])mysum([1,2,3,4])
[1, 2, 3, 4]
[2, 3, 4]
[3, 4]
[4]
[]
10
在每个递归层级上要加和的列表变得越来越小直到它变为空——递归循环结束。加和随着递归调用的展开而计算出来。这里实际就是把每次递归的第一个偏移给求和因为每次都会返回L[0]。
替代方案
也可以使用Python的三元if/else表达式也可以针对任何可加和的类型一般化如果我们至少假设输入中的一项的话这将会变得较容易些就像在第18笔记最小最大值的示例并且使用扩展序列赋值来使得第一个/其他的解包更简单 例子中的后两个由于空的列表而失败但是考虑到支持的任何对象类型的序列而不只是数字
mysum([1]) #第二个会失败 如果mysum([])
1mysum([s,p,a,m])
spammysum([spam,ham,eggs])
spamhameggs
研究这3个变体将会发现后两者在一个单个字符串参数上也有效例如mysum(spam)因为字符串是一字符的字符串的序列第三种变体在任意可迭代对象上都有效包括打开的输入文件但是其他的两种不会有效因为它们索引并且函数头部def mysum(first,*rest)尽管类似于第三种变体但根本没法工作因为它期待单个参数而不是一个单独的可迭代对象。
归是可以是直接的就像目前为止给出的例子一样也可以是间接的就像下面的例子一样一个函数调用另一个函数后者反过来调用其调用者。直接的效果是相同的尽管这在每个层级有两个函数调用
def mysum(l):if not l: return 0return nonempty(l)def nonempty(l): return l[0] mysum(l[1:])mysum([1.1,2.2,3.3,4.4])
11.0
循环语句VS递归
递归对于上一小节的求和的例子有效但在那种环境中可能过于追求技巧。
实际上递归在Python中并不像在Prolog或Lisp这样更加深奥的语言中那样常用因为Python强调像循环这样的简单的过程式语句循环语句通常更为自然。
例如while常常使得事情更为具体一些并且它不需要定义一个支持递归调用的函数
l [1,2,3,4]
sum 0
while l:sum l[0]l l[1:]sum
10
以及可以使用for循环为我们自动迭代使得递归在大多数情况下不必使用并且很可能递归在内存空间和执行时间方面效率较低
l [1,2,3,4]
sum 0
for x in l: sum xsum
10
有了循环语句不需要在调用堆栈上针对每次迭代都有一个本地作用域的副本并且避免了一般会与函数调用相关的速度成本。
处理任意结构
另一方面递归或者对等的显式的基于堆栈的算法可以要求遍历任意形状的结构。
作为递归在这种环境中的应用的一个简单例子考虑像下面的任务计算一个嵌套的子列表结构中所有数字的总和
l [1,[2,[3,4],5],6,[7,8]]
简单的循环语句在这里不起作用因为这不是一个线性迭代。嵌套的循环语句也不够用因为子列表可能嵌套到任意的深度并且以任意的形式嵌套。
相反下面的代码使用递归来对应这种一般性的嵌套以便顺序访问子列表
def sumtree(l):tot 0for x in l:if not isinstance(x,list):tot xelse:tot sumtree(x) #子列复选return totl [1,[2,[3,4],5],6,[7,8]]
print(sumtree(l))
36print(sumtree([1,[2,[3,[4,[5]]]]]))
15
print(sumtree([[[[[1],2],3],4],5]))
15
留意末尾的案例看看递归是如何遍历其嵌套的列表的。尽管这个例子是人为编写的它是一类更大的程序的代表例如继承树和模块导入链可以展示类似的通用结构。实际上在后面更为实用的示例中再次使用递归的这一用法
在第24笔记的reloadall.py中用来遍历导入链。
·在第28笔记的classtree.py中用来遍历类继承树。
·在第30笔记的lister.py中再次用来遍历类继承树。
尽管出于简单性和高效率的目的对于线性迭代通常应该更喜欢使用循环语句而不是递归还是会发现像后面的示例一样的不可缺少递归的情况。
此外有时候需要意识到程序中无意的递归的潜在性。类中的一些运算符重载方法例如__setattr__和__getattribute__如果使用不正确的话都有潜在的可能会递归地循环。递归是一种强大的工具但它会比预期的更好。 函数对象属性和注解
Python函数比想象的更为灵活。Python中的函数比一个编译器的代码生成规范还要多——Python函数是俯拾皆是的对象自身全部存储在内存块中。同样它们可以跨程序自由地传递和间接调用。它们也支持与调用根本无关的操作——属性存储和注解。
间接函数调用
由于Python函数是对象我们可以编写通用的处理它们的程序。函数对象可以赋值给其他的名字、传递给其他函数、嵌入到数据结构、从一个函数返回给另一个函数等等就好像它们是简单的数字或字符串。
函数对象还恰好支持一个特殊操作它们可以由一个函数表达式后面的括号中的列表参数调用。然而函数和其他对象一样属于通用的领域。
已经在前面的示例中看到了函数的这些通用应用中的一些这里进行一个快速概览强调对象模型。
例如对于用于一条def语句中的名称没有什么特别的它只是当前作用域中的一个变量赋值就好像它出现在一个符号的左边。在def运行之后函数名直接是一个对象的引用——我们可以自由地把这个对象赋给其他的名称并且通过任何引用调用它
def echo(msg): print(msg)echo(hello) #原始名称调用函数对象
hellox echo #x也引用了函数
x(hihihi) #调用对象通过名称添加
hihihi由于参数通过赋值对象来传递这就像是把函数作为参数传递给其他函数一样容易。随后被调用者可能通过把参数添加到括号中来调用传入的函数
def indirect(func,arg): func(arg) #通过传入对象调用indirect(echo,hihihi) #将函数传给另一个函数
hihihi
甚至可以把函数对象的内容填入到数据结构中就好像它们是整数或字符串一样。
例如下面的程序把函数两次嵌套到一个元组列表中作为一种动作表。由于像这样的Python复合类型可以包含任意类型的对象
schedule [(echo,hello),(echo,hi)]
for (func,arg) in schedule: func(arg) #容器中嵌入的调用函数hello
hi
这段代码只是遍历schedule列表每次遍历的时候使用一个参数来调用echo函数。
在第17笔记示例中所见到的函数也可以创建并返回以便之后使用
def make(lab):def echo(msg):print(lab : msg)return echof make(spam)
f(ham) #调用返回的函数
spam:ham
f(eggs)
spam:eggs通过这些示例可以看出Python的通用对象模式和无须类型声明使其有了非常高的灵活性。
函数内省
由于函数是对象可以用常规的对象工具来处理函数。
实际上函数比预料的更灵活。例如一旦创建一个函数可以像往常一样调用它 但是调用表达式只是定义来在函数对象上工作的一个操作。也可以通用地检查它们的属性 内省工具允许我们探索实现细节——例如函数已经附加了代码对象代码对象提供了函数的本地变量和参数等方面的细节 这里用上面的make函数举例
make.__name__
makedir(make)
[__annotations__, __builtins__, __call__, __class__, __closure__, __code__, __defaults__, __delattr__, __dict__, __dir__, __doc__, __eq__, __format__, __ge__, __get__, __getattribute__, __getstate__, __globals__, __gt__, __hash__, __init__, __init_subclass__, __kwdefaults__, __le__, __lt__, __module__, __name__, __ne__, __new__, __qualname__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__, __type_params__]make.__code__
code object make at 0x0000019CC375EB10, file pyshell#79, line 1dir(make.__code__)
[__class__, __delattr__, __dir__, __doc__, __eq__, __format__, __ge__, __getattribute__, __getstate__, __gt__, __hash__, __init__, __init_subclass__, __le__, __lt__, __ne__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__, _co_code_adaptive, _varname_from_oparg, co_argcount, co_cellvars, co_code, co_consts, co_exceptiontable, co_filename, co_firstlineno, co_flags, co_freevars, co_kwonlyargcount, co_lines, co_linetable, co_lnotab, co_name, co_names, co_nlocals, co_positions, co_posonlyargcount, co_qualname, co_stacksize, co_varnames, replace]make.__code__.co_varnames
(lab, echo)make.__code__.co_argcount
1
工具编写者可以利用这些信息来管理函数。
函数属性
函数对象不仅限于前面中列出的系统定义的属性也能向函数附加任意的用户定义的属性
make
function make at 0x0000019CC3CDA660make.count 0
make.count 1
make.count
1make.handles hihi
make.handles
hihidir(make)
[__annotations__, __builtins__, __call__, __class__, __closure__, __code__, __defaults__, __delattr__, __dict__, __dir__, __doc__, __eq__, __format__, __ge__, __get__, __getattribute__, __getstate__, __globals__, __gt__, __hash__, __init__, __init_subclass__, __kwdefaults__, __le__, __lt__, __module__, __name__, __ne__, __new__, __qualname__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__, __type_params__, count, handles]这样的属性可以用来直接把状态信息附加到函数对象而不必使用全局、非本地和类等其他技术。
和非本地不同这样的属性可以在函数自身的任何地方访问。从某种意义上讲这也是模拟其他语言中的“静态本地变量”的一种方式——这种变量的名称对于一个函数来说是本地的但是其值在函数退出后仍然保留。属性与对象相关而不是与作用域相关但直接效果是类似的。
函数注解
可以给函数对象附加注解信息——与函数的参数和结果相关的任意的用户定义的数据。
可以理解成用于提供函数的类型提示和文档说明。这种类型提示可以帮助开发者更好地理解函数的使用方式同时也可以利用这些信息进行一些静态类型检查从而提高代码的可读性和可维护性。
Python为声明注解提供了特殊的语法但是自身不做任何事情注解完全是可选的并且出现的时候只是直接附加到函数对象的__annotations__属性以供其他用户使用。
前面介绍了 keyword-only参数注解则进一步使函数头部语法通用化。考虑如下的不带注解的函数它编写为带有3个参数并且返回一个结果
def func(a,b,c): return abcfunc(1,2,3)
6
从语法上讲函数注解编写在def头部行就像与参数和返回值相关的任意表达式一样。对于参数它们出现在紧随参数名之后的冒号之后对于返回值它们编写于紧跟在参数列表之后的一个-之后。
例如这段代码注解了前面函数的3个参数及其返回值
def func(a:spam,b:(1,10),c:float)-int:return a b cfunc(1,2,3)
6
调用一个注解过的函数像以前一样不过当注解出现的时候Python将它们收集到字典中并且将它们附加给函数对象自身。参数名变成键如果编写了返回值注解的话它存储在键return下而注解键的值则赋给了注解表达式的结果
func.__annotations__
{a: spam, b: (1, 10), c: class float, return: class int}
由于注解只是附加到一个Python对象的Python对象注解可以直接处理。下面的例子只是注解了3个参数中的两个并且通用地遍历附加的注解
def func(a:spam,b,c:99):return a b cfunc(1,2,3)
6func.__annotations__
{a: spam, c: 99}for arg in func.__annotations__:print(arg, ,func.__annotations__[arg])a spam
c 99
值得注意有2点。首先如果编写了注解仍然可以对参数使用默认值——注解及其:字符出现在默认值及其字符之前。
例如下面的a:spam4意味着参数a的默认值是4并且用字符串spam注解它
def func(a: spam 4,b: (1,10) 5, c: float 6)-int:return a b cfunc(1,2,3)
6func()
15func(1,c10)
16func.__annotations__
{a: spam, b: (1, 10), c: class float, return: class int}
还要注意前面例子中的空格都是可选的——可以在函数头部的各部分之间使用空格也可以不用但省略它们对某些读者来说可能会提高代码的可读性
def func(a:spam4, b:(1,10)5, c:float6)-int:return a b cfunc(1,2,3)
6func.__annotations__
{a: spam, b: (1, 10), c: class float, return: class int}
注释可以用作参数类型或值的特定限制并且较大的API可能使用这一功能作为注册函数接口信息的方式。
实际上后面将会在第38笔记中看到一个潜在的应用那里将看到注解作为函数装饰器参数这是一个更为通用的概念其中信息编写于函数头部之外并且由此不仅限于一种用途的一种替代方法。和Python自身一样注解是一种功能随着你的想象来变化的工具。
最后注意注解只在def语句中有效在lambda表达式中无效因为lambda的语法已经限制了它所定义的函数工具。这把我们带入到下一个主题。 匿名函数lambda
除了def语句之外Python还提供了一种生成函数对象的表达式形式。由于它与LISP语言中的一个工具很相似所以称为lambda。在Python中这其实只是一个关键词作为引入表达式的语法而已。除了继承了数学的含糊性lambda比想象的要容易使用。
lambda表达式
lambda的一般形式是关键字lambda之后是一个或多个参数与一个def头部内用括号括起来的参数列表极其相似紧跟的是一个冒号之后是一个表达式 由lambda表达式所返回的函数对象与由def创建并赋值后的函数对象工作起来是完全一样的但是lambda有一些不同之处让其在扮演特定的角色时很有用。
·lambda是一个表达式而不是一个语句。因为这一点lambda能够出现在Python语法不允许def出现的地方——例如在一个列表常量中或者函数调用的参数中。作为一个表达式lambda返回了一个值一个新的函数可以选择性地赋值给一个变量名。相反def语句总是得在头部将一个新的函数赋值给一个变量名而不是将这个函数作为结果返回。·lambda的主体是一个单个的表达式而不是一个代码块。这个lambda的主体简单得就好像放在def主体的return语句中的代码一样。简单地将结果写成一个顺畅的表达式而不是明确的返回。因为它仅限于表达式lambda通常要比def功能要小你仅能够在lambda主体中封装有限的逻辑进去连if这样的语句都不能够使用。这是有意设计的——它限制了程序的嵌套lambda是一个为编写简单的函数而设计的而def用来处理更大的任务。
除了这些差别def和lambda都能够做同样种类的工作。例如如何使用def语句创建函数。 但是能够使用lambda表达式达到相同的效果通过明确地将结果赋值给一个变量名之后就能够通过这个变量名调用这个函数。 这里的f被赋值给一个lambda表达式创建的函数对象。这也就是def所完成的任务只不过def的赋值是自动进行的。
默认参数也能够在lambda参数中使用就像在def中使用一样。
x lambda aa1,bb2,cc3: abc
x()
a1b2c3
x(ee)
eeb2c3
在lambda主体中的代码想在def内的代码一样都遵循相同的作用域查找法则。
lambda表达式引入的一个本地作用域更像一个嵌套的def语句将会自动从上层函数中、模块中以及内置作用域中通过LEGB法则查找变量名。 为什么使用lambda
通常来说lambda起到了一种函数速写的作用允许在使用的代码内嵌入一个函数的定义。它们完全是可选的你总是能够使用def来替代它们但是在你仅需要嵌入小段可执行代码的情况下它们会带来一个更简洁的代码结构。
例如在稍后会看到回调处理器它常常在一个注册调用registration call的参数列表中编写成单行的lambda表达式而不是使用在文件其他地方的一个def来定义之后引用那个变量名。
lambda通常用来编写跳转表jump table也就是行为的列表或字典能够按照需要执行相应的动作。如下段代码所示。
l [lambda x: x**2, lambda x: x**3, lambda x: x**4]for f in l: print(f(2))4
8
16print(l[0](3))
9
当需要把小段的可执行代码编写进def语句从语法上不能编写进的地方时lambda表达式作为def的一种速写来说是最为有用的。
例如这种代码片段可以通过在列表常量中嵌入lambda表达式创建一个含有三个函数的列表。一个def是不会在列表常量中工作的因为它是一个语句而不是一个表达式。对等的def代码可能需要在想要使用的环境之外有临时性函数名称和函数定义。下面是对等def语句 实际上可以用Python中的字典或者其他的数据结构来构建更多种类的行为表从而做同样的事情。下面是另一个例子
key get{a1: lambda:22, get: lambda:2*4, z9: lambda:2**6}[key]()
8
在这里当Python创建这个字典的时候每个嵌套的lambda都生成并留下了一个在之后能够调用的函数。通过键索引来取回其中一个函数而括号使取出的函数被调用。与之前的if语句的扩展用法相比这样编写代码可以使字典成为更加通用的多路分支工具。
如果不是用lambda做这种工作需要使用三个文件中其他地方出现过的def语句来替代也就是在这些函数将会使用的那个字典外的某处需要定义这些函数。 同样会实现相同的功能但是def也许会出现在文件中的任意位置即使它们只有很少的代码。类似刚才lambda的代码提供了一种特别有用的可以在单个情况出现的函数如果这里的三个函数不会在其他的地方使用到那么将它们的定义作为lambda嵌入在字典中就是很合理的了。不仅如此def格式要求为这些小函数创建变量名这些变量名也许会与这个文件中的其他变量名发生冲突也可能不会但总是有可能。
lambda在函数调用参数里作为行内临时函数的定义并且该函数在程序中不在其他地方使用时是很方便的。
如何不要让Python代码变得晦涩难懂
由于lambda的主体必须是单个表达式而不是一些语句由此可见仅能将有限的逻辑封装到一个lambda中。如果你知道在做什么那么你就能在Python中作为基于表达式等效的写法编写足够多的语句。
例如如果你希望在lambda函数中进行print直接编写sys.stdout.write(str(x)\n)这个表达式而不是使用print(x)这样的语句。
类似地要在一个lambda中嵌套逻辑可以使用曾经介绍过的if/else三元表达式或者对等的但需要些技巧的and/or组合。正如我们前面所了解到的如下语句 能够由以下的概括等效的表达式来模拟 因为这样类似的表达式能够放在lambda中所以它们能够在lambda函数中来实现选择逻辑。
lower lambda x,y:x if xy else ylower(bb2,a)
a
lower(a,bb2)
a
此外如果需要在lamdba函数中执行循环能够嵌入map调用或列表解析表达式来实现。 这些技巧必须在万不得已的情况下才使用。一不小心它们就会导致不可读也称为晦涩难懂的Python代码。
一般来说简洁优于复杂明确优于晦涩而且一个完整的语句要比神秘的表达式要好。
这就是为什么lambda仅限于表达式。如果你有更复杂的代码要编写可使用deflambda针对较小的一段内联代码。从另一个方面来说你也会发现适度的使用这些技术是很有用处的。
嵌套lambda和作用域
lambda是嵌套函数作用域查找的最大受益者。
例如在下面的例子中lambda出现在def中很典型的情况并且在上层函数调用的时候嵌套的lambda能够获取到在上层函数作用域中的变量名x的值。 18笔记中关于嵌套函数作用域的讨论没有表明的就是lambda也能够获取任意上层lambda中的变量名。这种情况有些隐晦但是想象一下如果把上一个例子中的def换成一个lambda。 这里嵌套的lambda结构让函数在调用时创建了一个函数。无论以上哪种情况嵌套的lambda代码都能够获取在上层lambda函数中的变量x。这可以工作但是这种代码让人相当费解。出于对可读性的要求通常来说最好避免使用嵌套的lambda。
为什么要在意回调
lambda的另一个常见的应用就是为Python的tkinter GUI API定义行内的回调函数。
例如如下的代码创建了一个按钮这个按钮在按下的时候会打印一行信息假设tkinter在你的计算机上可用它在Windows和其他操作系统上是默认打开的。 这里回调处理器是通过传递一个用lambda所生产的函数作为command的关键字参数。与def相比lambda的优点就是处理按钮动作的代码都在这里嵌入了按钮创建的调用中。
实际上lambda直到事件发生时才会调用处理器执行。在按钮按下时编写的调用才发生而不是在按钮创建时发生。
因为嵌套的函数作用域法则对lambda也有效它们也使回调处理器变得更简单易用自Python2.2之后它们自动查找编写时所在的函数中的变量名并且在绝大多数情况下都不再需要传入参数默认参数。这对于获取特定的self实例参数是很方便的这些参数是在上层的类方法函数中的本地变量关于类的更多内容在第六部分介绍。 这里self必须要作为默认参数来传入到lambda中具体视版本。 在序列中映射函数map
程序对列表和其他序列常常要做的一件事就是对每一个元素进行一个操作并把其结果集合起来。例如在一个列表counter中更新所有的数字可以简单地通过一个for循环来实现。 因为这是一个如此常见的操作Python实际上提供了一个内置的工具为你做了大部分的工作。map函数会对一个序列对象中的每一个元素应用被传入的函数并且返回一个包含了所有函数调用结果的一个列表。如下所示。 之前简短地介绍过map它对一个可迭代对象中的项应用一个内置函数。
这里将会传入一个用户定义的函数来对它进行充分的利用从而可以对列表中的每一个元素应用这个函数map对每个列表中的元素都调用了inc函数并将所有的返回值收集到一个新的列表中。别忘了map在是一个可迭代对象。
由于map期待传入一个函数它恰好是lambda通常出现的地方之一 这里函数将会为counters列表中的每一个元素加3。因为这个函数不会在其他的地方用到所以将它写成了一行的lambda。因为这样使用map与for循环是等效的在多编写一些的代码后你就能够自己编写一个一般的映射工具了。 假设函数inc仍然像前面出现时那样可以用内置函数或我们自己的对等形式将其映射到一个序列 因为map是内置函数它总是可用的并总是以同样的方式工作还有一些性能方面的优势简而言之它要比自己编写的for循环更快。
此外map还有比这里介绍的更高级的使用方法。例如提供了多个序列作为参数它能够并行返回分别以每个序列中的元素作为函数对应参数得到的结果的列表。
pow(3,4) #3**4
81list(map(pow,[1,2,3],[2,3,4])) #1**2 2**3 3**4
[1, 8, 81]
对于多个序列map期待一个N参数的函数用于N序列。这里pow函数在每次调用中都使用了两个参数每个传入map的序列中都取一个。尽管我们大概也能够来模拟这样做但是当有速度优势的内置函数已经提供了这样的功能再去模拟意义不是很大。
注意map调用学过的列表解析很相似但是map对每一个元素都应用了函数调用而不是任意的表达式。因为这点限制从某种意义上来说它成为了不太通用的工具。尽管如此在某些情况下目前map比列表解析运行起来更快也就是说当映射一个内置函数时并且它所编写的代码也较少。
函数式编程工具filter和reduce
在Python内置函数中map函数是用来进行函数式编程的这类工具中最简单的内置函数代表函数式编程的意思就是对序列应用一些函数的工具。
例如基于某一测试函数过滤出一些元素filter以及对每对元素都应用函数并运行到最后结果reduce。由于range和filter都返回可迭代对象它们需要list调用来显示其所有结果。
例如下面这个filter的调用实现了从一个序列中挑选出大于0的元素。
list(range(-5,5))
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]list(filter((lambda x :x0),range(-5,5)))[1, 2, 3, 4]
序列中的元素若其返回值为真的话将会被键入到结果的列表中。就像map这个函数也能够概括地用一个for循环来等效但是它也是内置的运行比较快。 reduce在Python中则位于functools模块中要更复杂一些。它接受一个迭代器来处理但是它自身不是一个迭代器它返回一个单个的结果。这里是两个reduce调用计算了在一个列表中所有元素加起来的和以及乘起来的乘积。
from functools import reducereduce((lambda x,y: xy),[1,2,3,4])10
reduce((lambda x,y: x*y),[1,2,3,4])24
每一步reduce传递了当前的和或乘积以及列表中下一个的元素传给列出的lambda函数。默认序列中的第一个元素初始化了起始值。这里是一个对第一个调用的for循环的等效在循环中使用了额外的代码。 编写自己的reduce版本实际上相当直接。如下的函数模拟内置函数的大多数行为并且帮助说明其一般性的运作 这个内置的reduce还允许一个可选的第三个参数放置于序列的各项之前从而当序列为空时充当一个默认的结果。
再看看内置的operator模块其中提供了内置表达式对应的函数并且对于函数式工具来说它使用起来是很方便的要了解关于这一模块的更多内容请参阅Python的库手册。
import operator,functools
functools.reduce(operator.add,[2,4,6])
12
functools.reduce((lambda x,y: xy),[2,4,6])
12
与map一样filter和reduce支持了强大的函数式编程的技术。一些人也将lambda、列表解析扩展进了Python中函数式工具集中。