第6章 抽象
本章会介绍如何将代码组织成函数,并会详细介绍函数的参数、作用域,以及递归在程序中的用途。
懒惰即美德(略)
抽象和结构(略)
创建函数
如下是一个简单的定义函数的例子:
def hello(name):
print "Hello,", name, "!"
记录函数
如果在函数的开头写下字符串,字符串会被当作函数的一部分进行存储:
def square(x):
'Calculate the square of x'
return x*x
文档字符串可以按照如下方式访问:
square.__doc__
内建函数help在命令行中非常有用,通过如下方式就可以得到关于函数的具体信息:
help(square)
无返回值的函数
虽然没有现实return内容,但是所有函数都返回了东西,当不需要它们返回值的时候它们返回None。
参数详解
值从哪里来
定义函数时候的参数叫做形式参数,调用函数时的参数叫做实际参数。
函数封装的例子
def try_to_change(n):
n = 'Hello, Entity!'
name = 'Hello, Gumby!'
try_to_change(name)
print name
$ python hello.py
Hello, Gumby!
看如上例子,我们发现,调用try_to_change后,值在内部发生了改变,但是并没有影响到外部的name。那么再看下面这个例子:
def change(n):
n[0] = 'Mr.Gumby'
names = ['Mr.Entity', 'Mrs.Lucy']
change(names)
print names
$ python hello.py
['Mr.Gumby', 'Mrs.Lucy']
这里我们可以看到,列表在函数内的改变对外面的列表产生了影响,和前一个例子的区别就在于:第一个例子中,字符串是不可变的,函数内部进行赋值表示指向了一个新的字符串,而第二个例子则并没有改变之前的列表对象,只是改变之前列表的其中一个元素。如果要改变列表你可以直接将列表指向新的对象或者干脆指向None试试:
def change(n):
n = None
names = ['Mr.Entity', 'Mrs.Lucy']
change(names)
print names
$ python hello.py
['Mr.Entity', 'Mrs.Lucy']
如下用例子告诉你:将代码抽象函数的好处(个人觉得例子还不错,费些周章看看最好手动实现一下还是值得的)。
现在嘉定要写一个根据firstname、middlename、lastname来查找联系人完整名字的程序。复习下前面学习到的知识,用最原始的方法构建一个程序:
contact_book = {}
contact_book["first"] = {}
contact_book["middle"] = {}
contact_book["last"] = {}
me = "Magnus Lie Hetland"
contact_book["first"]["Magnus"] = me
contact_book["middle"]["Lie"] = me
contact_book["last"]["Hetland"] = me
print contact_book['middle']['Lie']
$ python hello.py
Magnus Lie Hetland
如上代码可以实现功能,但是当需要添加多个用户到contact_book的时候,代码的冗长可想而知。
我们可以对所涉及的步骤进行简单的抽象:
contact_book = {} #定义姓名字典
init(contact_book) // #初始化结构
store(contact_book, "Magnus Lie Hetland") #存储单姓名记录
lookup(contact_book, 'middle', 'Lie') //查询符合条件的记录
如下是对关键代码的实现:
def init(book):
book['first'] = {}
book['middle'] = {}
book['last'] = {}
def lookup(book, label, value):
return book[label].get(value)
def store(book, full_name):
names = full_name.split(' ')
if len(names) == 2: names.append(1, ' ')
labels = ['first', 'middle', 'last']
for name, label in zip(names, labels):
people = lookup(book, label, name)
if people:
people.append(full_name)
else:
book[label][name] = [full_name]
contact_book = {}
init(contact_book)
store(contact_book, "Magnus Lie Hetland")
store(contact_book, "Magnus Baz Hetland")
print lookup(contact_book, 'middle', 'Lie')
print lookup(contact_book, 'first', 'Magnus')
$ python hello.py
['Magnus Lie Hetland']
['Magnus Lie Hetland', 'Magnus Baz Hetland']
上述代码实现过程中可能有两点需要注意的,一个是book[label].get(value)直接写作book[label][value]是会报错的,因为可能键不存在;第二个是zip的运用,在构思程序的时候,需要比较名字的各个部分在book['first']、book['middle']、book['last']是否存在,自己的代码实现可能会很丑陋,使用zip使这一处理变得简单了。
原书在该部分讨论的一个重点是,为什么要改变参数,如上例我们大概已经知道了,某些数据结构就是用来存储和被改变的;另外一些时候,传递进来的参数是不能改变的,这时候我们可以构建满足需求的结果返回出去即可。
关键字参数和默认值
前面一小节的例子中我们使用参数都是和位置关联的,调用的时候不能错乱了位置。Python还提供一种称作关键字参数的用法,除了不用记忆参数顺序外,还可以设置默认值:
def hello(greeting="Hello", name="World"):
print greeting + ',', name
hello()
hello(name='MingZe')
hello(name="MingZe", greeting="Nice to meet you")
$ python hello.py
Hello, World
Hello, MingZe
Nice to meet you, MingZe
收集参数
可以让用户提供任意数量的参数(所谓联合参数)也是Python函数的一个功能,看如下示例:
def print_params(*params):
print params
print_params()
print_params("Hello")
print_params(1, 2, 3)
$ python hello.py
()
('Hello',)
(1, 2, 3)
还可以将普通的参数和联合参数混合使用:
def print_params(title, *params):
print title, params
print_params("Params:", 1, 2, 3)
$ python hello.py
Params: (1, 2, 3)
要将关键字参数(key=value的形式)也收录到params中,需要使用**params
,注意返回的结果是字典:
$ python hello.py
{'y': 2, 'x': 1, 'z': 3}
将几种参数方式混合起来使用:
def print_params(x, y=3, z=4, *pospar, **keypar):
print x, y, z
print pospar
print keypar
print_params(1, 2, 3, 4, 5, 6, 7, foo=1, bar = 2)
$ python hello.py
1 2 3
(4, 5, 6, 7)
{'foo': 1, 'bar': 2}
学习过了联合参数的使用以后,再回到上面想过的存储个人姓名的程序,就可以通过 *full_names 来一次存储多个姓名了。
反转过程
在上节中我们在函数定义中使用了 *params,然后实际调用的时候传递多个参数,比如1,2,3,参数会被收集到params中; **params和x=1, y=2同理。
反过来看,如果函数定义的是如下形式 def func(x,y),那么调用的时候就可以通过 params=(x, y) func(params), 同样对于关键字参数也是如此,定义def func(params), 调用params={x:1,y:2} func(*params),如下例所示:
def foo(x, y, z, m=0, n=0):
print x, y, z, m, n
args = (1,2,3)
kwds = {"m":4, "n": 5}
foo(*args, **kwds)
$ python hello.py
1 2 3 4 5
练习使用参数
该书使用了一个综合性稍强的练习题,任务中自己多体味吧。
作用域
我们通过执行形如x=1的赋值语句后,就能通过过x直接访问到值是因为python中做了形如 scope['x'] = 1的操作,这里的scope就是作用域。
作用于分为全局作用域和局部作用域,每个执行函数内部都创建一个局部作用域,看如下例子:
def foo(n):
n = 42
num = 1
foo(num)
print num
$ python hello.py
1
在函数中我们修改了参数的值,但在函数外面进行访问却仍旧是原来的值,这是因为n作为数字类型,会在函数内被新创建为局部作用域内的值,跟外部传入的不是同一个。
如果要对值进行操作,只要将修改后的值作为函数的返回值返回即可。
在函数内想要访问全局作用域内的值也很简单:
external = 'Brozu'
def foo(param):
return param + external
print foo("Shayne")
$ python hello.py
ShayneBrozu
递归
递归简单地说就是函数调用自身的程序结构,通常用来解决了一些能够由大化小,规律一致的问题。
两个经典:阶乘和幂
n的阶乘数学表示为 n * (n - 1) * (n - 2) ... * 2 * 1, 函数递归的表现形式如下:
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n - 1)
print factorial(10)
$ python hello.py
3628800
另一个常用的例子是幂,我们要计算x的n次幂可能会是这样的:
def power(x, n):
if n == 0:
return 1
else:
return x * power(x, n - 1)
print power(10, 3)
$ python hello.py
1000
当然你也可以用循环实现上述代码,甚至效率更加高,但是编程很重要一点是要追求代码的易读,尤其当程序代码量大的时候尤其重要。
另一个经典:二元查找
假定要你查找某个数字是否在某序列中,你可以通过不断取中间值来和查找值进行比较来得到最终的结果(当然要先排序)。如下是一个实现例子:
def search(sequence, number, left, right):
midd = (left + right) / 2
if left == right:
assert number == sequence[midd]
return midd
elif sequence[midd] > number:
return search(sequence, number, left, midd - 1)
else:
return search(sequence, number, midd, right)
seq = [34, 67, 15, 28, 89, 44, 9, 66]
seq.sort()
print search(seq, 67, 0, len(seq))
$ python hello.py
6