1.懒惰即美德
不要在每次需要的时候都把代码重写一遍
2.抽象和结构
3.创建函数
内建函数callable函数可用来判断函数是否可调用
>>>import math
>>> x = 1
>>> y = math.sqrt
>>> callable(x) False
>>> callable(y) True
函数callable在python3.0中不再使用,需要使用表达式hasattr(func,__call__)代替
创建函数
def hello(name):
return 'Hello, ' + name + '!'
返回斐波那契数列列表
def fibs(num):
result = [0, 1]
for i in range(num-2):
result.append(result[-2] + result[-1])
return result
>>> fibs(10) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
3.1记录函数
为函数添加文档字符串:
def square(x):
'Calculates the square of the number x.'
return x*x
文档字符串访问
>>> square.__doc__
'Calculates the square of the number x.'
内建help函数,得到关于函数包括文档字符串的信息:
>>> help(square)
Help on function square in module __main__:
square(x)
Calculates the square of the number x.
3.2并非真正函数的函数
没有return语句或者return语句后面没有返回值
只起到结束函数的作用
函数无返回值时,就返回None
4.参数魔法
4.1
形参,写在def语句函数名后面的变量
实参,调用函数时提供的值,是实际参数
4.2参数改变
4.2.1字符串
在函数内为参数赋予新值不会改变外部任何变量的值,
>>> def try_to_change(n):
n = 'Mr. Gumby'
>>> name = 'Mrs. Entity'
>>> try_to_change(name)
>>> name 'Mrs. Entity'
具体工作方式为
n = name
n = 'Mr. Gumby'
字符串(数字和元组)是不可改变的,即无法被修改(只能通过新值覆盖)
4.2.2将可变的数据结构如列表用在参数
>>> def change(n):
n[0] = 'Mr. Gumby'
>>> names = ['Mrs. Entity', 'Mrs. Thing' ]
>>> change(names)
>>> names
['Mr. Gumby', 'Mrs. Thing']
参数被改变了,
>>> n = names
>>> n[0] = 'Mr. Gumby'
当两个变量同时引用一个列表时,他们的确同时引用一个列表
避免这种情况,可以复制一个列表的副本,当在序列中做切片的时候,返回的切片总是一个副本。
>>> names = ['Mrs. Entity', 'Mrs. Thing' ]
>>> n = names[:]
现在n和name包含两个独立(不同)的列表,其值相等
>>> n is names False
>>> n == names True
现在改变n则不会影响到names
>>> change(names[;])
4.2.3为什么修改参数
使用函数改变数据结构是将程序抽象化的好方法,假设需要编写一个存储名字并能用名字,中间名或姓查找联系人的程序,可以使用如下数据结构:
storage = {}
storage['first'] = {}
storage['middle'] = {}
storage['last'] = {}
storage这个数据结构存储方式是带有3个键,’first','middle'和‘last’的字典,每个键下面又是一个字典
子字典可以使用名字(名字,中间名或姓)作为键,插入联系人列表作为值,比如要把我的名字加入这个数据结构:
>>> me = 'Magnus Lie Hetland'
>>> storage['first']['Magnus'] = [me]
>>> stroage['middle']['Lie'] = [me]
>>> storage['last']['Hetland'] = [me]
每个键下面都存储了一个以人名组成的列表,本例中,列表中只有我。
先如果想得到所有注册的中间名为lie的人,可如下
>>> storage['middle']['Lie']
['Magnus Lie Hetland']
加入我姐姐的名字,而且假设不知道数据库中已经存储了什么
>>> my_syster = 'Anne Lie Hetlan'
>>> storage['first'].setdefault('Anne'.[]).append(my_syster)
>>> storage['middle'].setdefault('Lie'.[]).append(my_syster)
>>> storage['last'].setdefault('Hetland'.[]).append(my_syster)
>>> storage['first']['Anne']
['Anne Lie Hetland']
>>> storage['middle']['Lie']
['Magnus Lie Hetland', 'Anne Lie Hetland']
抽象的要点就是隐藏更新时的繁琐的细节,这个过程可以用函数实现。
初始化数据结构的函数:
def init(data):
data['first'] = {}
data['middle'] = {}
data['last'] = {}
上面的代码将初始化语句放到了函数中,使用方法如下:
>>> storage = {}
>>> init(storage)
>>> storage
{'middle': {}, 'last': {}, 'first: {} )
字典的键没有具体的顺序,所以当字典打印出来时顺序是不同的
在编写存储名字的函数前,先写个获得名字的函数:
def lookup(data, label, name):
return data[label].get(name)
标签(比如‘middle')以及名字(比如’Lie')可以作为参数提供给lookup函数使用,这样会获得包含全名的列表
换句话说,如果我的名字已经储存了,可以像下面这样做:
>>> lookup(storage, 'middle', 'Lie')
['Magnus Lie Hetland']
返回的列表和存储在数据结构中的列表是相同的,所以如果列表被修改了,那么也会影响数据结构(没有查询到人的时候就问题不大,因为函数返回的是None)
def store(data.full_name):
names = full_name.split()
if len(names) == 2: names.insert(1, '')
labels = 'first', 'middle', 'last'
for label.name in zip(labels, names):
people = lookup(data, label, name)
if people:
people.append(full_name)
else:
data[label][name] = [full_name]
store函数执行以下步骤:
使用参数data和full_name进入函数,这两个参数被设置为函数在外部获得的一些值
通过拆分full_name,得到一个叫做names的列表
如果names的长度为2,(只有首名和末名),那么插入一个空字符串作为中间名
将字符串"first', 'middle'和'last'作为元组存储在labels中(也可以使用列表)
使用zip函数联合标签和名字,对于每一个(label, name)对,进行以下处理:
获得属于给定标签和名字的列表,将full_name添加到列表中,或者插入一个需要的新列表
试用一下程序:
>>> MyNames = {}
>>> init(MyNames)
>>> store(MyNames, 'Magnus Lie Hetland')
>>> lookup(MyNames, 'middle', 'Lie')
['Magnus Lie Hetland']
再试试
>>> store(MyNames, 'Robin Hood')
>>> store(MyNames, 'Robin Locksley')
>>> store(MyNames, 'first', 'Robin')
['Robin Hood', 'Robin Licksley']
>>> store(MyNames, 'Mr. Gumby')
>>> store(MyNames, 'middle', ' ' )
['Robin Hood', 'Robin Locksley', 'Mr. Gumby']
4.2.4参数不改变
在某些语言中(如C++),重绑定参数并使这些改变影响到函数外的变量是很平常的事,但在python中是不可能的,
函数只能修改参数对象本身,但是如果参数不可变---如数字,怎么办?
应该从函数中返回所有需要的值(如果值多于一个的话就以元组形式返回),
例如将变量的数值增1的函数:
>>> 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]
4.3关键字参数和默认值
目前位置所用的参数都是位置参数,因为它们的位置很重要,事实上比名字更重要,但是很难记住
使用参数名提供的参数叫做关键字参数,主要作用在于可以明确每个参数的作用,
def hello_1(greeting, name):
print '%s, %s!', %(greeting, name)
def hello_2(name, greeting):
print '%s, %s!', %(name, greeting)
>>> hello_1('Hello', 'world') Hello, world!
>>> hello_2('Hello', 'world') Hello. world!
提供参数的名字
>>> 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!
如果只提供name参数,
>>> hello_3(name='Gumby') Hello, Gumby!
位置和关键字参数可以联合使用,把位置参数放置在前面即可。
但是除非完全清楚程序的功能和参数意义,否则应避免混合使用。
4.4收集参数
存储多个名字,位置参数,返回元组*
>>> storage(data, name1, name2, name3)
def print_params(*params):
print params
>>> 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)
星号的意思就是收集其余的位置参数,如果不提供任何供收集的元素,params就是个空元组
>>> print_params_2('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 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}
>>> print_params_4(1,2)
123
()
{}
怎样实现多个名字同时存储:
def store(data, *full_names):
for full_name in full_names:
names full_name.split()
if len(names) == 2: names.insert(1, '')
labels = 'first', 'middle', 'last'
for label.name in zip(labels, names):
people = lookup(data, label, name)
if people:
people.append(full_name)
else:
data[label][name] = [full_name]
使用
>>> d = {}
>>> init(d)
>>> store(d, 'Han Solo')
多个
>>> store(d, 'Luke Skywalker'. 'Anakin Skywalker')
>>> lookup(d, 'last', 'Skywalker')
['Luke Skywalker', 'Anakin Skywalker']
4.5反转过程
在调用而不是定义时使用,
def add(x, y) : return x + y
params = (1, 2)
>>> add(*params) 3
对于参数列表来说工作正常,只要扩展部分是最新的就可以
使用双星号运算符来处理字典
>>> params = {'name' : 'Sir Robin', 'greeting' : 'Well met' }
>>> hello_3(**params) Well met, Sir Robin!
在定义或者调用函数时使用星号(或双星号)仅传递元组或字典,
>>> def with_stars(**kwds):
print kwds['name'], 'is', kwds['age'], 'years old'
>>> def withour_stars(kwds):
print kwds['name'], 'is', kwds['age'], 'years old'
>>> args = {'name': 'Mr.Gumby', 'age': 42}
>>> with_stars(**args) Mr.Gumby is 42 years old
>>> without_stars(args) Mr.Gumby is 42 years old
如上例,效果相同,所以星号只在定义函数(允许使用不定数目的参数)或者调用(‘分割’字典或者序列)时才有用
提示,使用拼接(splicing)操作符‘传递’参数很有用,因为不用关心参数个数等问题,如
def foo(x, y, z, m=0, n=0):
print x, y, z, m, n
def call_foo(*args, **kwds):
print "Calling foo!"
foo(*args, **kwds)
4.6练习使用参数
5.作用域
内建的vars函数将变量和值映射为不可见的字典,变量是值的名字,
>>> x = 1
>>> scope = vars()
>>> scope['x'] 1
>>> scope['x'] += 1
>>> x 2
一般来说vars返回的字典是不能修改的,因为官方文档说法,结果是未定义的,可能得不到想要的结果
这类不可见的字典叫做命名空间或者作用域,
除了全局作用域外,每个函数调用都会创建一个新的作用域
函数内的变量为局部变量
慎用全局变量,当参数名相同时,全局变量会被局部变量屏蔽
重新绑定全局变量(使变量引用其他新值)
如果在函数内部将值赋予一个变量,它会自动成为局部变量,除非告知python将其声明为全局变量,
>>> x = 1
>>> def change_global():
global x
x = x + 1
>>> change_global()
>>> x 2
嵌套作用域,python的函数是可以嵌套的,将一个函数放在另一个里面
def multiplier(factor):
def multiplyByFactor(number):
return number* factor
return multiplyByFactor
外层函数返回里层函数,也就是函数本身被返回了,但并没有被调用(返回的函数还可以访问它的定义所在的作用域),
即它“带着”它的环境和相关的局部变量
每次调用外层函数,内部的函数都被重新绑定,factor变量每次都有一个新值,
由于python的嵌套作用域,来自外部作用域的这个变量稍后会被内层函数访问。
>>> double = multiplier(2)
>>> double(5) 10
>>> multiplier(5)(4) 20
类似multiplyByFactor函数存储子封闭作用域的行为叫做闭包
外部作用域的变量一般来说都是不能进行重新绑定的,但在python3.0中,nonlocal关键字被引入。
它和global关键字使用方式类似,可以让用户对外部作用域(但非全局作用域)的变量进行赋值
6.递归
有用的递归函数包含以下几部分:
当函数直接返回值时有基本实例(最小可能性问题);递归实例,包括一个或多个问题最小部分的递归调用
6.1经典:阶乘和幂
计算数n的阶乘
def factorial(n):
if n == 1:
return 1
else :
return n * factorial(n-1)
计算幂,pow(2,3) 2*2*2
def power(x, n):
result = 1
for i in range(n):
result *= x
return result
递归版本
def power(x, n):
if n == 0:
return 1
else:
return x*power(x, n-1)
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)
>>> seq = [34, 67, 8, 123, 4, 100, 95]
>>> seq.sort()
>>> seq [4, 8,34, 57. 95, 100, 123]
>>> search(seq, 34) 2
>>> search(seq, 100) 5
函数式编程(map, filter, reduce函数)
map函数将序列中的元素全部传递给一个函数
>>> map(str, range(10)) ['0', '1', '2'. '3'. '4'. '5'. '6', '7', '8', '9']
filter函数可以基于一个返回布尔值的函数对元素进行过滤
>>> def func(x):
return x.isalnum()
>>> seq = ["foo", "x41", "?!", "***"]
>>> filter(func, seq) ['foo', 'x41']
本例中,使用列表推导式可以不用专门定义一个函数
>>> [ x for x in seq if x. isalnum()] ['foo', 'x41']
还有一个lambda表达式,可创建短小的函数
>>> filter(lambda x : x.isalnum(), seq) ['foo', 'x41']
reduce函数一般不能轻松被列表推导式代替,但通常用不到这个功能,它会将序列的前两个元素与给定的函数联合使用,
并且将他们的返回值和第3个元素继续联合使用,直到整个序列处理完毕,并得到一个最终结果。
例如,需计算一个序列的数字的和,可以使用reduce函数加上lambda x. y: x+y
>>> numbers = [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]
>>> reduce(lamda x, y : x + y, numbers) 1161
也可使用内建函数sum
7.小结
抽象,抽象是隐藏多余细节的艺术,定义处理细节的函数可以让程序更抽象
函数定义,函数使用def语句定义,他们是由语句组成的块,可以从外部世界获取值(参数),也可以返回一个或多个值作为运算的结果;
参数,函数从参数中得到需要的信息,也就是函数调用时设定的变量,python中有两类参数:位置参数和关键字参数,参数在给定默认值时是可选的
作用域,变量存在作用域中(命名空间),全局好局部作用域,作用域可以嵌套
递归,函数可以调用自身,如果他这么做了就是递归,一切用递归实现的功能都可以用循环实现,但是有时候递归函数更易读
函数型编程,包括lambda表达式以及map, filter, reduce函数