【循序渐进学Python】6.Python中的函数
1. 创建函数
一个函数代表一个行为并且返回一个结果(包括None),在Python中使用def
关键字来定义一个函数,如下:
def hello(name): print 'hello,' + name + '!'
接下来调用函数,并查看其返回值:
# output: # hello,gy! # None print hello('gy')
可以看到hello
函数首先打印了:Hello,gy!
,然后我们又将其返回值也打印出来,在这里因为没有返回具体的值,所以返回了None
。
下面我们可以定义一个用于计算斐波那契数列的函数,接收计算的位数(参数),返回计算的结果(返回值),如下:
1 def fibs(num): 2 result = [0,1] 3 for i in range(num - 2): 4 result.append(result[-2] + result[-1]) 5 return result 6 7 # output: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] 8 print fibs(10)
2. 文档字符串
文档字符串指的是在函数开头写下该函数的说明字符串,该字符串会作为函数的一部分进行存储。可以通过调用__doc__
属性来查看:
1 def fibs(num): 2 "根据指位数来计算斐波那契数列" 3 result = [0,1] 4 for i in range(num - 2): 5 result.append(result[-2] + result[-1]) 6 return result 7 8 # 输出:根据指位数来计算斐波那契数列 9 print fibs.__doc__
3. 参数
函数使用参数来传递信息,而参数类型又基本分为两大类型:可变类型参考和不可变类型参数。
3.1 不可变类型参数
不可变类型(字符串、数字和元组等)是不可变的,即无法修改,只能使用新的值来覆盖。使用不可变类型作为函数的参数时:在函数内为参数赋值不会改变其外部变量的值,如下:
1 # 字符串是不可变的 2 def try_to_change(n): 3 n = 'Gumby' 4 5 name = 'Sunshine' 6 7 # 尝试改变参数的值 8 try_to_change(name) 9 10 # output: Sunshine(函数内部的改变对其没有影响) 11 print name
3.2 可变类型参数
可变类型参数(列表等)指的是:使用可变的数据结构来作为函数的参数使用。在函数内部修改可变类型参数的值时,会同时改变其外部变量的值(因为它们引用的其实是同一个对象),如下所示:
1 # 列表是可变的 2 def try_to_change2(n): 3 n[0] = 'Sunshine' 4 5 person = ['Gumby','Bob'] 6 # 尝试改变参数的值 7 try_to_change2(person) 8 9 # output: ['Sunshine', 'Bob'] 10 # 函数内部的改变会作用于外部的变量 11 print person
如果想避免上面的情况可以对需要作为可变类型的参数的对象复制一个副本,这里是列表可以通过对其进行切片来返回一个新的副本,如下:
1 # 将可变的数据结构(列表)用作参数 2 def try_to_change2(n): 3 n[0] = 'Sunshine' 4 5 person = ['Gumby','Bob'] 6 n = person[:] # 通过切片返回新的副本 7 try_to_change2(n) 8 print person # ['Gumby', 'Bob'] 9 print n # ['Sunshine', 'Bob'] 10 print n is person # False
3.3 关键字参数和默认值
在我们之前使用参数的形式是:位置参数,即调用该函数给其传值时,是根据输入的值的先后顺序来给参数一一赋值 的。不过有时候如果我们需要定义的函数有大量的参数时,传值时只是于依赖于顺序的话是比较容易出错的。同时如果部分参数在不多数情况下值都是一样的,每次 调用都需要重新赋值也比较麻烦。针对于这种情况Python为我们提供了两个语法糖:即关键字参数和默认值。(PS:C#4.0新增的两个特性:具名参数和可选参数,和Python的这两个语法糖很类似,感兴趣的同学可以参考这里:《Effective C#》读书笔记——条目10:使用可选参数减少方法重载的数量<C#语言习惯>)。
使用关键字参数不需要对函数进行任何修改,只需要在调用函数时显示为参数赋值即可(不依赖特定顺序),如下:
1 def hello(gretting,name): 2 print "%s,%s!" % (gretting,name) 3 4 # output: hello,world! 5 hello(name='world',gretting='hello')
可以看到虽然调用时代码变多了,但是每个参数的含义一目了然。不过有时候我们的函数有些参数在大部分情况下使用同样的值,有时候也需要改变,这时候使用默认值,可以很好的解决这种问题,如下:
1 def hello(gretting = 'hello',name='world'): 2 print "%s,%s!" % (gretting,name) 3 4 hello() # hello,world! 5 hello(name = 'Sunshine') # hello,Sunshine! 6 hello('Greetings') # Greetings,world!
可以看到,如果没有给任何值的话,函数自动使用默认值,也可以通过关键字参数指定部分值,其他的依然使用默认参数,这为我们的方法调用提供了很大的灵活性。
3.4 使用任意参数列表
前面介绍的参数的个数基本都是固定的,有时候可以接收任意多的参数也是很有必要的,这时候可以使用Python为我们提供的语法糖(*
号)来实现:
1 def print_params(number,*params): 2 print number 3 print params 4 5 # output: 6 # 6 7 # (1, 2, 3, 4, 5, 6) 8 print_params(6,1,2,3,4,5,6) 9 10 # output: 11 # 0 12 # () 13 print_params(0)
我们可以看到参数前的*
号将所有的值放置到一个元组中(可以理解为将其他的参数收集起来)。如果没有为*
号后的参数赋值则其值为一个空的元组。不过使用一个*
号这样的语法不能处理关键字参数,如下:
1 def print_params(*params): 2 print params 3 4 # TypeError: print_params() got an unexpected keyword argument 'params' 5 print_params(params = (1,2,3))
解决的办法很简单,使用**
来修饰参数即可,如下所示:
1 # -- coding: utf-8 -- 2 def print_params(**params): 3 print params 4 print type(params) 5 6 # output: {'params': (1, 2, 3)} 7 # output: <type 'dict'> 8 print_params(params = (1,2,3))
可以看到使用**
修饰的参数本质上其实是一个字典类型。可以通过这个字典来收集参数。
3.5 解包参数列表
除了通过才定义函数的参数(形参)前添加*
或者**
来收集参数,我们还可以在调用函数时在实参前调解这两个操作符,它们表示其对应的逆过程。如下所示:
1 def add(x,y): 2 print x + y 3 4 params = (1,2) 5 6 # output: 3 7 add(*params)
通过使用*
操作符我们将params参数分割了add
函数所需的两个参数,同样我们也可以使用**
操作符来分割字典对象:
1 def hello(greeting,name): 2 print '%s,%s' %(greeting,name) 3 4 params = {'greeting':'hello','name':'world'} 5 6 # output: hello,world 7 # 这里使用*操作符也是可以的 8 hello(**params)