4.7.1 默认函数定义

最常用的就是为一个或多个参数设定默认值,这让函数可以用比定义时更少的参数来调用,例如:

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)

可以通过下面几种方式调用:

  • 给定一个必须参数: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,因为 i 在方法定义时值为5

注意 ! 默认值只会被赋值一次,所以当默认值是一个可变对象例如List、Dict或多数类时会有不同,这个方法似乎每次总是记住了前一次调用的结果

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, and type),可以通过下面任意方式调用:

parrot(1000)                                          # 1 positional argument
parrot(voltage=1000)                                  # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM')             # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000)             # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump')         # 3 positional arguments
parrot('a thousand', state='pushing up the daisies')  # 1 positional, 1 keyword

但下面的调用是不通过的:

parrot()                     # required argument missing
parrot(voltage=5.0, 'dead')  # non-keyword argument after a keyword argument
parrot(110, voltage=220)     # duplicate value for the same argument
parrot(actor='John Cleese')  # unknown keyword argument

在一次函数调用中,关键字参数必须跟在位置参数后面。所有传入的关键字参数必须符合方法能接收的参数(例如parrot方法就不接受actor参数),而顺序并不重要,这当然也包括了必须的参数(比如parrot(voltage=1000)就是通过的)。没有参数可以被第二次接受,所以下面的示例是错误的:

>>> def function(a):
...     pass
...
>>> function(0, a=0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: function() got multiple values for keyword argument 'a'

当最后一个参数以 **name 的形式出现,这表示接收一个包含了所有关键字参数的Dictionary字典(看这里介绍)。它还可以结合一个 *name 形式的参数来调用,即接收一个可选参数的元组,注意*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)
    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

值得注意的是,打印出来的关键字参数是通过代码中的 sorted() 方法来排序的,因为Dictionary字典的keys 是无序的,如果不这样做打印出来的参数就是无序的

 

4.7.3 可变参数

最少使用的是指定函数可以传入可变参数来调用,这些参数被包装在tuple元组中(元组的概念)。

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

通常这些可变参数出现在形参的最后一个,因为它们会一股脑儿地把所有剩下的参数都传入方法。而任何出现在 *args 可变参数之后的形参必须是关键字参数(‘keyword-only’ arguments),确保它们只能被用作关键字参数而不会被当做可变参数。

>>> 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 使用 * 拆分参数列表

相反的情况发生在当参数已经组成一个tuple元组或是list列表却需要拆分成方法所需要的单独的位置参数。例如内置的 range() 方法接收单独的 start 和 stop 两个参数,如果手头只有list或tuple,我们可以用 * 操作符来把list或tuple拆分成关键字。

>>> 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]

同样的,Dictionary字典也可用 ** 操作符拆分成关键字

>>> 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关键字,你可以创建一些简短的匿名function。这个function就返回两个参数之和: lambda a, b: a+b 。Lambda表达式精炼地将function限制在一个表达式中,可以在任何地方替代function object来使用,从语法上来说其实就是Python中常规方法的"语法糖"。与嵌套方法定义相似,lambda表达式能够引用位于function范围内的变量。

>>> def make_incrementor(n):
...     return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43

上面的例子使用Lambda function来返回一个方法,下面的例子是传入Lambda function来代替参数

>>> 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 文档字符串(Documentation Strings)

对于Doc strings,有一些约定是需要我们去遵守的。

第一行应当是对于方法目的简短且精确的概括。简要来说,既然方法的名称和返回类型可以通过其他方法获得,那么就不用再次明确地说明。
第一行应当以一个大写字母开头,以一个句号结尾。
如果有多行,那么第二行应为空,看起来像是把概括和剩下的描述部分分开。
剩下的行数则是用一行至多行来描述对象的调用规则、副作用等。

Python解释器并不会去除多行文本中的缩进,所以处理Doc strings的工具得自己去做,一般通过下面的约定来完成。
跟在文档第一行后面的第一个非空的行 A 决定了整个文档的缩进列数(我们不使用第一行作为参考是因为它通常是紧靠在文档的三个单引号'''后面,因此看起来并不明显)。相当于缩进长度的空格就被添加在所有行的前部。比 A 缩进列数更少的行不应该出现,但如果出现了,每一行前面所有的空格都会被移除。一个Tab缩进通常相当于8个空格。看下面的代码就很清楚:

>>> def my_function():
...     '''Do nothing.
...
...     No,really,it doesn't do anything.
...     '''
...     pass
...
>>> print(my_function.__doc__)
Do nothing.

        No,really,it doesn't do anything.

>>> def less_or_more_indentation_func():
...     '''Do something.
...
...     second line,normal line
... less indentation line
...             more indentation line
...     '''
...     pass
...
>>> print(less_or_more_indentation_func.__doc__)
Do something.

        second line,normal line
less indentation line
                more indentation line