第6章 抽象
6.3 创建函数
6.3.1 记录函数
如果在函数的开头写下字符串,它就会作为函数的一部分进行存储,称为“文档字符串”。
如下代码演示了如何给函数添加文档字符串:
def fibs(num):
'i am a fib function'
result = [0, 1]
for i in range(num-2):
result.append(result[-2] + result[-1])
return result
文档字符串可以按如下方式访问:
>>> fibs.__doc__
'i am a fib function'
注意 `__doc__`是函数属性。
内建的help函数是非常有用的。在交互式解释器中国使用它,可以得到关于函数,包括它的文档字符串的信息:
>>> help(fibs)
Help on function fibs in module __main__:
fibs(num)
i am a fib function
6.4 参数魔法
6.4.2 我能改变参数吗
在函数内为参数赋予新值,不会改变任何外部变量的值:
>>> def try_to_change(n):
... n = 'gumby'
...
>>> name = 'henry'
>>> try_to_change(name)
>>> name
'henry'
在try_to_change内,参数n获得了新值,但是它没有影响到name变量。n实际上是个完全不同的变量,具体的工作方式类似于下面:
>>> name = 'henry'
>>> n = name
>>> n = 'gumby'
>>> name
'henry'
当变量n改变的时候,变量name不变。
同样,当在函数内部把参数重绑定(赋值)的时候,函数外的变量不会受到影响。
注意 参数存储在局部作用域(local scope)内。
字符串(以及数字和元组)是不可变的,即无法修改(也就是说只能用新的值覆盖)。
1. 为什么我想修改参数
抽象的要点就是隐藏更新时的繁琐的细节,这个过程可以用函数实现。
下面的例子就是初始化数据结构的函数:
>>> def init(data):
... data['first'] = {}
... data['middle'] = {}
... data['last'] = {}
上面的代码只是把初始化语句放到了函数中,使用方法如下:
>>> storage = {}
>>> init(storage)
>>> storage
{'middle': {}, 'last': {}, 'first': {}}
可以看到,函数包办了初始化的工作,让代码更易读。
注意 字典的键并没有具体的顺序,所以当字典打印出来的时候,顺序是不同的。
如果我的参数不可变呢
函数只能修改参数对象本身。
6.4.4 收集参数
*的意思是“收集其余的位置参数”。
>>> def print_params_2(title, *params):
... print title
... print params
...
>>> print_params_2('Params:', 1, 2, 3)
Params:
(1, 2, 3)
但是,不能处理关键字参数:
>>> print_params_2('...', something=42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: print_params_2() got an unexpected keyword argument 'something'
所以,我们需要一个能处理关键字参数的“收集”操作。
>>> def print_params_4(x, y, z=3, *pospar, **keypar):
... print x, y, z
... print pospar
... print keypar
...
>>> print_params_4(1, 2, 3, 5, 6, 7, foo=1, bar=2)
1 2 3
(5, 6, 7)
{'foo': 1, 'bar': 2}
6.5 作用域
可以把变量看作是值的名字。在执行x=1赋值语句后,名称x引用到值1。这就像用字典一样,键引用值。变量和所对应的值用的是个“不可见”的字典。实际上这么说已经很接近真实的情况了。内建的vars函数可以返回这个字典:
>>> x = 1
>>> vars()
{'me': 'magnus lie hetland', 'x': 1, 'name': 'henry', 'power': <function power at 0x7fbb03b94b90>, '__builtins__': <module '__builtin__' (built-in)>, 'storage': {'middle': {}, 'last': {}, 'first': {}}, 'try_to_change': <function try_to_change at 0x7fbb03b94938>, '__package__': None, 'init': <function init at 0x7fbb03b949b0>, 'lookup': <function lookup at 0x7fbb03b94a28>, 'print_params_4': <function print_params_4 at 0x7fbb03b94b18>, '__name__': '__main__', 'n': 'gumby', '__doc__': None, 'print_params_2': <function print_params_2 at 0x7fbb03b94aa0>}
>>> vars()['x']
1
>>> scope = vars()
>>> scope['x']
1
>>> scope['x'] += 1
>>> x
2
警告 一般来说,vars所返回的字典是不能修改的,因为根据官方的Python文档的说法,结果是未定义的。换句话说,可能得不到想要的结果。
这类“不可见字典”叫做命名空间或者作用域。那么到底有多少个命名空间?除了全局作用域外,每个函数调用都会创建一个新的作用域:
嵌套作用域
闭包(closure)
6.6 递归
6.6.2 另外一个经典:二元查找
实现一个二分查找算法:
# biSearchDemo.py
def search(sequence, number, lower, upper=None):
if upper is None:
upper = len(sequence) - 1
if lower == upper:
assert number == sequence[upper]
return upper
else:
middle = (lower + upper) // 2
if number > sequence[middle]:
return search(sequence, number, middle+1, upper)
else:
return search(sequence, number, lower, middle)
提示 标准库中的bisect模块可以非常有效地实现二元查找。
参考文献:
1.《Pytho基础教程(第2版·修订版)》。