第06章 抽象
本章介绍如何将语句组织成函数,这样,可以告诉计算机如何做事。
懒惰即美德
假如要计算斐波那契数列(任何一个数是前两数之和的数字序列)
- >>> fibs=[0,1]
- >>> for i in range(8):fibs.append(fibs[-2]+fibs[-1]) #fibs[-2]+fibs[-1]后两位数,append往后添加
- #运行后,包含10个斐波那契数列的10个数字是
- >>> fibs
- [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
如果允许用户输入,从而改变计算的值,要如何做呢?
- >>> fibs = [0,1]
- >>> num = input('Enter number here:')
- Enter number here:10
- >>> for i in range(num-2):fibs.append(fibs[-2]+fibs[-1])
- >>> fibs
- [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
如果该程序要经常用到,就应该再抽象一些,如下面:
- num = input('How many numbers do you want? ')
- print fibs(num)
程序应该是非常抽象的,就像"下载页面、计算频率,打印单词频率"一样易懂.
事实上我们现在就能把这段描述翻译成Python
- page = download_page()
- freqs = compute_frequencies(page)
- for word,freq in freqs:
- print word, freq
创建函数
函数可以调用,它执行某种行为并返回值。
一般来说,内建的callable函数可以判断函数是否可调用:
- >>> import math
- >>> x = 1
- >>> y = math.sqrt
- >>> callable(x)
- False
- >>> callable(y)
- True
Note:callbale()在Python3.0里面会用hasattr(func.__call__)来代替
创建函数是组织程序的关键,那么如何定义函数呢? 下面就是个最简单的函数
- >>> def hello(name): return 'Hello,' + name + '!'
- >>> hello('Jerry')
- 'Hello,Jerry!'
像上面的斐波那契要写成函数的话,就方便多了,可以传入任意数字
def fibs(num):
result = [0,1]
for i in range(num-2):
result.append(result[-2]+result[-1])
return result
print fibs(10)
Note:
1. return 语句非常重要,是用来从函数中返回值的,如果没有的话,返回None
- >>> def sum(x,y):result = x + y
- >>> print sum(1,2)
- None
2. return 相当于程序中的break.比如说下面的:
- def test():
- print 'This is 1 line.'
- return
- print 'This is 2 line'
- test()
- #输出结果
- >>>This is 1 line.
由此可见: 第二个打印没有显示
记录函数
如果想要函数被别人理解的话,可以用#注释,另外一个直接加上字符串
如果直接放def函数后面的话,会作为函数的一部分,称为文档字符串
可以用内置的模块__doc__来查看文档
最主要,也最常用的是help(),dir()来查看函数相关信息
- >>> dir(square)
- ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
- >>> help(square)
- Help on function square in module __main__:
-
- square(x)
- Calculates the square of the number x
参数魔法
形参:def 后面的变量就是形式参数,简称形参
实参:调用函数提供的值就是实际参数,简称实参,或叫参数,或叫值.
变量定义在函数体内,为局部变量,定义在函数体内,为全局变量
- >>> def try_to_change(n):n = 'Mr, Gumby'
- >>> name = 'Mrs, Smith'
- >>> try_to_change(name) #将name作为实参传给try_to_change函数
- >>> name #全局变量值不变
- 'Mrs, Smith'
字符串及元组是不可变的,因此无法被修改,如果是可变的数据结构如列表做为参数呢?
- >>> def change(n): n[0] = 'Mr. Gumby'
- >>> name = ['Mrs. Smith','Mr. Jing']
- >>> change(name)
- >>> name #值已经发生了变化
- ['Mr. Gumby', 'Mr. Jing']
这是跟上面的区别所在,这里面的列表发生了改变:
- >>> name = ['Mrs. Smith','Mr. Jing']
- >>> n = name #模拟传参数
- >>> n[0] = 'Mr. Gumby' #改变列表
- >>> name
- ['Mr. Gumby', 'Mr. Jing']
如果不想被修改,就得拷贝其副本 n = name[:]
- >>> storage={}
- >>> storage['first']={}
- >>> storage['middle']={}
- >>> storage['last']={}
- >>> storage
- {'middle': {}, 'last': {}, 'first': {}}
#storage这种字典的存储方式,有3个键'first','middle','last'.
每个键又对应了一个字典,在子字典中,可以使用名字作为键,插入联系人列表为值.
>>> me='Magnus Lei Hetland '
- >>> storage['first']['Magus']=[me]
- >>> storage['middle']['Lei']=[me]
- >>> storage['last']['Hetland']=[me]
- >>> storage
- {
- 'middle': {'Lei': ['Magnus Lei Hetland']},
- 'last': {'Hetland': ['Magnus Lei Hetland']},
- 'first': {'Magus': ['Magnus Lei Hetland']}
- }
每个键下面都存储一个以人名的列表。本例中,列表只有我
如果要得到所有注册中间名为Lei的,可以这样:
>>> storage['middle']['Lei']
['Magnus Lei Hetland']
过程有些枯燥,如果要扩展这个数据库,且不知道里面储存了什么。
比如说:假如我姐姐的名字
- >>> my_sister='Anne Lei Hetland'
- >>> storage['first'].setdefault('Anne',[]).append(my_sister)
- >>> storage['middle'].setdefault('Lei',[]).append(my_sister)
- >>> storage['last'].setdefault('Hetland',[]).append(my_sister)
- >>> storage['first']['Anne']
- ['Anne Lei Hetland']
- >>> storage['middle']['Lei']
- ['Magnus Lei Hetland', 'Anne Lei Hetland']
如果要写大程序来更新的话,更会显得臃肿不堪.
抽象的要点就是隐藏更新时的繁琐细节,这个过程可以用函数来实现。
下面的例子就是初始化数据结构的例子:
- >>> def init(data):
- ... data['first']={}
- ... data['middle'] = {}
- ... data['last'] = {}
- ...
- >>> init(storage)
- >>> storage
- {'middle': {}, 'last': {}, 'first': {}}
可以看到,函数包办初始化的工作,让程序更易读.
如果是不可变值,比如说数字,又该如何做呢
- >>> def inc(x): return x+1
- ...
- >>> i = 10
- >>> i = inc(i)
- >>> i
- 11
如果想改变参数的话,有个小技巧,放在列表中.
- >>> def inc(x):
- ... x[0] = x[0] + 1
- ...
- >>> foo = [10]
- >>> inc(foo)
- >>> foo
- [11]
这样代码只会返回新值,比较清新。
------
关键字参数和默认值
位置参数的概念,考虑下面两个函数
- >>> 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='Hi', name='Jerry')
- Hi,Jerry!
参数名和值要对应
- >>> hello_2(name='Jerry',greeting='Hi')
- Jerry,Hi!
这类参数名提供的参数叫关键字参数。主要作用是明确每个参数的作用,
避免了下面这样奇怪的函数调用。
- store('Mr. Smith',10,20,13,5)
- store(patient='Mr. Smith',hour=10,minutes=20,day=13,month=5)
尽管多打了几个字,但一目了然,弄乱了参数的顺序,对程序不会有任何影响。
关键字参数最厉害的地方是可以跟函数提供默认值。
- >>> def hello_3(greeting='Hello',name='World'):
- ... print '%s,%s!' % (greeting,name)
- ...
- >>> hello_3() #如果不加参数的话,就用默认值
- Hello,World!
- >>> hello_3('greeting') #带参数的话,按参数顺序赋值
- greeting,World!
- >>> hello_3('greeting','universe') #按提供的顺序
- greeting,universe!
- # 如果只想提供name,而让greeting默认
- >>> hello_3(name='Sherry')
- Hello,Sherry!
位置参数和关键字参数可以联合使用,将位置参数放前面.
Note:
除非完全清楚程序的功能和参数的意义,否则应避免将位置参数和关键字参数混合使用。一般来说,只有强制要求的参数个数比可修改的具有默认值的参数个数少的时候,才使用上面提到的参数书写方式。
- >>> def hello_4(name,greeting='Hello',punctuation='!'):
- ... print '%s, %s%s' % (greeting,name,punctuation)
- ...
- >>> hello_4('Jerry')
- Hello, Jerry!
- >>> hello_4('Jerry','Howdy')
- Howdy, Jerry!
- >>> hello_4('Jerry','Howdy','...')
- Howdy, Jerry...
- >>> hello_4('Jerry',punctuation='.')
- Hello, Jerry.
- >>> hello_4('Jerry',greeting='Top of the morning to ya')
- Top of the morning to ya, Jerry!
- >>> hello_4()
- Traceback (most recent call last):
- File "<stdin>", line 1, in ?
- TypeError: hello_4() takes at least 1 argument (0 given)
#如果最后一个name也用默认值的话,就不会产生上面的异常.
收集参数
有的时候提供多参数是很有必要的,那么如何做呢? 很简单
- >>> def print_parms(*parms):
- ... print parms
- ...
#1个参数的话,会作为元祖打印出来,里面还有逗号
- >>> print_parms('Hello')
- ('Hello',)
- >>> print_parms(1,2,3)
- (1, 2, 3)
#parms前面的*号,将所有的参数放到一个元祖里面,然后使用。
那么能不能联合普通参数和收集的参数呢,当然可以。
- >>> def print_parms_2(title,*parms):
- ... print title
- ... print parms
- ...
- >>> print_parms_2('Parms:',1,2,3)
- Parms:
- (1, 2, 3)
#在这里,*变成了收集其余的位置参数
- >>> print_parms_2('Nothing:')
- Nothing:
- ()
的确如此,非常有用,那么能不能处理关键字参数呢?
- >>> print_parms_2('hmm...',someting=42)
- Traceback (most recent call last):
- File "<stdin>", line 1, in ?
- TypeError: print_parms_2() got an unexpected keyword argument 'someting'
可以看出,应该不行,那么要如何实现呢? 就要用到"**"
- >>> def print_parms_3(**parms):
- ... print parms
- ...
- >>> print_parms_3(x=1,y=2,z=3)
- {'y': 2, 'x': 1, 'z': 3}
#返回的是字典而不是元祖
如果将*,**放在一起使用呢?
- >>> def print_parms_4(x,y,z=3,*pospar,**keypar):
- ... print x,y,z
- ... print pospar
- ... print keypar
- ...
- >>> print_parms_4(1,2,3,5,6,7,foo=1,bar=2)
- 1 2 3
- (5, 6, 7)
- {'foo': 1, 'bar': 2}
反转过程
那么要如何使用*,**呢? 看下面一个简单的例子:
- >>> def add(x,y): return x+y
- ...
- >>> parms=(1,2)
- #下面这样会报错
- >>> add(parms)
- Traceback (most recent call last):
- File "<stdin>", line 1, in ?
- TypeError: add() takes exactly 2 arguments (1 given)
- #必需这样,前面加个*号
- >>> add(*parms)
- 3
#字典方面的调用
- >>> def hello_3(greeting='Hello',name='World'):
- ... print '%s,%s!' % (greeting,name)
- ...
- >>> params = {'name':'Sir Robin','greeting':'Well met'}
- >>> hello_3(**params)
- Well met,Sir Robin!
#再看下面的,看加双*和没加**
- >>> def with_star(**kwd):
- ... print kwd['name'],'is',kwd['age'],'years old!'
- ...
- >>> def without_star(kwd):
- ... print kwd['name'],'is',kwd['age'],'years old!'
- ...
- >>> args = {'name':'Mr. Gumby','age':35}
- >>> with_star(**args)
- Mr. Gumby is 35 years old!
- >>> without_star(args)
- Mr. Gumby is 35 years old!
#可以看出两者情形一样,所以*只在定义函数(允许不定数目的参数)
和调用(分割字典或序列)才有用.
Note:
使用拼接(Splicing)操作符传递参数很有用,因为不用担心参数的个数
- >>> def foo(x,y,z,m=0,n=0):
- ... print x,y,z,m,n
- ...
- >>> def call_foo(*arg,**kwds):
- ... print 'Calling foo'
- ... foo(*arg,**kwds)
练习使用参数:
def story(**kwds):
return 'Once upon a time. There was a ' \
'%(job)s called %(name)s.' % kwds
def power(x, y, *others):
if others:
print 'Received redundant parameters:', others
return pow(x, y)
def interval(start, stop=None, step=1):
'Imitates range() for step>0'
if stop is None:
start, stop = 0, start
result = []
i = start
while i < stop:
result.append(i)
i += step
return result
print story(job='king', name='Gumby')
print story(name='Jerry', job='king')
params = {'job': 'language', 'name': 'Python'}
print story(**params)
del params['job']
print story(job='stroke of genius', **params)
print power(2, 3)
print power(3, 2)
print power(y=3, x=2)
params = (5,) * 2
print power(*params)
print power(2, 3, 'Hello,World!')
print interval(10)
print interval(1, 5)
print interval(3, 12, 4)
print power(*interval(3, 7))
输出结果:
D:\>python Python.py
- Once upon a time. There was a king called Gumby.
- Once upon a time. There was a king called Jerry.
- Once upon a time. There was a language called Python.
- Once upon a time. There was a stroke of genius called Python.
- 8
- 9
- 8
- 3125
- Received redundant parameters: ('Hello,World!',)
- 8
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- [1, 2, 3, 4]
- [3, 7, 11]
- Received redundant parameters: (5, 6)
- 81
作用域
- >>> 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
#函数体内使用外部变量
- >>> def combine(param): print param+external
- ...
- >>> external = 'berry'
- >>> combine('Shrub')
- Shrubberry
WARN:像这样引用变量是很多错误的原因:
假如局部变量跟全局变量重名该如何做呢?
- >>> def comb(param):
- ... print param + globals()['param']
- ...
- >>> param='Sherry'
- >>> comb('Jerry->')
- Jerry->Sherry
那么如何更改全局变量呢?
- >>> x = 1
- >>> def change_global():
- ... global x
- ... x +=1
- ...
- >>> x
- 1
- >>> change_global()
- >>> x
- 2
嵌套作用域: 一个函数嵌套在另一个函数里面
- >>> def foo():
- ... def bar():
- ... print 'Hello,World!'
- ... bar()
- ...
- >>> foo()
- Hello,World!
- >>> def multiplier(factor):
- ... def multiplyByFactor(number):
- ... return number*factor
- ... return multiplyByFactor
- ...
- >>> double = multiplier(2)
- >>> double(5)
- 10
- >>> triple = multiplier(3)
- >>> triple(3)
- 9
- >>> multiplier(5)(4)
- 20
#再来看一个
- >>> def A(x):
- def B(y):
- def C(z):
- return x+y+z
- return C
- return B
- >>> A(1)(2)(3)
- 6
递归:自己调用自己,下面是个最简单的无限递归的例子:
- >>>def A():
- return A()
- >>>A() #无限循环,等消耗掉所有内存资源后,报最大递归深度的错误,类似于while True
- File "<pyshell#2>", line 2, in A
- return A()
- RuntimeError: maximum recursion depth exceeded
#break,return合并使用,避免无限循环
#每次函数调用时,生成新的命名空间,意味着当函数调用自身,会有两个函数同时运行。
两个经典:阶乘及幂
n*(n-1)*(n-2)...2*1,可以用普通函数来实现
- >>> def factorial(n):
- result = n
- for i in range(1,n):
- result *=i
- return result
-
- >>> factorial(3)
- 6
也可以用递归实现:
- >>> def factorial(n):
- if n == 1: #1的阶乘为1
- return 1
- else: #大于1的阶乘是n*(n-1)!
- return n*factorial(n-1)
幂如何实现呢?
- >>> def power(x,n): #一般的实现
- result = 1
- for i in range(n):
- result *=x
- return result
- >>> power(2,3)
- 8
- >>> def power(x,n): #阶乘实现
- if n == 0:
- return 1
- else:
- return x*power(x,n-1)
- >>> power(2,3)
- 8
本章新函数
map(func,seq[,seq,]) 对序列中的每个元素应用函数
filter(func,seq) 返回其函数为真的元素的列表
reduce(func,seq[,initial]) 等同于func(func(func(seq[0],seq[1],seq[2],...)))
sum(seq) 返回seq中所有元素的和
apply(func[,args[,kwargs]]) 调用函数,可以提供参数