4.7 更多关于函数的定义
你也可以用参数的一个可变数字来定义函数。一共有三种形式,它们可以结合使用。
4.7.1 默认参数值
最常用的形式就是给一个或多个参数指定一个默认值。这种形式创建出来的函数可以使用比它定义的所需要的更少的参数来调用。举个栗子:
def ask_ok(prompt, retries=4, reminder='Please try again!'):
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 ValueError('invalid user response')
print(reminder)
这个函数可以用若干种方式来调用。
只给定强制的参数:
ask_ok('Do you really want to quit?')
给定可选参数之一:
ask_ok('OK to overwrite the file?', 2)
或者给定所有的参数:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
这个例子也介绍了关键字 in
。它测试出了一个序列是否包含了某个特定的值。
默认值在 定义 范围内的函数的定义的那个地方就会被求值出来。所以:
i = 5
def f(arg=i):
print(arg)
i = 6
f()
这回输出 5。
重要警告: 默认值只会被计算出一次。当默认值是一个可变对象的时候 (如列表,字典,或者大部分类的实例) 就会不太一样。举个栗子,下面的函数在之后接连的调用中不断累加:
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
这将会打印出:
[1]
[1, 2]
[1, 2, 3]
译者注: 默认参数仅仅在第一次调用的时候会传入函数的内部,之后调用都会引用第一次传入的那个值,指向第一次赋值的内存,后面的调用就不会重新赋值了
如果你不想让一个函数的几次调用共享默认值,你个以把函数写成下面这样:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
4.7.2 关键字参数
函数可以使用 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, "!")
这个函数接受一个必要的参数 (voltage) 和三个可选参数 (state, action, 还有 type)。这个函数可以用下面的方式来调用:
parrot(1000) # 1 个位置参数
parrot(voltage=1000) # 1 个关键字参数
parrot(voltage=1000000, action='VOOOOOM') # 2 个关键字参数
parrot(action='VOOOOOM', voltage=1000000) # 2 k个关键字参数
parrot('a million', 'bereft of life', 'jump') # 3 个位置参数
parrot('a thousand', state='pushing up the daisies') # 1 个位置参数, 1 个关键字参数
但是下面的调用都是无效的:
parrot() # 必要参数缺失
parrot(voltage=5.0, 'dead') # 关键字参数后面是 非关键字参数
parrot(110, voltage=220) # 同一个参数有重复的值
parrot(actor='John Cleese') # 未知的关键字参数
在一个函数调用中,关键字参数必须跟在 位置参数 后面 (译者注:位置参数就是和其他语言一样,直接传入的参数,其所在的位置和参数列表相对应)。所有的关键字参数必须匹配函数所接受的参数 (例如,actor
不是 parrot
函数的一个有效参数),而且他们的顺序并不重要。这同样也包含了非可选参数 (例如,parrot(voltage=1000)
也是有效的)。所有的参数都不能接受接受数值超过一次。这里有一个以为这一条限制而失败的函数:
>>> def function(a):
... pass
...
>>> function(0, a=0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: function() got multiple values for keyword argument 'a'
如果一个 **name
这种形式的形参 (形式参数) 在最后出现了,那么它就会接受一个 字典 (dictionary) (参考 映射类型——字典),这个字典包含了所有除了那些与形参对应的参数之外的关键字参数。也可以结合使用带有 *name
这样形式的一种形参 (在下一小节会介绍),这种形参接受一个包含位置变量的元组 (tuple),该元组中的位置变量在形参表之外。(*name
必须在 **name
之前出现。) 例如,如果我们像下面这样定义一个函数:
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)
for kw in keywords:
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.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch
注意,关键字参数打印的顺序是有保证的,这样就能匹配他们在参数调用时传入的顺序了。
4.7.3 可变 (任意) 参数列表
最后,最不常使用的一种方式就是指定一个可以用任意数量的参数来调用的函数。这些参数都要被包裹在一个元组 (tuple,参阅元组和序列)中。在参数的可变数量之前,零个或者更多的普通参数都可以出现。
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
通常情况下,这些 可变 (variadic)
参数都会在形参列表的末尾,因为他们会收集所有剩下的传入函数的的参数。任何出现在 *arg
之后的形参都是“限定关键字”的参数,这就意味着他们只能用作关键字参数,而不能作为位置参数。
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
4.7.4 解包参数列表
如果函数需要传入相互独立的位置参数,而参数已经存在于一个列表或者元组中时,在这种相反的情况下,参数就需要被 解包。举个栗子,内建的函数 range() 期望得到独立的参数 start (开头) 和 stop (结尾)。如果他们不是分别都能获取的,就写一个函数,通过用 * 操作调用这个函数来把参数从列表或元组中解包出来:
>>> list(range(3, 6)) # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args)) # call with arguments unpacked from a list
[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 !
4.7.5 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')]
4.7.6 文档字符串
下面是关于文档字符串的内容和格式的一些约定。
第一行必须总是一个关于这个对象的简明扼要的概括。为了使其简短,它不应该显式的说明该对象的名字或属性,因为这些东西会通过其他方式得到 (例外,如果对象名正好是一个形容函数操作的动词)。这一行应该以一个大写字母开头,以一个句号结尾。
如果文档字符串有多行,第二行应该是一个空行,这个空行视觉上把概括和剩下的描述分隔开。之后的行应该在一个或多个段落中,它们用于描述对象的调用规则,副作用等等。
Python 的语法分析器不会去除 Python 中多行字符串的每行的缩进,所以如果需要的话,处理文档的工具就必须要去除缩进。
第一行字符串之后的第一个非空行确定了整个文档字符串的缩进量。(我们不能用第一行,因为它一般情况下,它紧挨着字符串的左引号,这样他在字符串字面量中的的缩进量就不明显。) 和这个缩进量“等量”的空白接下来都会从字符串每一行的开头去除掉。缩进更少的行不应该出现,但是如果它们出现了,它们所有的开头空白字符都应该被去除。空白字符是否等量应该在 tab 扩展之后测试 (通常等于 8 个空格)。
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything.
译者注: doc 属性,doc 的左右两边各有两个下划线
4.7.7 函数注释 (Function Annotations)
函数注释是完全可选的、关于用户自定义函数所使用的类型的数据元信息。(更多信息,见 PEP 484)。
作为一个字典来说,函数的注释都存放在 __annotations__
属性中,对函数的其他部分并没有影响。参数注释 使用一个在参数名字后面跟一个冒号来定义,冒号后面跟上一个表达式,表达式算出就是参数注释的值。返回值注释 用 ->
来定义,后面跟上一个表达式,它位于参数列表和用来标记 def
语句结束的冒号之间。接下来的一个例子的一个位置参数,一个关键字参数,和返回值都被注释了:
>>> def f(ham: str, eggs: str = 'eggs') -> str:
... print("Annotations:", f.__annotations__)
... print("Arguments:", ham, eggs)
... return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'
译者注: 不要把函数的注释和代码的注释弄混淆了。虽然它们都翻译为 注释, 但是,代码的注释是 comment,函数的注释是 annotation。为了不引起歧义,建议大家直接用英语。后文中如果出现这个术语,我将直接使用 annotation,而不会翻译为 注释。