这部分介绍:递归函数、函数属性和注解, lambda表达式、如map和filter这样的函数式编程工具。
当你开始使用函数时,就开始面对如何将组件聚合在一起的选择了。例如,如何将任务分解成为更有针对性的函数(导致了聚合性),函数将如何通信(耦合性)等。你需要深入考虑函数的大小等概念,因为他们直接影响到代码的可用性。
函数设计概念:
耦合性:对于输入使用参数并且对于输出使用return语句。一般来讲,你需要力求让函数独立于它外部的东西。
耦合性:只有在真正必要的情况下使用全局变量。全局变量通常是一种不太好的函数间通信的办法,他们引发依赖关系和计时的问题
耦合性:不要改变可变类型的参数,除非调用者希望这样做。
聚合性:每一个函数都应该有一个单一的,统一的目标。在设计完美的情况下,每一个函数中都应该做一件事:这件事可以用一个简单说明句来总结。如果这个句子包含了很多事情(如,这个函数让员工产生并提交了一个披萨订单),你也许就应该想想是不是要将它分解成多个更简单的函数了。否则,是无法重用一个在函数中把所有步骤都混合在一起的代码。
大小:每一个函数都应该相对较小
耦合:避免直接改变在另一个模块文件中的变量
Python的类依赖于修改传入的可变对象:类的函数会自动设置传入参数self的属性,从而修改每个对象的状态信息。另外,如果没有使用类,全局变量通常是模块中函数保留调用中状态的最佳方式,如果都在预料之中,全局变量的副作用就没什么危险。
通常来讲,我们应该尽量使函数和其他补充组件中的外部依赖最小化。函数的自包性越好,它越容易理解、复用和修改。
def mysum(ll):
if not ll:
return 0
else:
return ll[0]+mysum(ll[1:]) # call myself
mysum([1,3,5,7])
Out[76]: 16
Python的三元表达式替代:
def mysum2(ll):
return 0 if not ll else ll[0]+mysum2(ll[1:])
mysum2([4,6,3,7])
处理任意结构(递归):
计算应该嵌套的子列表结构中所有数字的总和:
简单的循环语句在这里不起作用,因为这不是一个线性迭代。嵌套的循环语句也不够用,因为子列表可能嵌套到任意的深度并且以任意的形式嵌套。下面的代码使用递归来应对这种一般性的嵌套,以便顺序访问子列表:
def sumtree(ll):
result = 0
for x in ll:
if not isinstance(x, list):
result += x
else:
result += sumtree(x)
return result
lll = [1, [2, [3, 4], 5], 6, [7, 8]]
sumtree(lll)
Out[83]: 36
函数对象: 属性和注解
间接函数调用
Python函数是对象,我们可以编写通用的处理他们的程序。函数对象可以赋值给其他的名字、传递给其他函数、嵌入到数据结构、从一个函数返回给另一个函数等等,就好像它们是简单的数字或字符串。函数对象还恰好支持一个特殊操作:它们可以由一个函数表达式后面的括号中的列表参数调用。在def运行之后,函数名直接是一个对象的引用--我们可以自由地把这个对象赋值给其他的名称并且通过任何引用调用它:
>>> def echo(message):
print(message)
>>> echo('direct call')
direct call
>>> x = echo
>>> x('indirect call')
indirect call
由于参数通过赋值对象来传递,这就像是把函数作为参数传递给其他函数一样容易:
>>> def indirect(func, arg):
func(arg)
>>> indirect(echo, 'argument call')
argument call
我们甚至可以把函数对象的内容填入到数据结构中,就好像它们是整数或字符串一样:
>>> schedule = [(echo, 'spam'), (echo, 'ham')]
>>> for (func, arg) in schedule:
func(arg)
spam
ham
>>>
函数也可以创建并返回以便之后使用:
>>> def make(label):
def echo(message):
print(label+':'+message)
return echo
>>> ff = make('heiheihei')
>>> ff('mm')
heiheihei:mm
>>> ff('gg')
heiheihei:gg
>>>
函数内省
函数属性:
>>> echo.__name__
'echo'
>>> dir(echo)
['__annotations__', '__call__', '__class__', '__closure__', '__code__',
... ...
'__str__', '__subclasshook__']
>>>
也可以向函数附加任意的用户定义的属性:
>>> echo
<function echo at 0x000002993A548620>
>>> echo.count = 0
>>> echo.count += 1
>>> echo.count
1
>>> dir(echo)
['__annotations__', '__call__', '__class__', '__closure__', '__code__',
... ...
'__str__', '__subclasshook__', 'count']
>>>
这样的属性可以用来直接把状态信息附加到函数对象,而不必使用全局、非本地和类等其他技术。这种变量的名称对于一个函数来说是本地的,都是,其值在函数退出后仍然保留。
函数注解
>>> def func(a, b, c): #不带注解的函数
return a+b+c
>>> def func(a:'spam', b:(1,10), c:float)->int: #带注解的函数
return a+b+c
>>> func.__annotations__
{'a': 'spam', 'b': (1, 10), 'c': <class 'float'>, 'return': <class 'int'>}
>>>
>>> def func(a:'spam'=4, b:(1,10)=5, c:float=6)->int: #带默认值的注解函数
return a+b+c
注解可以作为函数装饰器参数。注解是一种功能随着你的想象变化的工具。
注意,注解值在def语句中有效,在lambda表达式中无效,因为lambda的语法已经限制了它所定义的函数工具。
匿名函数:lambda
lambda常常以一种行内进行函数定义的形式使用,或者用作推迟执行一些代码。
lambda表达式:
lambda argument1, argument2, ..., argumentN: expression using arguement
lambda是一个表达式,而不是一个语句
lambda的主体是一个单个的表达式,而不是一个代码块
>>> f = lambda x, y, z: x+y+z
>>> f(2,3,4)
9
>>> f = (lambda a='fee', b='fie', c='foe':a+b+c) #支持默认参数
>>> f('wee')
'weefiefoe'
>>>
为什么使用lambda?
函数速写。
回调处理器,它常常在一个注册调用的参数列表中编写成单行的lambda表达式。
lambda通常用来编写跳转表,也就是行为的列表或字典,能够按照需要执行相应的动作。如下:
>>> ll = [lambda x: x**2,
lambda x: x**3,
lambda x: x**4]
>>> for f in ll:
print(f(2))
4
8
16
>>> print(ll[0](3))
9
>>>
其实我们可以用python中的字典 或者其他数据结构来构建更多种类的行为表:
>>> key = 'got'
>>> {'already': (lambda: 2+2),
'got': (lambda: 2*4),
'one': (lambda: 2**6)}[key]()
8
这样编写代码可以使字典成为更加通用的多路分支工具。
回调:
>>> import sys
>>> from tkinter import Button, mainloop
>>> x = Button(text='Press me',
command=(lambda:sys.stdout.write('spam\n')))
>>> x.pack()
>>> mainloop()
这里,回调处理器是通过传递一个用lambda所生产的函数作为command的关键字参数。与def相比lambda的优点是处理按钮动作的代码都在这里,嵌入了按钮创建的调用中。
lambda直到事件发生时才会调用处理器执行,在按钮按下时,编写的调用才发生,而不是在按钮创建时发生。
因为嵌套的函数作用域法则对lambda也有效,他们也使回调函数处理器变得更简单易用。这对于获取特定的self实例参数是很方便的:
class MyGui:
def makewidgets(self):
Button(command=(lambda: self.onPress('spam')))
def onPress(self, message):
......
在老版本中,self必须作为默认参数来传入到lambda中。
在序列中映射函数:Map
map对一个可迭代对象中的项应用一个函数: map(func, iterable_arg)
由于map期待传入一个函数,它恰好是lambda通常出现的地方之一。
list(map(lambda x: x+3), [1,2,3])
map高级使用方法,如提供了多个序列作为参数,它能够并行返回分别以每个序列中的元素作为函数对应参数得到的结果的列表。
>>> pow(3,4)
81
>>> list(map(pow,[1,2,3],[2,3,4]))
[1, 8, 81]
函数式编程工具: filter 和 reduce
在python内置函数中,map函数是用来进行函数式编程的这类工具中最简单的内置函数代表:函数式编程的意思就是对序列应用一些函数的工具。如,基于某一测试函数过滤出一些元素(filter), 以及对每个元素都应用函数并运行到最后结果(reduce)。
range和filter都返回可迭代对象,它们需要list调用来显示所有结果。
>>> list(map(pow,[1,2,3],[2,3,4]))
[1, 8, 81]
>>> list(range(-5,5))
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
>>> list(filter((lambda x:x>0), range(-4,5)))
[1, 2, 3, 4]
reduce位于functools模块中,它接收一个迭代器来处理 ,但是,它本身本身一个迭代器,它返回一个单个的结果:
>>> from functools import reduce
>>> reduce((lambda x, y: x*y), [1,2,3,4])
24
Learning Python, Fourth Edition, by Mark Lutz.