5. 函数#
5.1. 定义函数#
定义 函数使用关键字 def,后跟函数名与括号内的形参列表。函数语句从下一行开始,并且必须缩进。
函数内的第一条语句是字符串时,该字符串就是文档字符串,也称为 docstring,详见 文档字符串。利用文档字符串可以自动生成在线文档或打印版文档,还可以让开发者在浏览代码时直接查阅文档。
函数在 执行 时使用函数局部变量符号表,所有函数变量赋值都存在局部符号表中;引用变量时,首先,在局部符号表里查找变量,然后,是外层函数局部符号表,再是全局符号表,最后是内置名称符号表。因此,尽管可以引用全局变量和外层函数的变量,但最好不要在函数内直接赋值(除非是 global 语句定义的全局变量,或 nonlocal 语句定义的外层函数变量)。
在调用函数时会将实际参数(实参)引入到被调用函数的局部符号表中;因此,实参是使用 按值调用 来传递的(其中的 值 始终是对象的 引用 而不是对象的值)。 当一个函数调用另外一个函数时,会为该调用创建一个新的局部符号表。
函数定义在当前符号表中把函数名与函数对象关联在一起。解释器把函数名指向的对象作为用户自定义函数。还可以使用其他名称指向同一个函数对象,并访问访该函数
def fib(n): # 输出斐波那契数列直到 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
fib
<function __main__.fib(n)>
f = fib
f(100)
0 1 1 2 3 5 8 13 21 34 55 89
即使没有 return 语句的函数也有返回值,尽管这个值可能相当无聊。 这个值被称为 None (是一个内置名称)。 通常解释器会屏蔽单独的返回值 None。 如果你确有需要可以使用 print() 查看它。
print(fib(0))
None
5.2. 函数参数#
5.2.1. 默认值参数#
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
reply = input(prompt)
if reply in {'y', 'ye', 'yes'}:
return True
if reply 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!') # 给出所有实参
---------------------------------------------------------------------------
StdinNotImplementedError Traceback (most recent call last)
Cell In[6], line 1
----> 1 ask_ok('Do you really want to quit?') # 只给出必选实参
2 ask_ok('OK to overwrite the file?', 2) # 给出一个可选实参
3 ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!') # 给出所有实参
Cell In[5], line 3, in ask_ok(prompt, retries, reminder)
1 def ask_ok(prompt, retries=4, reminder='Please try again!'):
2 while True:
----> 3 reply = input(prompt)
4 if reply in {'y', 'ye', 'yes'}:
5 return True
File ~/myenv/lib/python3.10/site-packages/ipykernel/kernelbase.py:1281, in Kernel.raw_input(self, prompt)
1279 if not self._allow_stdin:
1280 msg = "raw_input was called, but this frontend does not support input requests."
-> 1281 raise StdinNotImplementedError(msg)
1282 return self._input_request(
1283 str(prompt),
1284 self._parent_ident["shell"],
1285 self.get_parent("shell"),
1286 password=False,
1287 )
StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.
默认值只计算一次。默认值为列表、字典或类实例等可变对象时,会产生与该规则不同的结果。例如,下面的函数会累积后续调用时传递的参数:
i = 5
def f(arg=i):
print(arg) # 输出5
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
print(f(1)) # 不会累积结果
print(f(2))
print(f(3))
[1]
[2]
[3]
5.2.2. 关键字参数#
kwarg=value 形式的参数(实参)称为 关键字参数,也称为命令形参。
# 定义函数接受一个必选参数(voltage)和三个可选参数(state, action 和 type)
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(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 个关键字参数
-- This parrot wouldn't voom if you put 1000 volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's a stiff !
-- This parrot wouldn't voom if you put 1000 volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's a stiff !
-- This parrot wouldn't VOOOOOM if you put 1000000 volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's a stiff !
-- This parrot wouldn't VOOOOOM if you put 1000000 volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's a stiff !
-- This parrot wouldn't jump if you put a million volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's bereft of life !
-- This parrot wouldn't voom if you put a thousand volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's pushing up the daisies !
函数调用时,关键字参数必须跟在位置参数后面。关键字参数的顺序并不重要,但必须匹配函数定义的中的参数。以下调用函数的方式都无效:
parrot() # 缺失必需的参数
parrot(voltage=5.0, 'dead') # 关键字参数后存在非关键字参数
parrot(110, voltage=220) # 同一个参数重复的值
parrot(actor='John Cleese') # 未知的关键字参数
Cell In[12], line 2
parrot(voltage=5.0, 'dead') # 关键字参数后存在非关键字参数
^
SyntaxError: positional argument follows keyword argument
5.2.2.1. 约束参数形式#
我们可以使用/
,*
来约定参数形式:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| 位置或关键字 |
| - 仅限关键字
-- 仅限位置
/ 和 * 是可选的。这些符号表明形参如何把参数值传递给函数:位置、位置或关键字、关键字。函数定义中未使用 / 和 * 时,参数可以按位置或关键字传递给函数。
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)
5.2.3. 任意实参列表#
任意实参列表也称为可变参数,保存到元组里面。
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
def say_hi(greet, *args):
print("say " + greet + " to ")
for arg in args:
print(arg)
say_hi("hi", "lucy", "jack", "frank")
*args 形参后的任何形式参数只能是仅限关键字参数,即只能用作关键字参数,不能用作位置参数:
def concat(*args, sep="/"):
return sep.join(args)
print(concat("earth", "mars", "venus"))
print(concat("earth", "mars", "venus", sep="."))
5.2.4. 解包实参列表#
函数调用要求独立的位置参数,但实参在列表或元组里时,要执行解包实参的操作。
* 操作符把实参从列表或元组解包出来
** 操作符解包字典
l = list(range(3, 6)) # 附带两个参数的正常调用
print(l)
args = [3, 6]
l = list(range(*args)) # 解包列表
print(l)
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) # 解包字典
5.3. Lambda 表达式#
lambda 关键字用于创建小巧的匿名函数。lambda a, b: a+b 函数返回两个参数的和。Lambda 函数可用于任何需要函数对象的地方。在语法上,匿名函数只能是单个表达式。在语义上,它只是常规函数定义的语法糖。与嵌套函数定义一样,lambda 函数可以引用包含作用域中的变量。
def make_incrementor(n):
return lambda x: x + n
f = make_incrementor(42)
print(f(0))
print(f(1))
5.4. 文档字符串#
档字符串内容和格式的约定:
第一行应为对象用途的简短摘要。为保持简洁,不要在这里显式说明对象名或类型,因为可通过其他方式获取这些信息(除非该名称碰巧是描述函数操作的动词)。这一行应以大写字母开头,以句点结尾。
文档字符串为多行时,第二行应为空白行,在视觉上将摘要与其余描述分开。后面的行可包含若干段落,描述对象的调用约定、副作用等。
Python 解析器不会删除 Python 中多行字符串字面值的缩进,因此,文档处理工具应在必要时删除缩进。这项操作遵循以下约定:文档字符串第一行 之后 的第一个非空行决定了整个文档字符串的缩进量(第一行通常与字符串开头的引号相邻,其缩进在字符串中并不明显,因此,不能用第一行的缩进),然后,删除字符串中所有行开头处与此缩进“等价”的空白符。不能有比此缩进更少的行,但如果出现了缩进更少的行,应删除这些行的所有前导空白符。转化制表符后(通常为 8 个空格),应测试空白符的等效性。
def my_function():
"""Do nothing, but document it.
No, really, it doesn't do anything.
"""
pass
print(my_function.__doc__)
5.5. 函数注解#
函数注解 是可选的用户自定义函数类型的元数据完整信息。
标注 以字典的形式存放在函数的 annotations 属性中而对函数的其他部分没有影响。 形参标注的定义方式是在形参名后加冒号,后面跟一个会被求值为标注的值的表达式。 返回值标注的定义方式是加组合符号 ->,后面跟一个表达式,这样的校注位于形参列表和表示 def 语句结束的冒号。
def f(ham: str, eggs: str = 'eggs') -> str:
print("Annotations:", f.__annotations__)
print("Arguments:", ham, eggs)
return ham + ' and ' + eggs
f('spam')
5.6. 编码风格#
Python 项目大多都遵循 PEP 8 的风格指南;它推行的编码风格易于阅读、赏心悦目。Python 开发者均应抽时间悉心研读;以下是该提案中的核心要点:
缩进,用 4 个空格,不要用制表符。 4 个空格是小缩进(更深嵌套)和大缩进(更易阅读)之间的折中方案。制表符会引起混乱,最好别用。
换行,一行不超过 79 个字符。
这样换行的小屏阅读体验更好,还便于在大屏显示器上并排阅读多个代码文件。
用空行分隔函数和类,及函数内较大的代码块。
最好把注释放到单独一行。
使用文档字符串。
运算符前后、逗号后要用空格,但不要直接在括号内使用: a = f(1, 2) + g(3, 4)。
类和函数的命名要一致;按惯例,命名类用 UpperCamelCase,命名函数与方法用 lowercase_with_underscores。命名方法中第一个参数总是用 self。
编写用于国际多语环境的代码时,不要用生僻的编码。Python 默认的 UTF-8 或纯 ASCII 可以胜任各种情况。
同理,就算多语阅读、维护代码的可能再小,也不要在标识符中使用非 ASCII 字符。