当你需要编写大型程序时,你可能想要写一段代码供其它地方调用,或者将程序中的相同逻辑抽出来,这时你就需要用到函数。但函数不仅能提供这些功能,通过使用函数还能帮助我们更容易的理解代码。

定义函数

下面的代码定义了一个函数fib,用于计算Fibonacci数列:

>>> def fib(n):
        """Print a Fibonacci series up to n."""
		a, b = 0, 1
		while a < n:
			print(a, end=' ')
			a, b = b, a+b
		print()

>>> fib(200)
0 1 1 2 3 5 8 13 21 34 55 89 144

首先是关键词def,后面跟函数名和参数列表。从下一行开始就是函数的主体,必须缩进。


函数的第一行可以是描述性的字符串,可选,如果有,则会作为函数的一部分进行存储,成为文档字符串。有工具可以使用文档字符串自动生成在线或者打印的文档,也可以通过下面的方式访问:

>>> fib.__doc__
'Print a Fibonacci series up to n.'

或者使用help函数:

>>> help(fib)
Help on function fib in module __main__:

fib(n)
    Print a Fibonacci series up to n.

函数可以使用return返回一个值,例如,为上面的fib函数返回一个结果序列:

>>> def fib2(n):
	"""Return a list containing the Fibonacci series up to n."""
	result = []
	a, b = 0, 1
	while a < n:
		result.append(a)
		a, b = b, a+b
	return result

>>> f100 = fib2(100)
>>> f100
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

如果函数没有return语句,则返回None,如:

>>> f100 = fib(100)
0 1 1 2 3 5 8 13 21 34 55 89 
>>> print(f100)
None

下面介绍更多python中函数的更多特性。

为参数指定默认值

为函数参数指定默认值非常有用,这样在调用该函数时可以指定更少的参数,例如:

>>> def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
		while True:
			ok = input(prompt)
			if ok in ('y', 'ye', 'yes'):
				return True
			if ok in ('n', 'no', 'nop', 'nope'):
				return False
			retries = retries - 1
			if retries < 0:
				raise OSError('uncooperative user')
			print(complaint)

该函数你可以通过下面的方式调用:


    1)仅指定强制参数:

>>> ask_ok('Do you really want to quit?')
Do you really want to quit?y
True

    2)给可选参数中的一个:

>>> ask_ok('OK to overwrite the file?', 2)
OK to overwrite the file?n
False

    3)给出所有参数:

>>> ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
OK to overwrite the file?n
False

需要注意的是,函数的默认值在定义的一刻就确定了,例如:

>>> i = 5
>>> def f(arg=i):
	print(arg)
>>> i = 6
>>> f()
5

由于定义f的时候i的值为5,所以调用f()总是打印5。

警告:默认值只会赋值一次,但当默认值是一个可变对象时(例如list、字典、或者可变类的实例),将可能存在问题,看下面的例子,将传入的值累计在了一起:

>>> def f(a, L=[]):
		L.append(a)
		return L
>>> print(f(1))
[1]
>>> print(f(2))
[1, 2]
>>> print(f(3))
[1, 2, 3]

如果你不想在的多个调用间共享默认值,你可以这样做:

>>> def f(a, L=None):
		if L is None:
			L = []
		L.append(a)
		return L

关键字参数

函数可以使用关键字参数来调用,形式为:kwarg=value。下面是一个例子:

>>> def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
		print("-- This parrot wouldn't", action, end=' ')
		print("if you put", voltage, "volts through it.")
		print("-- Lovely plumage, the", type)
		print("-- It's", state, "!")

parrot指定了一个强制参数和3个可选参数。parrot可以使用下面的形式调用:

parrot(1000)
parrot(voltage=1000)
parrot(voltage=1000000, action='VOOOOOM')
parrot(action='VOOOOOM', voltage=1000000)
parrot('a million', 'bereft of life', 'jump')
parrot('a thousand', state='pushing up the daisies')

注意下面的调用方式是无效的:

parrot()
parrot(voltage=5.0, 'dead')
parrot(110, voltage=220)
parrot(actor='John Cleese')

可变长度参数

函数支持可变长度参数,传入的参数将被打包为一个元组,在可变参数之前,0个或者多个参数可以提供。

>>> def write_multiple_items(file, separator, *args):
	file.write(separator.join(args))

*args表示可以输入任意个参数。通常,可变参数都会放到参数列表的最后,因为他们将包括所有后面的输入参数。任何可变参数后面的参数都必须以关键字参数的形式使用。下面看一个具体的例子:

>>> def concat(*args, sep="/"):
	    return sep.join(args)
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'

除了'*',python还支持'**'。'**'表示传入的参数为字典,看下面的例子:

>>> def print_params(**params):
	print(params)
>>> print_params(x=1,y=2,z=3)
{'z': 3, 'x': 1, 'y': 2}

这样,打印出params为一个字典。下面看一个综合的例子:

>>> def cheeseshop(kind, *arguments, **keywords):
	print("-- Do you have any", kind, "?")
	print("-- I'm sorry, we're all out of", kind)
	for arg in arguments:
		print(arg)
	print("-" * 40)
	keys = sorted(keywords.keys())
	for kw in keys:
		print(kw, ":", keywords[kw])

		
>>> cheeseshop("Limburger", "It's very runny, sir.",
           "It's really very, VERY runny, sir.",
           shopkeeper="Michael Palin",
           client="John Cleese",
           sketch="Cheese Shop Sketch")
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch

解包list和元组

前面已经使用'*'和'**'将传入参数打包为一个元组和字典,但如果参数已经为一个list或者元组,怎么将其作为参数传入函数呢?可以使用'*'和'**'来将其解包。看下面的例子:

>>> args = [3, 6]
>>> list(range(*args))
[3, 4, 5]

同样的方式,字典可以作为关键字参数使用,通过使用'**'操作符:

>>> def parrot(voltage, state='a stiff', action='voom'):
	print("-- This parrot wouldn't", action, end=' ')
	print("if you put", voltage, "volts through it.", end=' ')
	print("E's", state, "!")
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !

Lambda表达式

使用lambda关键字可以创建小的匿名的函数,例如下面的函数返回两个参数的和:

lambda a, b: a+b

lambda函数在语法上被限制为单表达式,语义上,他们仅是通常的函数定义的简化语法。像嵌套函数定义,lambda函数引用包含范围内的变量。

>>> def make_incrementor(n):
	return lambda x: x + n

>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43

上面是使用Lambda表达式返回了一个函数,下面是另一个例子,传递一个小的函数作为参数:

>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

函数注释

函数注释是可选的,包括关于用户定义函数的任意的信息。python本身和标准库都不使用函数注释,这里展示一下函数注释的语法。
注释被存储在函数的__annotations__属性中,参数注释通过参数后面加一个冒号定义,被一个表达式跟随;返回注释通过语法->定义。下面是一个例子:

>>> def f(ham: 42, eggs: int = 'spam') -> "Nothing to see here":
	print("Annotations:", f.__annotations__)
	print("Arguments:", ham, eggs)

>>> f('wonderful')
Annotations: {'return': 'Nothing to see here', 'ham': 42, 'eggs': <class 'int'>}
Arguments: wonderful spam

作用域

python中变量被放在一个字典中,通过“名称->值”的形式存储。内建的vars函数可以返回这个字典:

>>> x = 1
>>> scope = vars()
>>> scope['x']
1

这种字典就叫做命名空间或者作用域。每个函数调用都会创建一个新的作用域,函数内的变量被称为局部变量,参数类似于局部变量。

python中提供了方法在函数中访问全局变量,可以使用globals函数获取全局变量值,该函数的近亲是vars,例如:

>>> def combine(parameter):
	print(parameter + globals()['parameter'])

>>> parameter = 'berry'
>>> combine('Shrub')
Shrubberry

也可以使用绑定全局变量的方法:

>>> x = 1
>>> def change_global():
	global x
	x = x + 1

>>> change_global()
>>> x
2

但这样容易导致混乱,建议尽量不要这样做。