目录
递归函数、函数属性和注释、lambda表达式,如map和filter这样的函数式编程工具都是本文叙述的内容。
谈到设计函数,必须要提到函数的耦合性和聚合性。关于如何避免耦合性,需要记住以下几点:1)对于输入使用参数,并且输出使用return语句,2)只有在真正必要的情况下使用全局变量,3)不要改变可变类型的参数,除非调用者希望这样做,4)避免直接改变在另一个模块文件中的变量。所谓聚合,要求每一个函数应该有一个单一的、统一的目标。python代码以简单明了著称,一个过长或者有着深层嵌套的函数往往就成为设计缺陷的征兆。
通常要使函数和其他编程组件中的外部依赖性最小化,函数的自包含性越好,它越容易被理解、复用和修改。
1 递归函数VS循环
递归是一种有用的技术,因为它允许程序遍历拥有任意的,不可预知的形状的结构。
下面是用递归的方式求和,过于技巧,直接使用循环即可,循环不需要在调用堆栈时针对每次迭代都有一个本地作用域的副本,还可以避免与函数调用相关的速度成本。
>>> def mysum(L):
... if not L:
... return 0
... else:
... return L[0] + mysum(L[1:])
...
>>> mysum([1,2,3,4,5])
15
# 循环
>>> L = [1,2,3,4,5]
>>> sum = 0
>>> while L:
... sum += L[0]
... L = L[1:]
...
>>> sum
15
递归的优势:处理任意结构
>>> def sumtree(L):
... tot = 0
... for x in L:
... if not isinstance(x,list):
... tot += x
... else:
... tot += sumtree(x)
... return tot
...
>>> L = [1,[2,[3,4],5],6,[7,8]]
>>> print(sumtree(L))
36
2 函数对象:属性和注解
函数可以跨程序自由地传递和间接调用,也支持与调用根本无关的操作——属性存储和注解
1)间接函数调用总结
函数对象可以赋值给其他的名字,传递给其他函数,嵌入到数据结构,从一个函数返回给另一个函数等等
# 函数名直接是一个函数对象的引用
>>> def echo(message):
... print(message)
...
>>> 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!
2) 函数内省
允许我们探索函数的本地变量和参数方面的细节,利用这些信息来管理函数
>>> def f(a):
... b = 'spam'
... return b*a
...
>>> f(8)
'spamspamspamspamspamspamspamspam'
>>> f.__name__
'f'
>>> dir(f)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
3) 函数属性
除了系统定义的属性,我们可以向函数附加用户定义的任意属性。这些属性可以将状态信息附加给函数对象,而不必使用全局、非本地和类等其他技术。这些属性可以在函数自生的任何地方访问,类似其他语言的“静态本地变量”的访问方式。属性与对象相关,不与作用域相关,但最终的效果是相似的。
>>> f.count = 0
>>> f.count += 1
>>> f.count
1
>>> f.handles = 'Button-Press'
>>> f.handles
'Button-Press'
>>> dir(f)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'handles']
>>>
4) 函数注解(python3)
注解信息是与函数参数和结果相关的任意的用户定义的数据。它有特殊的语法,不做任何事情,是可选的。出现的时候,只是直接附加到函数对象的__annotations__属性供其他用户使用。
注解编写在def的头部,对于参数,它们出现在紧随参数名之后的冒号之后;对于返回值,它们编写于紧跟在参数列表之后的一个->之后。当注解出现的时候,python将它们收集到字典中,并将它们附加给函数对象,参数名变成键,若返回值也编写注解,则存在在键“return”下。
>>> def func(a:'spam', b:(1,10), c:float)->int:
... return a+b+c
...
>>> func(1,2,3)
6
>>> func.__annotations__
{'a': 'spam', 'b': (1, 10), 'c': <class 'float'>, 'return': <class 'int'>}
注意:即使编写了注解,也可以对参数使用默认值,注解出现在默认值之前。
3 lambda匿名函数
python提供了一种生成函数对象的表达式形式,由于它于LISP语言中的一个工具很相似,所以称为lambda。就像def一样,这个表达式创建一个之后能够调用的函数,但是它返回了一个函数而不是将这个函数赋值给一个变量名。这也是lambda有时叫做匿名函数的原因。
lambda的一般形式是关键字lambda,之后是一个或多个参数,后面紧跟的是一个冒号,之后是一个表达式
lambda与普通函数的区别是lambda是一个表达式,而不是一个语句;lambda的主体是一个单个表达式,而不是一个代码块。默认参数也可以在lambda参数中使用。lambda的主体代码像在def内的代码一样都遵循相同的作用域查找法则。
>>> f = lambda x,y,z: x+y+z
>>> f(2,3,4)
9
>>> x = lambda a='fee', b='fie', c='foe': a+b+c
>>> x('wee')
'weefiefoe'
>>> def knights():
... title = 'Sir'
... action = (lambda x: title + ' ' + x)
... return action
...
>>> act = knights()
>>> act('robin')
'Sir robin'
使用lambda的原因:起到函数速写的作用,可以利用列表、字典或者其他数据结构来构建行为表
>>> L = [lambda x:x**2, lambda x: x**3, lambda x: x**4]
>>> print(L[0](3))
9
>>> key = 'got'
>>> {'already':(lambda: 2+2),
... 'got': (lambda: 2*4),
... 'one':(lambda: 2**6)}[key]()
8
当python创建这个字典的时候,每个嵌套的lambda都会生成并留下一个在之后能够调用的函数,通过键索引来取回其中一个函数,而括号使取出的函数被调用。
1) 序列映射函数map
程序中常常要做的是对每一个元素进行一个操作并将结果集合起来,map能完成此功能,它会对一个序列对象中的每一个元素应用被传入的函数,并且返回一个包含所有函数调用的一个对象
>>> def inc(x): return x + 10
...
>>> counters = [1,2,3,4]
>>> list(map(inc, counters))
[11, 12, 13, 14]
2) filter和reduce
filter:基于某个函数过滤一些函数
reduce:对每对元素都应用函数并运行到最后结果。reduce传递了当前的和或乘积以及列表中的下一个元素。
>>> list(filter((lambda x: x > 0), range(-5, 5)))
[1, 2, 3, 4]
>>> from functools import reduce
>>> reduce((lambda x, y: x + y),[1,2,3,4])
10