定义函数

先来看一个 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(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
1
2
3
4
5
6
7
8
9
10
11

函数定义必须使用 def 关键字,之后是函数名和参数列表。

第二行的字符串是一段文档说明,可用于生成文档,同时我们可以通过 fib.__doc__ 获取这个字符串。

每个函数都有返回值,没有通过 return 语句返回的默认返回 None

每次函数在执行的时候,都会创建一个新的符号表来记录函数创建的本地变量。当需要使用某一个变量的时候,会先在当前函数的符号表中查找,如果没有找到,会再到上一层函数的符号表中查找,直到最终在全局符号表中查找。

因此,在函数中,不能直接修改全局变量或者外层函数的变量的值,除非我们使用 global 语句后者 nonlocal 语句。

当调用函数的时候,函数的参数也会记录到符号表中来,并且传参是值传递的方式,也就是说,如果参数是一个对象,传过来的值是指向这个对象的引用,而不是对象本身。

我们还可以通过 __annotations__ 来访问函数的元数据,比如参数的类型,返回值类型等信息。我们需要在定义函数的时候指定这些元数据。

>>> 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'
1
2
3
4
5
6
7
8
9

函数参数

参数默认值

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)
1
2
3
4
5
6
7
8
9
10
11

上面的代码定义了两个默认值,我们可以有如下是三种方式调用这个函数:

  • 只传第一个必填参数 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!')

注意,参数的默认值只计算一次,后续调用这个函数,参数的默认值不会重新计算。

def f(a, L=[]):
    L.append(a)
    return L

print(f(1))
print(f(2))
print(f(3))
1
2
3
4
5
6
7

输出:

[1]
[1, 2]
[1, 2, 3]
1
2
3

关键字参数

定义关键字参数的形式为 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, "!")
1
2
3
4
5

函数 parrot 有一个必填参数 voltage 和三个可选参数(stateaction,和 type)。我们可以这么传参:

parrot(1000)          # 1 个位置参数
parrot(voltage=1000)  # 1 个关键字参数
parrot(voltage=1000000, action='VOOOOOM') # 2 个关键字参数
parrot(action='VOOOOOM', voltage=1000000) # 2 个关键字参数
parrot('a million', 'bereft of life', 'jump') # 3 个位置参数
parrot('a thousand', state='pushing up the daisies') # 1 个位置参数,一个关键字参数
1
2
3
4
5
6

如下传参方式会报错:

parrot() # 必填参数未传
parrot(voltage=5.0, 'dead') # 关键字参数后跟位置参数
parrot(110, voltage=220) # 参数重复
parrot(actor='John Cleese') # 未知的关键字参数
1
2
3
4

在调用函数的时候,关键字参数必须在位置参数后面。并且,所有的关键字参数必须与函数的定义匹配,不能传递未定义的关键字参数。在使用关键字参数的时候,参数顺序没有要求。同一个参数只能传递一个值,传递多个值会报错。

当存在形式为 **name 的最终形式参数时,这个参数会收到一个字典,这个字典中保存着所有除了明确定义的关键字参数以外的关键字参数。当存在 *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)
    for kw in keywords:
        print(kw, ":", keywords[kw])
1
2
3
4
5
6
7
8

我们这样调用这个函数:

cheeseshop("Limburger", "It's very runny, sir.",
           "It's really very, VERY runny, sir.",
           shopkeeper="Michael Palin",
           client="John Cleese",
           sketch="Cheese Shop Sketch")
1
2
3
4
5

输出如下:

-- 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
1
2
3
4
5
6
7
8

函数传参

通常情况下,给 Python 函数传参,既可以用位置参数,也可以用关键字参数。但是,传参的形式有一个约束:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      -----------    ----------     ----------
        |             |                  |
        |        位置或关键字参数           |
        |                                只能是关键字参数
        只能是位置参数
1
2
3
4
5
6

/* 是可先的,如果使用了这两个符号,则表示传递参数的形式。 / 之前的必须是位置参数,* 之后的必须是关键字参数。

参考如下函数定义:

>>> def standard_arg(arg):
...     print(arg)
...
>>> def pos_only_arg(arg, /):
...     print(arg)
...
>>> def kwd_only_arg(*, arg):
...     print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
...     print(pos_only, standard, kwd_only)
1
2
3
4
5
6
7
8
9
10
11

调用方式如下:

>>> standard_arg(2)
2

>>> standard_arg(arg=2)
2

>>> pos_only_arg(1)
1

>>> kwd_only_arg(arg=3)
3

>>> combined_example(1, 2, kwd_only=3)
1 2 3

>>> combined_example(1, standard=2, kwd_only=3)
1 2 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

解包参数

有些时候,函数的参数已经存在于一个列表或者字典里,我们需要对其进行解包,然后再作为参数值传给函数。

我们可以使用 * 操作符对列表或者元组进行解包,使用 ** 操作符对字典进行解包。

>>> args = [3, 6]
>>> list(range(*args)) # 解包调用 list 函数
[3, 4, 5]
1
2
3
>>> 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 !
1
2
3
4
5
6
7
8

Lambda 表达式

通过 lambda 关键字可以创建 lambda 表达式。lambda 表达式本质上是函数的一个语法糖,用来创建一些简短的函数。

>>> def make_incrementor(n):
...     return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
1
2
3
4
5
6
7
8
>>> 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')]
1
2
3
4

关注微信公众号,获取最新推送~

加微信,深入交流~