6.1 懒惰即美德
- 不需要在每次需要代码的时候把代码重写一遍,真正的程序员会让自己的程序更抽象一点
6.2 抽象和结构
- 抽象可以节省很多工作,计算机乐于处理精确和具体的指令,但人只需要抽象的指导。组织计算机程序也是类似的,程序应该是非常抽象的。
6.3 创建函数
- 函数是可以调用,它执行某种行为并返回一个值,一般来说,内建的callable函数可以用来判断函数是否可以调用。
>>>import math
>>>x = 1
>>>y = math.sqrt
>>>callable(x)
False
>>>callable(y)
True
可以使用def语句定义函数。
def hello(name):
return 'Hello, ' + name + '!'
然后可以像使用内建函数一样使用它:
>>>print hello('world')
Hello, world!
>>>print hello('Gumby')
Hello, Gumby!
6.3.1 记录函数
- 如果想要给函数写文档,让后面使用该函数的人能理解的话,可以加入注释。另外一个方式就是直接写上字符串,例如在def语句后面,在模块或者类的开头。如果在函数的开头写下字符串,它就会作为函数的一部分存储,这称为文档字符串。
def sequare(x):
'Calculates the sequare of the number x.'
return x*x
>>>print sequare.__doc__
Calculates the sequare of the number x.
>>>help(sequare)
Help on function sequare in module __main__:
sequare(x)
Calculates the sequare of x
6.3.2 并非真正函数的函数
- python的有些函数不返回任何东西,可以没有return语句,或者虽有return语句,但不返回值。
def test():
print 'This is printed'
return
print 'This is not'
这里的return语句只起到结束语句的作用
>>>x = test()
This is printed
>>>x
>>>print x
None
6.4 参数魔法
6.4.1 值从哪里来
- 编写函数只是给程序需要的部分提供服务,能保证函数在被提供给可接受参数的时候正常工作就行,参数错误的话显然会导致失败。
写在def语句中函数名后面的变量通常叫做函数的形式参数,而调用函数的时候提供的值是实际参数,或者称为参数。
6.4.2 我能改变参数吗
- 在函数内为参数赋予新值不会改变外部任何变量的值:
>>>def try_to_change(n):
n = 'Mr. Gumby'
>>>name = 'Mrs. Entity'
>>>try_to_change(name)
>>>name
'Mrs. Entity'
- 和下面的例子比较
>>>def change(n):
n[0] = 'Mr. Gumby'
>>>names = ['Mrs. Entity', 'Mrs. Thing']
>>>change(names)
>>>names
['Mr. Gumby', 'Mrs. Thing']
当两个变量同时引用一个列表的时候,他们的确是同时引用一个列表。为了避免这种情况们可以赋值一个列表的副本,当在序列中做切片的时候,返回的切片总是一个副本。
>>>names = ['Mrs. Entity', 'Mrs. Thing']
>>>n = name[:]
>>> n is names
False
>>>n == names
True
如果现在改变n,则不会影响到names。现在的参数n包含一个副本,而原始的列表是安全的。
1. 为什么我想要修改参数
- 假设需要编写一个存储名字并且能用名字、中间名、或姓查找联系人的程序,可以使用下面的数据结构
storage = {}
storage['first'] = {}
storage['second'] = {}
storage['last'] = {}
storage这个数据结构的存储方式是带有三个键“first”、“middle”、“last”的字典,每个键下面又存储一个字典。子字典中可以使用名字作为键,插入联系人列表作为值。
>>>me = 'Magnus Lie Hetland'
>>>storage['first']['Magnus'] = [me]
>>>storage['smiddle']['Lie'] = [me]
>>>storage['last']['Hetland'] = [me]
每个键下面存储了以人名组成的列表,本例中,列表中只有我。
如果想要得到所有注册的中间名为Lie的人,可以这样做:
>>>storage['middle']['Lie']
['Magnus Lie Hetland']
把人名加入列表中的步骤有些枯燥乏味,例如:
>>>my_sister = 'Anne Lie Hetland'
>>>storage['first'].setdefault('Anne', []).append(my_sister)
>>>storage['middle'].setdefault('Lie', []).append(my_sister)
>>>storage['last'].setdefault('Hetland', []).append(my_sister)
抽象的要点就是隐藏更新时繁琐的细节,这个过程可以用函数实现,下面的例子就是初始化数据结构的函数
def init(data):
data['first'] = {}
data['middle'] = {}
data['last'] = {}
在编写存储名字的函数前,先写个获得名字的函数
def lookup(data, label, name):
return data[label].get(name)
def store(data, full_name):
names = full_name.split()
if len(names) == 2: names.insert(1,'')
labels = 'first', 'middle', 'last'
for labels, name in zip(labels, names):
people = lookup(data, label, name):
if people:
people.append(full_name)
else:
data[label][name] = [full_name]
2. 如果我的参数不可变呢
在某些语言中,重绑定参数并且使这些改变影响到函数外的变量是很平常的事情,但在python中函数只能修改参数对象本身。如果想要修改不可变的参数,只能从函数中返回所有你需要的值。
>>>def inc(x): return x+1
>>>foo = 10
>>>foo = inc(foo)
>>>foo
11
也可以使用小技巧,把值放在列表中
>>>def inc(x): x[0] = x[0] + 1
>>>foo = [10]
>>>inc(foo)
>>>foo
[11]
6.4.3 关键字参数和默认值
- 到目前使用的参数都叫做位置参数,因为它们的位置很重要,本节中引入的这个功能能够回避位置问题。考虑下面的两个函数
def hello_1(greeting, name):
print '%s, %s!' % (greeting, name)
def hello_2(name, greeting):
print '%s, %s!' % (greeting, name)
可以提供参数的名字
>>>hello_1(greeting = 'Hello', name = 'world')
Hello, world!
>>>hello_1(name = 'world', greeting = 'Hello')
Hello, world!
这种使用参数名提供的参数叫做关键字参数,它的主要作用在于可以明确每个参数的作用。关键字参数最厉害的地方在于可以在函数中给参数提供默认值。
def hello_3(greeting = 'Hello', name = 'world'):
print '%s, %s!' % (greeting, name)
>>>hello_3()
Hello, world!
>>>hello_3('Greetings')
Greetings, world!
>>>hello_3('Greetings', 'universe')
Greetings, universe!
6.4.4 收集参数
有些时候让用户提供任意数量的参数是很有用的,比如在名字存储程序中,每次只能存一个名字。用户可以给函数提供任意多的参数,实现起来也不难。
def print_params(*params):
print params
调用看看
>>>print_params('Testing')
('Testing',)
>>>print_params(1,2,3)
(1,2,3)
def print_params_2(title, *params):
print title
print params
>>>print_params_2('Params:', 1, 2, 3)
Params:
(1,2,3)
>>>print_params_3('Nothing:')
Nothing:
()
处理关键字参数的“收集”操作为“*”
def print_params_3(**params):
print params
>>>print_params_3(x=1, y=2, z=3)
{'z': 3, 'x': 1, 'y':2}
def_params_4(x, y, z=3, *pospar, **keypar):
print x, y, z
print pospar
print keypar
>>>print_params_4(1, 2, 3, 4, 5, 6, 7, foo=1, bar=2)
1, 2, 3
(4, 5, 6, 7)
{'foo': 1, 'bar': 2}
>>>print_params_4(1,2)
1, 2, 3
()
{}
6.4.5 反转过程
- 在调用过程中使用和*可以实现和上述相反的操作
def add(x, y): return x + y
>>>params = (1, 2)
>>>add(*params)
3
6.3 作用域
在执行x=1赋值语句后,名称x引用到值1,这就像用字典一样,当然变量和所对应的值用的是个不可见的字典。内建的vars()函数可以返回这个字典。
>>>x = 1
>>>scope = vars()
>>>scope['x']
1
>>>scope['x'] += 1
>>>x
2
这类不可见字典叫做命名空间或者命名域,除了全局作用域外,每个函数调用都会创建一个新的作用域:
>>>def foo(): x =42
...
>>>x = 1
>>>foo()
>>>x
1
函数内的变量称为局部变量,参数的工作原理类似于局部变量,所以用全局变量的名字作为参数名并没有问题。
>>>def output(x): print x
...
>>>x = 1
>>>y = 2
>>>output(y)
2
也可以在函数内读取全局变量,但是当局部变量或者参数的名字和想要访问的全局变量名相同的话,就不能直接访问了,全局变量会被局部变量屏蔽。可以使用globals函数获取全局变量值,该函数的近亲是vars,它可以返回全局变量的字典。
>>>def combine(parameter):
print parameter + globals()['parameter']
...
>>>parameter = 'berry'
>>>combine('Shrub')
Shrubberry
重绑定全局变量
>>>x = 1
>>>def change_global():
global x
x = x + 1
>>>change_global()
>>>x
2
6.6 递归
6.6.1 两个经典:阶乘和幂
def factorial(n):
result = n
for i in range(1,n):
result *= i
return result
def factorial(n):
if n == 1:
reutrn 1
else:
return n * factorial(n-1)
6.6.2 二元查找
def search(sequence, number, lower, upper):
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)
6.7 小结
- 抽象:隐藏多余细节
- 函数定义:使用def语句定义,它们是由语句组成的块,可以从外部世界获取值
- 参数:函数从参数中得到需要的信息,也就是函数调用时设定的变量。python有两类参数,位置参数和关键字参数,参数在给定默认值时是可选的。
- 作用域
- 递归
- 函数型编程