将语句组合成函数,可以告诉计算机如何完成任务,且只需说一次,而不用反复向计算机传达详细指令。
斐波那契数(一种数列,其中每个数都是前两个数的和):
>>> fibs = [0,1] >>> for i in range(10): fibs.append(fibs[-2]+fibs[-1]) >>> fibs [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
优化一下代码,使其指定动态的范围:
>>> fibs = [0,1] >>> num = input('enter:') enter:10 >>> for i in range(int(num)-2): fibs.append(fibs[-2]+fibs[-1]) >>> fibs [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
如果不从用户那里读取数字,而是通过参数来获取,可以继续优化代码:
>>> def fibs(num): re = [0,1] for i in range(num-2): re.append(re[-2]+re[-1]) return re >>> fibs(10) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
def语句定义函数,return语句用于从函数返回值。num是一个形参,可以在其他地方多次调用fibs(),而不用每次都要将代码再敲一遍。
除了#注释之外,还有一种编写注释的方式,就是添加独立的字符串。放在函数开头的字符串称为文档字符串。
>>> def square(x): 'Calculates the square of the number x.' return x * x >>> square.__doc__ #访问文档字符串 'Calculates the square of the number x.'
__doc__是函数的属性。
>>> help(square) #内置函数help可以获取有关函数的信息 Help on function square in module __main__: square(x) Calculates the square of the number x.
修改参数
当参数为可变的数据结构(列表)时,在函数内修改参数,在外面调用函数。
>>> 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 = names #假装传递名字作为参数 >>> n[0] = 'Mr.Gumby' #修改列表,这步上面是在函数内进行的 >>> names ['Mr.Gumby', 'Mrs.Thing']
将同一个列表赋给两个变量时,这两个变量将同时指向这个列表。想避免这种结果,必须创建列表的副本。
>>> names = ['Mrs.Entity','Mrs.Thing'] >>> n = names[:] #对序列进行切片操作时,返回的切片是副本。现在n和names包含2个相等但不同的列表。这里也可以用names.copy()来创建副本。 >>> n[0] = 'Mr.Gumby' >>> names ['Mrs.Entity', 'Mrs.Thing'] >>> n ['Mr.Gumby', 'Mrs.Thing']
将切片参数传给函数,也不会影响:
>>> def change(n): n[0] = 'Mr.Gumby' >>> names = ['Mrs.Entity','Mrs.Thing'] >>> change(names[:]) >>> names ['Mrs.Entity', 'Mrs.Thing']
栗子:假设编写一个程序,让它储存姓名,并让用户能够根据名字,中间名或姓找人。
创建初始化数据结构的函数:
>>> def init(data): data['first'] = {} data['middle'] = {} data['last'] = {} >>> storage = {} >>> init(storage) #这个函数承担了初始化职责,提高了代码的可读性。 >>> storage {'first': {}, 'middle': {}, 'last': {}}
然后需要一个函数来获取人员姓名:
>>> def lookup(data,label,name): return data[label].get(name)
接着要将人员存储到数据结构中:
>>> def store(data,full_name): #将参数data和full_name提供给这个函数。 names = full_name.split() #通过拆分full_name创建1个名为names的列表。 if len(names) == 2 : names.insert(1,'') #如果只有名字和姓,没有中间名,就将中间名设为空字符串 labels = 'first','middle','last' #将label存储到元组中(也可以是列表) for name,label in zip(names,labels): #zip函数将label和name合并,可以对label-name对执行一些操作 people = lookup(data,label,name) if people: people.append(full_name) #将full_name插入一个新列表 else: data[label][name]=[full_name]
现在运行看看:
>>> me = {} >>> init(me) >>> me {'first': {}, 'middle': {}, 'last': {}} >>> store(me,'Magnus Lie Hetland') >>> me {'first': {'Magnus': ['Magnus Lie Hetland']}, 'middle': {'Lie': ['Magnus Lie Hetland']}, 'last': {'Hetland': ['Magnus Lie Hetland']}} >>> lookup(me,'middle','Lie') ['Magnus Lie Hetland'] >>> store(me,'Robin Hood') >>> store(me,'Robin Locksley') >>> store(me,'Mr. Gumby') >>> lookup(me,'first','Robin') ['Robin Hood', 'Robin Locksley'] >>> lookup(me,'middle','') ['Robin Hood', 'Robin Locksley', 'Mr. Gumby']
关键字参数
当函数需要传多个参数时,参数的位置就变的非常重要,比名字还重要。
>>> def hello_1(greeting,name): print('{},{}!'.format(greeting,name)) >>> def hello_2(name,greeting): print('{},{}!'.format(name,greeting)) >>> hello_1('hello','world') hello,world! >>> hello_2('hello','world') hello,world!
为了避免遗忘参数的位置,可以用关键字参数(用名称指定参数):
>>> hello_1(greeting='hello',name='world') hello,world! >>> hello_2(greeting='hello',name = 'world') world,hello!
给参数指定默认值:
>>> def hello_3(greeting = 'hello',name='world'): #给参数指定默认值后,调用函数时可不提供它。 print('{},{}!'.format(greeting,name)) >>> hello_3() #不提供参数 hello,world! >>> hello_3('Greetings','universe') #提供全部参数值 Greetings,universe! >>> hello_3(name='Gumby') #提供部分参数值
收集参数
>>> def print_params(*params): #参数前面的星号将提供的所有值都放在一个元组中,也就是将这些值收集起来。 print(params) >>> print_params('Testing') ('Testing',) >>> print_params(1,2,3) (1, 2, 3) >>> x,*y,z = 1,2,3,4,5,6 #上面的情况与赋值时的星号有点相似,不过赋值时带星号的变量收集多余的值 >>> x,y,z (1, [2, 3, 4, 5], 6) #并且是将收集的值放在列表中 >>> def print_params_2(title,*params): print(title) print(params) >>> print_params_2('Params:',1,2,3) #星号意味着收集余下的位置参数 Params: (1, 2, 3) >>> print_params_2('Nothing:') #如果没有可收集的参数,params将是一个空元组 Nothing: ()
当带星号的参数位置不在末尾:
>>> def in_the_middle(x,*y,z,f): print(x,y,z,f) >>> in_the_middle(1,2,3,4,5,z=6,f=7) #需要使用名称来指定后续参数 1 (2, 3, 4, 5) 6 7 >>> in_the_middle(1,2,3,4,5,6,7) Traceback (most recent call last): File "<pyshell#26>", line 1, in <module> in_the_middle(1,2,3,4,5,6,7) TypeError: in_the_middle() missing 2 required keyword-only arguments: 'z' and 'f'
单个星号不会收集关键字参数,要收集关键字参数,可使用两个星号:
>>> def print_params_3(**params): print(params) >>> print_params_3(x=1,y=2,z=3) #两个星号得到的是一个字典 {'x': 1, 'y': 2, 'z': 3} >>> def print_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,foo=1,bar=2) 1 2 3 (4, 5, 6) {'foo': 1, 'bar': 2}
结合这些技术,可以将上面存储姓名的栗子再次优化:
>>> 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 name,label in zip(names,labels): people = lookup(data,label,name) if people: people.append(full_name) else: data[label][name] = [full_name] >>> init(me) >>> store(me,'Luke Skywalker','Anakin Skywalker') >>> lookup(me,'first','Luke') ['Luke Skywalker']
优化之后,store里可以一次存储多个名字。
分配参数
>>> def hello(greeting='hello',name='Toney'): print('{},{}!'.format(greeting,name)) >>> params = {'name':'Mary','greeting':'Greet'} >>> hello(**params)
Greet,Mary!
通过使用运算符**可将字典中的值分配给关键字参数。
只有在定义函数(允许可变数量的参数)或调用函数时(拆分字典或序列)使用,星号才能发挥作用。
综合的栗子:
>>> def story(**kwds): return 'Once upon a time,there was a ' '{job} called {name}'.format_map(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: #如果没有给参数stop指定值 start,stop = 0,start #就调整参数start和stop的值 result = [] i = start #从start开始往上数 while i < stop: #数到stop位置 result.append(i) #将当前数的数加到result列表末 i += step # i=i+1 return result >>> print(story(job='king',name='Gumby')) Once upon a time,there was a king called Gumby >>> print(story(name='Sir Robin',job='brave knight')) Once upon a time,there was a brave knight called Sir Robin >>> params = {'job':'language','name':'Python'} >>> print(story(**params)) Once upon a time,there was a language called Python >>> power(2,3) 8 >>> power(y=3,x=2) 8 >>> params = (5,)*2 >>> power(*params) 3125 >>> power(3,3,'hello,world') Received redundant parameters: ('hello,world',) 27 >>> interval(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
作用域
当局部变量中有一个与全局变量命名相同的变量时,可用函数globals来访问全局变量。
>>> def combine(paramter): print(paramter + globals()['paramter']) >>> paramter = 'berry' >>> combine('Shrub') Shrubberry
在函数内给变量赋值时,该变量默认是局部变量,但可以指定该变量是全局变量。
>>> x = 1 >>> def change(): global x x = x+1 >>> change() >>> x 2
递归
函数可调用自身,称为递归。可使用递归完成的任何任务都可使用循环来完成,但有时使用递归函数的可读性更高。
>>> def fac(n): result = n #将result设置为n for i in range(1,n): result *= i #依次乘以1到n-1的每个数字 return result #返回result >>> fac(5) 120 >>> def fac(n): if n == 1: return 1 else: return n * fac(n-1) >>> fac(5) 120
上面两个函数的功能一致,都是计算数字n的阶乘,貌似在这种场景下,递归的可读性比较高。