title: 函数 create: 2016.12.7 modified: 2016.12.7 tags: python 函数 参数

  4

[TOC]

接下来会介绍如果将语句组织成函数。还会详细介绍参数(parameter)和作用域(scope),以及递归的概念。

1 创建函数

内建的callable函数可以用来判断函数是否可调用:


In [1]:
import math
x=1
y=math.sqrt
callable(x)


Out[1]:
False

In [2]:
callable(y)


Out[2]:
True

怎么定义函数呢?使用def语句即可:


In [5]:
def hello(name):
    return 'Hello, '+name+'!'

In [6]:
print hello('world')


Hello, world!

本例中return语句是用来从函数中返回值的。

1.1 记录函数

如果想给函数写文档,让后面使用该函数人能理解的话,可以加入注释(例如,#hello、"""hello""""、'''hello''')。下面代码演示了如何给函数添加文档字符串:


In [14]:
def square(x):
    '''Calculates the square of the number x.'''
    return x*x

文档字符串可以按如下方式访问:


In [15]:
square.__doc__              #__doc__是函数属性。属性名中的双下划线表示它是个特殊属性。


Out[15]:
'Calculates the square of the number x.'

内建的help函数是非常有用的,可以得到关于函数,包括它的文档字符串信息。


In [16]:
help(square)


Help on function square in module __main__:

square(x)
    Calculates the square of the number x.

2 参数

写在def语句中函数名后面的变量通常叫做函数的形式参数,而调用函数时提供的值是实际参数,或者称为参数

2.1 我能改变参数吗


In [31]:
def try_to_change(n):
    n='Gumby'
name='Entity'
try_to_change(name)
name


Out[31]:
'Entity'

在try_to_change内,参数n获得了新值,但是它没有影响到name变量。
字符串(以及数字和元组)是不可改变的,即无法被修改(只能用新值覆盖)。所以它们做参数的时候也就无需多做介绍。但是如果将可变的数据结构如列表用作参数的时候会发生什么:


In [32]:
def change(n):
    n[0]='Humby'
names=['Entity','Thing']
change(names)
names


Out[32]:
['Humby', 'Thing']

本例中,参数被改变了。有些奇怪吧?下面不用函数调用再做一次:


In [21]:
names=['Entity','Thing']
n=names    #当两个变量同时引用一个列表的时候,它们是同一个对象。
n[0]='Gumby'
names


Out[21]:
['Gumby', 'Thing']

In [22]:
n is names


Out[22]:
True

如果想避免上述情况,可以复制一个列表的副本。当在序列中做切片的时候,返回的切片就是一个副本。


In [27]:
names=['Entity','Thing']
n=names[:]
n is names


Out[27]:
False

In [28]:
n==names


Out[28]:
True

In [29]:
n[0]='Gumby'
names


Out[29]:
['Entity', 'Thing']

In [30]:
n


Out[30]:
['Gumby', 'Thing']

使用函数改变数据结构(比如列表或字典)是将程序抽象化的好方法,这时就需要改变参数。假设需要编写一个存储名字并且能用名字、中间名或姓查找联系人的程序,可以使用下面的数据结构:


In [2]:
def init(data):
    data['first']={}
    data['middle']={}
    data['last']={}
storage={}   #storage就是参数,需要改变
init(storage)
storage


Out[2]:
{'first': {}, 'last': {}, 'middle': {}}

可以看到,函数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]:
['Magnus Lie Hetland']

将人名加到列表中的步骤有点枯燥乏味,尤其是要加入很多姓名相同的人时,需要扩展已经存储了那些名字的列表,如下:


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]:
['Anne Lie Hetland']

In [7]:
storage['middle']['Lie']


Out[7]:
['Magnus Lie Hetland', 'Anne Lie Hetland']

如果要写个大程序来这样更新列表,那么很显然程序很快就会变得臃肿且笨拙不堪了。
在编写存储名字的函数前,先写个获得名字的函数:


In [9]:
def lookup(data, label, name):
    return data[label].get(name)
lookup(storage,'middle','Lie')


Out[9]:
['Magnus Lie Hetland', 'Anne Lie Hetland']

标签(比如”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]:
['Magnus Lie Hetland']

2.2 关键字参数和默认值

上述所使用的参数都叫做位置参数,因为它们的位置很重要—事实上比它们的名字更加重要。接下来引入的这个功能可以回避位置问题,当你慢慢习惯使用这个功能以后,就会发现程序规模越大,它们的作用也就越大。
考虑下面两个函数:


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')


Hello,world
Hello,world

有些时候(尤其是参数很多的时候),参数的顺序是很难记住的。为了让事情简单些,可以提供参数的名字:


In [17]:
hello_1(greeting='Hello',name='world')
hello_1(name='world',greeting='Hello')


Hello,world
Hello,world

这样一来,顺序就完全没影响了。但参数名和值一定要对应。这类使用参数名提供的参数叫做关键字参数。它的作用在于可以明确每个参数的作用。尽管这么做打的字就多了些,但是很显然,每个参数的含义变得更加清晰。而且就算弄乱了参数的顺序,对于程序的功能也没有任何影响。
关键字参数最厉害的地方在于可以在函数中给参数提供默认值:


In [18]:
def hello_3(greeting='Hello',name='world'):
    print "%s,%s" % (greeting,name)

当参数具有默认值的时候,调用的时候就不用提供参数了:


In [19]:
hello_3()


Hello,world

In [20]:
hello_3('Greetings')


Greetings,world

In [21]:
hello_3('Greetings','universe')


Greetings,universe

但是如果只想提供name参数,而让greeting使用默认值该怎么办呢?


In [22]:
hello_3(name='Gumby')


Hello,Gumby

位置和关键字参数是可以联合使用的。把位置参数放置在前面就可以了。

2.3 收集参数

有些时候让用户提供任意数量的参数是很有用的。比如在名字存储程序中,用户每次只能存一个名字。如果能像下面这样存储多个名字就更好了:
store(data,name1,name2,name3)
用户可以给函数提供任意多的参数。实现起来也不难。试着像下面这样定义函数:


In [23]:
def print_params(*params):
    print params

这里只指定了一个参数,但是前面加上了个星号。这是什么意思?让我们用一个参数调用函数看看会发生什么:


In [24]:
print_params('Testing')


('Testing',)

可以看到,结果作为元组打印出来,因为里面有个逗号。那么在params中使用多个参数看看会发生什么:


In [26]:
print_params('Testing',1,2,3)


('Testing', 1, 2, 3)

参数前的星号将所有值放置在同一个元组中。可以说是将这些值收集起来,然后使用。不知道能不能联合普通参数?


In [29]:
def print_params_2(title, *params):
    print title
    print params
print_params_2('Params:', 1,2,3)


Params:
(1, 2, 3)

所以星号的意思就是“收集其余的位置参数”。如果不提供任何供收集的元素,params就是个空元组:


In [30]:
print_params_2('Nothing:')


Nothing:
()

那么能不能处理关键字参数呢?


In [31]:
print_params_2('Hmm...',something=42)


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-31-3b2ecc07b82c> in <module>()
----> 1 print_params_2('Hmm...',something=42)

TypeError: print_params_2() got an unexpected keyword argument 'something'

看来不行。所以我们需要另外一个能处理关键字参数的“收集”操作。原来需要在参数前面加上两个星号“**”


In [33]:
def print_params_3(**params):
    print params
print_params_3(x=1,y=2,z=3)


{'y': 2, 'x': 1, '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)


1 2 3
(5, 6, 7)
{'foo': 1, 'bar': 2}

In [36]:
print_params_4(1,2)


1 2 3
()
{}

联合使用这些功能,可以做的事就多了。现在回到原来的问题上:怎么实现多个名字同时存储。解决方案如下:


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]:
['Luke Skywaller']

2.4 反转过程

如何将参数收集为元组和字典已经讨论过了,但是事实上,如果使用*和**的话,也可以执行相反的操作。那么参数收集的逆过程是什么呢?


In [42]:
def add(x,y):
    return x+y
add(1,2)


Out[42]:
3

比如说有个包含由两个要相加的数字组成的元组,可以使用*运算符来分配它们到“另一端”:


In [43]:
params=(1,2)
add(*params)


Out[43]:
3

可以使用同样的技术来处理字典—使用双星号运算符。


In [45]:
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

在定义或调用函数时使用星号(或者双星号)仅传递元组或字典。

2.5 练习使用参数

下面把上述方法放在一起举个例子:


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')


Once upon a time, there was a king called Gumby.

In [52]:
print story(name='Sir Robin',job='brave knight')


Once upon a time, there was a brave knight called Sir Robin.

In [57]:
params={'job':'language','name':'Python'}
print story(**params)


Once upon a time, there was a language called Python.

In [58]:
del params['job']
params


Out[58]:
{'name': 'Python'}

In [59]:
print story(job='stroke of genius', **params)


Once upon a time, there was a stroke of genius called Python.

In [60]:
power(2,3)


Out[60]:
8

In [61]:
power(3,2)


Out[61]:
9

In [62]:
power(y=3,x=2)


Out[62]:
8

In [63]:
params=(2,)*2
params


Out[63]:
(2, 2)

In [64]:
power(*params)


Out[64]:
4

In [65]:
params=(2,)*4
power(*params)


Received redundant parameters: (2, 2)
Out[65]:
4

In [66]:
interval(10)


Out[66]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [67]:
interval(1,8,2)


Out[67]:
[1, 3, 5, 7]

3 作用域

到底什么是变量?可以把它们看作是值的名字。在执行x=1赋值语句后,名称x引用到值1。这就像用字典一样,键引用值。变量和所对应的值用的是个“不可见”的字典。内建的vars函数可以返回这个字典:


In [72]:
x=1
scope=vars()
scope['x']


Out[72]:
1

In [73]:
scope['x']+=1
x


Out[73]:
2

这类“不可见字典”叫做命名空间或者作用域。那么到底有多少个命名空间?除了全局作用域外,每个函数调用都会创建一个新的作用域(局部作用域局部命名空间):


In [74]:
def foo():
    x=42
x=1
foo()
x


Out[74]:
1

当调用foo的时候,新的命名空间被创建,它只作用于foo内的代码块。函数内的变量被称为局部变量(local variable)。

如果在函数内部将值赋予一个变量,它会自动成为局部变量—除非告知Python将其声明为全局变量:


In [75]:
x=1
def change_global():
    global x
    x=x+1
change_global()
x


Out[75]:
2

4 递归

函数可以调用其他函数,也可以调用自身。递归(recursion)简单来说就是引用(或调用)自身的意思。一切用递归实现的功能都可以用循环实现,但有些时候递归函数更易读。
首先,计算数n的阶乘。那么怎么计算呢?可以使用循环:


In [77]:
def factorial_1(n):
    result=n
    for i in range(1,n):
        result*=i
    return result
factorial_1(3)


Out[77]:
6

下面来看看使用递归的版本:
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]:
6

考虑另外一个例子,计算幂(就像内建的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]:
8

接下来把它改为递归版本: 对于任意数字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]:
8

5 小结