1.函数的创建
函数是可以调用的(可能带有参数,也可能无参),它执行某种行动并且返回一个值。一般来说,内建的callable函数可以用来判断函数是否可调用。
1 >>> import math 2 >>> x = 1 3 >>> y = math.sqrt 4 >>> callable(x) 5 False 6 >>> callable(y) 7 True
注意:callable函数在python3.0中不可使用,需要使用表达式hasattr(func,_call_)代替。
运用函数实现斐波那契数列
1 # 实现斐波那契数列函数 2 def fibs(num): 3 result = [0,1] 4 for i in range(num-2): 5 result.append(result[-2]+result[-1]) 6 return result 7 # 掉用斐波那契函数 8 result = fibs(10) 9 print(result)
1.文档化函数
为了让其他人使用该函数的时候理解,可以加注释。还有另外一种方法是直接写上字符串。实例如下:
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 print(fibs.__doc__) 9 10 ----- 11 输出结果: 12 D:\Python27\python.exe D:/pythonwork/test01/function_1.py 13 该函数用于实现斐波那契数列
2.并非真正函数的函数
数学意义上的函数,总是在计算其参数后返回点什么。Python的有些函数却并非返回任何东西。在其他语言中,这类函数有可能有其他名字,比如过程。但是python的函数就是函数,基本在数学意义上不是。
没有return语句,或者虽有return语句但是return后面没有跟任何值的函数不返回值:
1 >>> def test(): 2 ... print(‘hello python‘) 3 ... # 这里的return语句只起到结束函数的作用 4 ... return 5 ... print(‘hi python‘) 6 ... 7 >>> test() 8 hello python 9 >>> x = test() hello python >>> print(x) None
test函数其实返回了一个值“None”
注意:千万不要被默认行为所迷惑。如果if语句内返回值,那么要确保其他分支也有其他返回值,这样一来当调用者期待一个序列的时候,就不会意外地返回None。
2.参数魔法
1.简单区分形参和实参
在python写在def语句中函数名后面的变量通常叫做函数的形参,而调用函数时提供的值是实参,或者称为参数。
2.能改变参数吗?
在函数内为参数赋值不会影响外部任何变量的值:
1 >>> def try_to_change(n): 2 ... n = ‘Mr.Gumby‘ 3 ... 4 >>> name = ‘Mr.Entity‘ 5 >>> try_to_change(name) 6 >>> name 7 ‘Mr.Entity‘ 8 >>>
为了方便理解,可以不用函数模拟一下
1 >>> name = ‘Mr.Entity‘ 2 >>> n = name #这就话的作用基本上等于传参数 3 >>> n = ‘Mr.Gumby‘ #在函数内部完成的 4 >>> name 5 ‘Mr.Entity‘
注意:参数存储在局部作用域(local scope)内。
如果将可变的数据结构如列表用作参数的时候会发生什么:
1 >>> def change(n): 2 ... n[0] = ‘Mr.Gumby‘ 3 ... 4 >>> names = [‘Mr.G‘,‘Mr.Thing‘] 5 >>> change(names) 6 >>> names 7 [‘Mr.Gumby‘, ‘Mr.Thing‘] 8 >>>
可以看到例子中参数发生了改变,解决这样的问题,可以先用切片的方法复制一个副本,然后对副本进行操作。这样就不影响原始数据了。
3.为什么要修改参数
使用函数改变数据结构(比如字典和列表)是一种将程序抽象化的好方法。
假设需要编写一个存储名字并且能用名字、中间名或者姓查询联系人的程序,可以使用下面数据结构:
1 storage = {} 2 storage[‘frist‘] = {} 3 storage[‘middle‘] = {} 4 storage[‘last‘] = {}
storage这个数据结构是带有3个键‘first‘、‘middle‘、‘last‘的字典。每个键下面都又存储一个字典。字典中,可以使用名字(名字、中间名或姓)作为键,插入联系人列表作为值。比如要把一个英文名字加入这个数据,可以按如下操作:
1 me = ‘Magnus Lie Hetland‘ 2 storage[‘first‘][‘Magnus‘] = [me] 3 storage[‘first‘][‘middle‘] = [me] 4 storage[‘first‘][‘last‘] = [me] 5 print(storage[‘first‘][‘Magnus‘]) 6 print(storage[‘first‘][‘middle‘]) 7 print(storage[‘first‘][‘last‘]) 8 9 ------- 10 结果如下: 11 D:\Python27\python.exe D:/pythonwork/test01/function_1.py 12 [‘Magnus Lie Hetland‘] 13 [‘Magnus Lie Hetland‘] 14 [‘Magnus Lie Hetland‘]
将人名加到列表中的步骤有点枯燥无味,尤其是要加入很多人名的时候。代码如下:
1 my_sister = ‘Anne Lie Hetland‘ 2 storage[‘first‘].setdefault(‘Anne‘, []).append(my_sister) 3 storage[‘middle‘].setdefault(‘Lie‘, []).append(my_sister) 4 storage[‘last‘].setdefault(‘Hetland‘, []).append(my_sister) 5 print(storage[‘middle‘][‘Lie‘]) 6 print(storage[‘first‘][‘Anne‘]) 7 8 ---------- 9 结果如下: 10 D:\Python27\python.exe D:/pythonwork/test01/function_1.py 11 [‘Magnus Lie Hetland‘, ‘Anne Lie Hetland‘] 12 [‘Anne Lie Hetland‘]
如果写大程序来这样更新列表,那么很显然程序很快就会得臃肿不堪。
怎么却解决呢,那么就要用到抽象了,抽象的要点就是隐藏更新时繁琐的细节,可以用函数来实现这个过程。
实现一个添加查找用户名的程序,代码如下:
1 # 创建初始化结构的函数 2 def init(data): 3 data[‘first‘] = {} 4 data[‘middle‘] = {} 5 data[‘last‘] = {} 6 7 # 创建查询函数 8 def lookup(data, label, name): 9 return data[label].get(name) 10 11 # 编写增加用户名的函数 12 def store(data, full_name): # 使用参数data和full_name进入这个函数,这两个参数被设置为函数在外部获得的一些值。 13 names = full_name.split() # 通过拆分full_name,得到一个叫做names的列表 14 if len(names) == 2: # 如果names的长度为2,那么插入一个字符串作为中间名 15 names.insert(1, ‘‘) 16 labels = (‘first‘, ‘middle‘, ‘last‘) 17 for label, name in zip(labels, names): # 使用zip函数联合表情和名字,对每一个(label,name)对,进行处理 18 people = lookup(data, label, name) # 调用查询函数,如果存在列表则追加,不存在则创建 19 if people: 20 people.append(full_name) 21 else: 22 data[label][name] = [full_name] 23 24 #####################################主函数######################################### 25 MyNames = {} 26 # 引用初始化结构函数 27 init(MyNames) 28 # 调用存储函数 29 store(MyNames, ‘Naguns Lie Hetland‘) 30 store(MyNames, ‘Anne Lie Hetland‘) 31 store(MyNames, ‘MyNames, Robin Hood‘) 32 # 调用查询函数 33 name = lookup(MyNames, ‘middle‘, ‘Lie‘) 34 print(name)
4.如果参数是不可变的呢
在python中:函数只能修改参数对象本身。但是如果你的参数不可变(比如数字),又该怎么办呢?
不好意思,没有办法。
将变量的数值增1的函数可以按如下方式来写 1 >>> def inc(x): return x+1 2 ... 3 >>> inc(10) 4 11 5 >>> x 6 >>> foo = inc(10) 7 >>> foo 8 11 如果真的像改变参数,那么可以使用一点小技巧,将值放置在列表中: 9 >>> def inc_2(x): x[0] = x[0] + 1 10 ... 11 >>> foo = inc_2(10) 12 >>> foo = inc_2([10]) 13 >>> foo 14 >>> foo = [10] 15 >>> inc_2(foo) 16 >>> foo 17 [11] 18 >>>
5.关键字参数和默认值
目前为止我们所使用的参数都叫做位置参数,因为他们的位置很重要,事实上比他们的名字还重要。
如以下例子:
1 def hello_1(greeting, name): 2 print(‘%s,%s!‘ % (greeting, name)) 3 4 5 def hello_2(name, greeting): 6 print(‘%s,%s!‘ % (name, greeting)) 7 8 9 hello_1(‘hello‘, ‘python‘) 10 11 hello_2(‘hello‘, ‘python‘) 12 13 ----------------- 14 结果:(两个代码显示的功能是一样的) 15 D:\Python27\python.exe D:/pythonwork/test01/function_2.py 16 hello,python! 17 hello,python!
参数的顺序很难记住,为了让事情简单些,可以提供参数的名字:
def hello_1(greeting=‘hello‘, name=‘python‘): print(‘%s,s%‘%(greeting, name)) # 这样一来顺序就完全没有影响了# 但参数值一定要对应
这类使用参数名提供的参数叫做关键字参数。他的主要作用在于可以明确每个参数的作用。避免调用错参数。
关键字参数最厉害的地方在于可以在函数中给参数提供默认值:
1 >>> def hello_3(greeting=‘Hello‘, name=‘python‘): 2 ... print(‘%s,%s!‘%(greeting, name)) 3 ... 4 >>> hello_3() 5 Hello,python!
6.收集参数
以下写法可以给函数提供多个参数
def print_params(*params):
print(params)
“*”的意义就是“收集其余的位置参数”。如果不提供任何供收集的元素,params就是个空元祖
那么能不能处理关键字参数呢?见如下代码:
1 >>> print_params_2(‘Hmm...‘, something=42) 2 Traceback (most recent call last): 3 File "<stdin>", line 1, in <module> 4 NameError: name ‘print_params_2‘ is not defined 5 >>>
很明显是不行的,所以我们需要采用另外一种方法来处理关键字参数的“收集”。这是就需要用到“**”
事例如下:
1 >>> def print_params_3(**params): 2 ... print(params) 3 ... 4 >>> print_params_3(x=1,y=2,z=3) 5 {‘x‘: 1, ‘z‘: 3, ‘y‘: 2} 6 >>>
测试证明是可行的。反悔的是字典,而不是元组。
那么放在一起是否可行呢?
>>> 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,foo=1,bar=2) 1 2 3 (4, 5) {‘foo‘: 1, ‘bar‘: 2}
回到之前怎么实现多个名字同时存储的问题上。解决方案如下:
1 # 编写增加用户名的函数 2 def store(data, *full_names): # 使用“*full_names”收集参数,实现多个名字同时存储。 3 for full_name in full_names: # 使用参数data和full_name进入这个函数,这两个参数被设置为函数在外部获得的一些值。 4 names = full_name.split() # 通过拆分full_name,得到一个叫做names的列表 5 if len(names) == 2: # 如果names的长度为2,那么插入一个字符串作为中间名 6 names.insert(1, ‘‘) 7 labels = (‘first‘, ‘middle‘, ‘last‘) 8 for label, name in zip(labels, names): # 使用zip函数联合表情和名字,对每一个(label,name)对,进行处理 9 people = lookup(data, label, name) # 调用查询函数,如果存在列表则追加,不存在则创建 10 if people: 11 people.append(full_name) 12 else: 13 data[label][name] = [full_name]
7.参数收集的逆过程
1 >>> params = {‘name‘:‘Sir Robin‘,‘greeting‘:‘Well met‘} 2 >>> hello_3(**params) 3 Well met,Sir Robin! 4 >>>
在定义或者调用函数时使用星号(或者双星号)仅传递元组或字典。
星号只用在定义函数(允许使用不定数目的参数)或者调用(“分割”字典或者序列)时才有用。
提示:
使用拼接(splicing)操作符“传递”参数很有用,因为这样一来就不用关心参数的个数之类的问题了,例如:
1 def foo(x, y, z, m=0, n=0): 2 print(x, y, z, m, n) 3 4 5 def call_foo(*args, **kwds): 6 print(‘Calling foo!‘) 7 foo(*args, **kwds)
8.使用参数例子:
1 def story(**kwds): 2 return ‘Once upon a time, there was a %(job)s called %(name)s.‘ % kwds 3 4 5 def power(x, y, *others): 6 # 如果为类型 7 if others: 8 print(‘Received redundant parameters:‘, others) 9 return pow(x, y) 10 11 12 def interval(start, stop=None, step=1): # start开始值, stop结束值, step步长 13 ‘Imitates range() for step >0 ‘ # 函数的用途 14 if stop is None: # 如果没有为stop提供直... 15 start, stop = 0, start # 指定参数 16 result = [] # 定义列表 17 i = start # 计算索引 18 while i < stop: # 直到检索到stop的索引 19 result.append(i) # 将索引添加到result内 20 i += step # 用step增加索引i 21 return result # 以列表的形式返回结果
小结:
- 抽象:抽象是隐藏多余细节的艺术。定义处理细节的函数可以让程序更加抽象。
- 函数定义:函数使用def语句定义。他们是由语句组成的块,可以从“外部世界”获取值(参数),也可以返回一个或者多个值作为运算结果。
- 参数:函数从参数中得到需要的信息,也就是函数调用时设定的变量。Python中有两类参数:位置参数和关键字参数。参数在给定默认值时是可选的。