title: 函数 create: 2016.12.7 modified: 2016.12.7 tags: python 函数 参数
4
[TOC]
接下来会介绍如果将语句组织成函数。还会详细介绍参数(parameter)和作用域(scope),以及递归的概念。
In [1]:
import math
x=1
y=math.sqrt
callable(x)
Out[1]:
In [2]:
callable(y)
Out[2]:
怎么定义函数呢?使用def语句即可:
In [5]:
def hello(name):
return 'Hello, '+name+'!'
In [6]:
print hello('world')
本例中return语句是用来从函数中返回值的。
In [14]:
def square(x):
'''Calculates the square of the number x.'''
return x*x
文档字符串可以按如下方式访问:
In [15]:
square.__doc__ #__doc__是函数属性。属性名中的双下划线表示它是个特殊属性。
Out[15]:
内建的help函数是非常有用的,可以得到关于函数,包括它的文档字符串信息。
In [16]:
help(square)
In [31]:
def try_to_change(n):
n='Gumby'
name='Entity'
try_to_change(name)
name
Out[31]:
在try_to_change内,参数n获得了新值,但是它没有影响到name变量。
字符串(以及数字和元组)是不可改变的,即无法被修改(只能用新值覆盖)。所以它们做参数的时候也就无需多做介绍。但是如果将可变的数据结构如列表用作参数的时候会发生什么:
In [32]:
def change(n):
n[0]='Humby'
names=['Entity','Thing']
change(names)
names
Out[32]:
本例中,参数被改变了。有些奇怪吧?下面不用函数调用再做一次:
In [21]:
names=['Entity','Thing']
n=names #当两个变量同时引用一个列表的时候,它们是同一个对象。
n[0]='Gumby'
names
Out[21]:
In [22]:
n is names
Out[22]:
如果想避免上述情况,可以复制一个列表的副本。当在序列中做切片的时候,返回的切片就是一个副本。
In [27]:
names=['Entity','Thing']
n=names[:]
n is names
Out[27]:
In [28]:
n==names
Out[28]:
In [29]:
n[0]='Gumby'
names
Out[29]:
In [30]:
n
Out[30]:
使用函数改变数据结构(比如列表或字典)是将程序抽象化的好方法,这时就需要改变参数。假设需要编写一个存储名字并且能用名字、中间名或姓查找联系人的程序,可以使用下面的数据结构:
In [2]:
def init(data):
data['first']={}
data['middle']={}
data['last']={}
storage={} #storage就是参数,需要改变
init(storage)
storage
Out[2]:
可以看到,函数init包办了初始化的工作,让代码更易读。 把自己的名字加入这个数据结构:
In [4]:
me='Magnus Lie Hetland'
storage['first']['Magnus']=[me]
storage['middle']['Lie']=[me]
storage['last']['Hetland']=[me]
每个键下面都存储了一个以人名组成的列表。本例中,列表中只有我。
现在如果想得到所有注册中间名为Lie的人,如下:
In [5]:
storage['middle']['Lie']
Out[5]:
将人名加到列表中的步骤有点枯燥乏味,尤其是要加入很多姓名相同的人时,需要扩展已经存储了那些名字的列表,如下:
In [6]:
my_sister='Anne Lie Hetland'
storage['first'].setdefault('Anne',[]).append(my_sister)
storage['middle'].setdefault('Lie',[]).append(my_sister)
storage['last'].setdefault('Hetland',[]).append(my_sister)
storage['first']['Anne']
Out[6]:
In [7]:
storage['middle']['Lie']
Out[7]:
如果要写个大程序来这样更新列表,那么很显然程序很快就会变得臃肿且笨拙不堪了。
在编写存储名字的函数前,先写个获得名字的函数:
In [9]:
def lookup(data, label, name):
return data[label].get(name)
lookup(storage,'middle','Lie')
Out[9]:
标签(比如”middle“)以及名字(比如”Lie“)可以作为参数提供给lookup函数使用,这样会获得包含了全名的列表。
注意,返回的列表和存储在数据结构中的列表是相同的,所以如果列表被修改了,那么也会影响数据结构。
编写存储名字的函数:
In [10]:
def store(data,full_name):
names=full_name.split() #拆分full_name,得到一个names列表
if len(names)==2:
names.insert(1,'') #如果names长度为2(只有首名和末名),则插入一个空字符作为中间名
labels='first','middle','last' #将'first','middle','last'作为元组存储在labels中,也可以使用列表,只是为了方便而去掉括号
for label, name in zip(labels, names): #使用zip函数联合标签和名字
people = lookup(data, label, name)
if people:
people.append(full_name)
else:
data[label][name]=[full_name]
来试用一下刚刚实现的程序:
In [11]:
Mynames={}
init(Mynames)
store(Mynames,'Magnus Lie Hetland')
lookup(Mynames,'middle','Lie')
Out[11]:
In [15]:
def hello_1(greeting,name):
print "%s,%s" % (greeting,name)
def hello_2(name,greeting):
print "%s,%s" % (name,greeting)
两个代码所实现的是完全一样的功能,只是参数名字反过来了:
In [16]:
hello_1('Hello','world')
hello_2('Hello','world')
有些时候(尤其是参数很多的时候),参数的顺序是很难记住的。为了让事情简单些,可以提供参数的名字:
In [17]:
hello_1(greeting='Hello',name='world')
hello_1(name='world',greeting='Hello')
这样一来,顺序就完全没影响了。但参数名和值一定要对应。这类使用参数名提供的参数叫做关键字参数。它的作用在于可以明确每个参数的作用。尽管这么做打的字就多了些,但是很显然,每个参数的含义变得更加清晰。而且就算弄乱了参数的顺序,对于程序的功能也没有任何影响。
关键字参数最厉害的地方在于可以在函数中给参数提供默认值:
In [18]:
def hello_3(greeting='Hello',name='world'):
print "%s,%s" % (greeting,name)
当参数具有默认值的时候,调用的时候就不用提供参数了:
In [19]:
hello_3()
In [20]:
hello_3('Greetings')
In [21]:
hello_3('Greetings','universe')
但是如果只想提供name参数,而让greeting使用默认值该怎么办呢?
In [22]:
hello_3(name='Gumby')
位置和关键字参数是可以联合使用的。把位置参数放置在前面就可以了。
In [23]:
def print_params(*params):
print params
这里只指定了一个参数,但是前面加上了个星号。这是什么意思?让我们用一个参数调用函数看看会发生什么:
In [24]:
print_params('Testing')
可以看到,结果作为元组打印出来,因为里面有个逗号。那么在params中使用多个参数看看会发生什么:
In [26]:
print_params('Testing',1,2,3)
参数前的星号将所有值放置在同一个元组中。可以说是将这些值收集起来,然后使用。不知道能不能联合普通参数?
In [29]:
def print_params_2(title, *params):
print title
print params
print_params_2('Params:', 1,2,3)
所以星号的意思就是“收集其余的位置参数”。如果不提供任何供收集的元素,params就是个空元组:
In [30]:
print_params_2('Nothing:')
那么能不能处理关键字参数呢?
In [31]:
print_params_2('Hmm...',something=42)
看来不行。所以我们需要另外一个能处理关键字参数的“收集”操作。原来需要在参数前面加上两个星号“**”
In [33]:
def print_params_3(**params):
print params
print_params_3(x=1,y=2,z=3)
返回的是字典而不是元组。放一起来看看:
In [35]:
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)
In [36]:
print_params_4(1,2)
联合使用这些功能,可以做的事就多了。现在回到原来的问题上:怎么实现多个名字同时存储。解决方案如下:
In [40]:
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]
In [41]:
d={}
init(d)
store(d,'Magnus Lie Hetland','Luke Skywaller')
lookup(d,'last','Skywaller')
Out[41]:
In [42]:
def add(x,y):
return x+y
add(1,2)
Out[42]:
比如说有个包含由两个要相加的数字组成的元组,可以使用*运算符来分配它们到“另一端”:
In [43]:
params=(1,2)
add(*params)
Out[43]:
可以使用同样的技术来处理字典—使用双星号运算符。
In [45]:
def hello_3(greeting='Hello',name='world'):
print "%s,%s" % (greeting,name)
params={'name':'Sir Robin','greeting':'Well met'}
hello_3(**params)
在定义或调用函数时使用星号(或者双星号)仅传递元组或字典。
In [50]:
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
In [51]:
print story(job='king',name='Gumby')
In [52]:
print story(name='Sir Robin',job='brave knight')
In [57]:
params={'job':'language','name':'Python'}
print story(**params)
In [58]:
del params['job']
params
Out[58]:
In [59]:
print story(job='stroke of genius', **params)
In [60]:
power(2,3)
Out[60]:
In [61]:
power(3,2)
Out[61]:
In [62]:
power(y=3,x=2)
Out[62]:
In [63]:
params=(2,)*2
params
Out[63]:
In [64]:
power(*params)
Out[64]:
In [65]:
params=(2,)*4
power(*params)
Out[65]:
In [66]:
interval(10)
Out[66]:
In [67]:
interval(1,8,2)
Out[67]:
In [72]:
x=1
scope=vars()
scope['x']
Out[72]:
In [73]:
scope['x']+=1
x
Out[73]:
这类“不可见字典”叫做命名空间或者作用域。那么到底有多少个命名空间?除了全局作用域外,每个函数调用都会创建一个新的作用域(局部作用域或局部命名空间):
In [74]:
def foo():
x=42
x=1
foo()
x
Out[74]:
当调用foo的时候,新的命名空间被创建,它只作用于foo内的代码块。函数内的变量被称为局部变量(local variable)。
如果在函数内部将值赋予一个变量,它会自动成为局部变量—除非告知Python将其声明为全局变量:
In [75]:
x=1
def change_global():
global x
x=x+1
change_global()
x
Out[75]:
In [77]:
def factorial_1(n):
result=n
for i in range(1,n):
result*=i
return result
factorial_1(3)
Out[77]:
下面来看看使用递归的版本:
1的阶乘是1;
大于1的数n的阶乘是n乘n-1的阶乘。
In [79]:
def factorial_2(n):
if n==1:
return 1
else:
return n*factorial_2(n-1)
factorial_2(3)
Out[79]:
考虑另外一个例子,计算幂(就像内建的pow函数或**运算符一样)。
先看一个简单的例子:power(x,n)是x自乘n-1次的结果。
In [84]:
def power_1(x,n):
result=1
for i in range(n):
result*=x
return result
power_1(2,3)
Out[84]:
接下来把它改为递归版本:
对于任意数字x来说,power(x,0)是1;
对于任何大于0的数,power(x,n)是x乘以power(x,n-1)的结果。
In [85]:
def power_2(x,n):
if n==0:
return 1
else:
return x*power(x,n-1)
power_2(2,3)
Out[85]: