函数是编程语言中最基础的构建块之一,它将一系列操作封装成一个可重用的单元。在Python中,函数不仅是一种代码组织方式,更是一种强大的抽象工具,能够提高代码的可读性、可维护性和复用性。本文将从函数的定义、调用与返回值机制,以及文档字符串的规范与应用三个方面,系统解析 Python 函数的基础概念。

一、函数定义、调用与返回


1. 函数的基本语法结构

Python 中定义函数使用def关键字,基本语法格式如下:

1
2
3
4
def 函数名(参数列表):
"""函数文档字符串"""
# 函数体代码
return 返回值

函数名应遵循 Python 的命名规范,通常使用小写字母和下划线的组合(如get_user_info),且需要有意义,能够清晰表达函数的功能。

参数列表是函数接收外部输入的接口,可以包含多种形式的参数:

  • 位置参数(Positional Arguments):按顺序传递的参数
  • 关键字参数(Keyword Arguments):通过参数名传递的参数
  • 默认参数(Default Arguments):具有默认值的参数
  • 可变参数(Variable Arguments):*args(位置参数)和**kwargs(关键字参数)

2. 函数的调用过程

当程序执行到函数调用语句时,Python 解释器会执行以下步骤:

  1. 函数对象查找:根据函数名在当前作用域中查找对应的函数对象
  2. 参数绑定:将实参(实际参数)与形参(形式参数)进行匹配
  3. 栈帧创建:为该函数调用创建新的栈帧(stack frame),包含局部变量表和执行上下文
  4. 函数体执行:从函数体的第一行开始顺序执行代码
  5. 返回值处理:当遇到return语句或函数体执行完毕时,处理返回值
  6. 栈帧销毁:释放该函数调用占用的内存资源
  7. 控制权转移:将控制权交回调用函数的位置,继续执行后续代码

函数调用生命周期:

第 1-2 步: 函数对象查找 & 参数绑定
第 3-4 步: 栈帧创建 & 函数体执行
第 5-7 步: 返回值处理 & 栈帧销毁 & 控制权转移

3. 函数返回值机制

函数返回值是函数执行后向调用者传递的结果,是函数与外部交互的重要方式。

(1) 单返回值

单个返回值是最简单的形式,使用return关键字后跟一个表达式:

1
2
3
4
5
6
7
def calculate_sum(a, b):
"""计算两个数的和"""
result = a + b
return result # 单一返回值

total = calculate_sum(10, 20)
print(total) # 输出:30

(2) 多返回值

Python 函数可以返回多个值,这在其他语言中需要通过复杂的方式实现,但在 Python 中却异常简洁:

1
2
3
4
5
6
7
8
9
10
def calculate_info(a, b):
"""计算两个数的和与积,并返回两个结果"""
sum_result = a + b
product = a * b
return sum_result, product # 多个返回值

# 调用函数并解包返回值
sum_val, product_val = calculate_info(5, 3)
print(f"和:{sum_val}") # 输出:和:8
print(f"积:{product_val}") # 输出:积:15

多返回值的底层实现是通过将多个值自动打包为一个元组(tuple)返回,调用者可以通过元组解包或直接访问元组元素的方式获取各个值。这使得 Python 在处理需要返回多个相关数据时格外优雅。
python 的多返回值特性,其底层是通过将多个值自动打包为一个元组(tuple)来实现的。

(3) 返回值的类型与灵活性

python 函数的返回值具有以下特点:

  • 动态类型:函数可以返回任何类型的对象,包括基本类型(整数、字符串等)、可变对象(列表、字典等)或自定义对象
  • 单值或多值:函数可以返回一个值或多个值(通过元组)
  • 可选返回:如果函数中没有return语句,或return语句没有指定值,则默认返回None
  • 任意位置的 return:函数中可以有多个return语句,但程序只会执行第一个遇到的return语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def analyze_number(num):
"""分析数字的性质"""
if num > 0:
return True, "正数"
elif num < 0:
return False, "负数"
else:
return None, "零" # 可以返回不同类型的对象

result = analyze_number(10)
print(result) # 输出:(True, '正数')

result_type = type(result)
print(f"返回值类型:{result_type.__name__}") # 输出:返回值类型:tuple

(4) 参数传递机制

python 采用的是”按对象引用传递”(pass by object reference)的参数传递机制,这与传统的”按值传递”和”按引用传递”有本质区别:

1
2
3
4
5
6
7
8
def modify_list(lst):
"""修改列表内容"""
lst.append(100) # 修改可变对象,外部列表会变化
print(f"函数内修改后:{lst}")

my_list = [1, 2, 3]
modify_list(my_list)
print(f"函数外列表:{my_list}") # 输出:函数外列表:[1, 2, 3, 100]

参数传递的底层规则

  • 不可变对象(如整数、字符串、元组):在函数内部修改形参时,实际上是创建了新的对象,原实参不受影响
  • 可变对象(如列表、字典):在函数内部修改形参时,实际是在修改原始对象,外部实参会看到变化
  • 默认参数陷阱:可变对象作为默认参数时,其值在函数定义时初始化一次,后续调用会共享该对象
1
2
3
4
5
6
7
8
def func(a, b=[]):
"""演示默认参数陷阱"""
b.append(a)
return b

print(func(1)) # [1]
print(func(2)) # [2, 1](因为默认参数b只在函数定义时创建一次)
print(func(3, [])) # [3](显式传递空列表可以避免共享)

⚠️ 警告:可变对象作为默认参数时,其值在函数定义时初始化一次,后续所有调用会共享该对象,可能导致意外的副作用。

二、函数的文档字符串


1. 重要与定义

文档字符串(docstring)是 python 中一种特殊的字符串,用于描述模块、类、函数或方法的功能、参数、返回值等信息。根据 PEP 257 规范,文档字符串应是函数/类/模块定义中的第一个语句,使用三引号包裹。

文档字符串的重要性主要体现在:

  • 代码自解释:帮助其他开发者(或未来的自己)快速理解代码功能
  • 自动化文档生成:通过工具如 Sphinx 将 docstring 转换为 HTML/PDF 文档
  • 代码测试:支持 doctest 等模块在 docstring 中编写示例代码并验证结果
  • IDE支持:主流 IDE(如PyCharm、VSCode)会解析 docstring 提供参数提示

2. 格式规范

python 社区存在多种 docstring 格式规范,主要的包括:

(1) PEP 257标准

PEP 257 是 python 官方关于文档字符串的规范,它主要规定了 docstring 的位置和基本格式:

  • 位置规则:必须位于函数/类/模块定义的第一条语句
  • 语法要求:使用三引号("""''')包裹,即使内容只有一行
  • 结构建议:单行 docstring 可简短描述功能;多行 docstring 通常包含功能描述、参数说明、返回值等

(2) Google 风格 docstring

Google 风格的 docstring 以简洁明了著称,格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def calculate_average(numbers):
"""计算一组数字的平均值。

Args:
numbers (list[int/float]): 包含数字的列表,元素必须为整数或浮点数

Returns:
float: 输入列表中数字的平均值

Raises:
ValueError: 如果输入列表为空
TypeError: 如果列表中包含非数值元素

Examples:
>>> calculate_average([10, 20, 30])
20.0
>>> calculate_average([-5, 15, 25])
11.666666666666666
"""
if not numbers:
raise ValueError("输入列表不能为空")

total = 0
for num in numbers:
if not isinstance(num, (int, float)):
raise TypeError(f"元素必须为数值类型,但得到:{type(num).__name__}")
total += num

return total / len(numbers)

(3) NumPy/SciPy 风格 docstring

NumPy 风格的 docstring 结构更为详细,适合科学计算和库开发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def calculate_root(value, degree=2):
"""计算给定值的n次根。

计算一个数值的n次根,其中n为正整数。如果数值为负且n为偶数,则返回NaN。

Parameters
----------
value : int or float
需要计算根的数值
degree : int, optional
根的次数,默认为2(平方根)

Returns
-------
float
输入值的n次根,如果输入无效则返回NaN

Raises
------
ValueError
如果degree不是正整数

See Also
--------
math.sqrt : 计算平方根的内置函数

Notes
-----
该函数使用牛顿迭代法计算近似值,精度为1e-10。

Examples
--------
计算平方根:
>>> calculate_root(25)
5.0

计算立方根:
>>> calculate_root(64, 3)
4.0

处理负数平方根:
>>> calculate_root(-8, 3)
-2.0

处理无效次数:
>>> calculate_root(16, -2)
Traceback (most recent call last):
...
ValueError: degree必须为正整数
"""
if not isinstance(degree, int) or degree <= 0:
raise ValueError("degree必须为正整数")

if value < 0 and degree % 2 == 0:
return float('NaN')

# 使用牛顿迭代法计算近似根
guess = value
while True:
new_guess = ((degree - 1) * guess + value / (guess ** (degree - 1))) / degree
if abs(new_guess - guess) < 1e-10:
break
guess = new_guess

return new_guess

(4) reStructuredText 风格 docstring

reST 风格的 docstring 使用特殊的标记语法,适合与 Sphinx 等文档生成工具配合使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def format_number(number, precision=2):
"""格式化数字为指定精度的字符串表示。

:param number: 要格式化的数字
:type number: int or float
:param precision: 格式化的精度,小数点后位数,默认为2
:type precision: int
:return: 格式化后的字符串,如"12.35"
:rtype: str

.. note:: 如果精度大于7,可能会丢失一些小数位精度

.. warning:: 如果输入非数值类型,会抛出TypeError异常

.. seealso:: :func:`round` - 内置的四舍五入函数

Examples
--------
格式化整数:
>>> format_number(123)
'123.00'

格式化浮点数:
>>> format_number(123.4567)
'123.46'

指定更高精度:
>>> format_number(3.1415926535, 5)
'3.14159'
"""
if not isinstance(number, (int, float)):
raise TypeError("number必须为数值类型")

if precision < 0:
precision = 0

return f"{round(number, precision):.{precision}f}"

3. 实际应用

(1) 使用 help() 函数查看文档

python 内置的help()函数可以利用 docstring 提供函数/类的帮助文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def greet(name):
"""向用户提供问候。

该函数接收用户姓名,并返回个性化的问候语。

参数:
name (str): 用户的姓名,长度不能为0

返回:
str: 问候字符串,如"Hello, Alice!"

异常:
ValueError: 如果name为空字符串
"""
if not isinstance(name, str):
raise TypeError("name必须为字符串类型")
if not name.strip():
raise ValueError("name不能为空字符串")

return f"Hello, {name}!"


help(greet) # 在交互式环境中调用会显示格式化的帮助文档

(2) 与 Sphinx 文档生成工具集成

Sphinx 是 python 社区最流行的文档生成工具,它可以利用 docstring 自动生成 API 文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 在代码文件中
def calculate_area(radius):
"""计算圆的面积。

使用公式πr²计算圆的面积。

参数:
radius (float): 圆的半径,必须大于0

返回:
float: 圆的面积,保留小数点后两位

异常:
ValueError: 如果半径小于等于0
"""
if radius <= 0:
raise ValueError("半径必须大于0")

import math
return math.pi * radius ** 2

# 在Sphinx的配置文件中
# 使用numpydoc扩展解析NumPy风格的docstring
extensions = ['numpydoc']

# 在Sphinx的文档文件中
.. automodule:: my_module
:members:
:undocified: False

(3) 使用doctest进行测试

doctest 模块允许在 docstring 中编写示例代码,并在运行时自动验证这些示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def factorial(n):
"""计算n的阶乘。

计算一个非负整数n的阶乘,即n! = n × (n-1) × ... × 1。

参数:
n (int): 非负整数

返回:
int: n的阶乘值

异常:
ValueError: 如果n不是非负整数
TypeError: 如果n不是整数类型

Examples:
>>> factorial(5)
120
>>> factorial(0)
1
>>> factorial(10)
3628800
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n必须是非负整数
"""
if not isinstance(n, int):
raise TypeError("n必须为整数类型")

if n < 0:
raise ValueError("n必须是非负整数")

result = 1
for i in range(1, n+1):
result *= i

return result


# 在命令行中运行:python -m doctest your_script.py
# 如果所有示例都通过,会显示"0 passed and 0 failed";否则会报告失败的测试

(4) IDE代码提示支持

主流 IDE 会解析 docstring 中的参数和返回值信息,提供代码补全和类型提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def connect_to_database(host, port=5432, username=None, password=None):
"""连接到数据库服务器。

该函数建立与数据库服务器的连接,并返回连接对象。

参数:
host (str): 数据库服务器主机地址
port (int, optional): 数据库端口,默认为5432
username (str, optional): 数据库用户名
password (str, optional): 数据库密码

返回:
DatabaseConnection: 数据库连接对象

异常:
ConnectionError: 如果无法连接到数据库
AuthenticationError: 如果认证失败
"""
# 连接数据库的实现代码...
pass

当在 PyCharm 中调用此函数时,IDE 会根据 docstring 中的参数描述提供参数提示,包括参数类型和说明信息。

4. 最佳实践

(1) 文档字符串的结构

根据 PEP 257 和主流规范,推荐的文档字符串结构如下:

  • 单行 docstring

    1
    2
    3
    def add(a, b):
    """返回两个数的和。"""
    return a + b

    注意:即使内容适合一行,也应使用三引号

  • 多行 docstring

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    def process_data(data, output_format='json'):
    """处理输入数据并以指定格式输出。

    该函数接收原始数据,进行一系列处理(如验证、转换),然后以指定的格式返回结果。

    参数:
    data (dict): 原始输入数据
    output_format (str, optional): 输出格式,可选值为'json'或'xml',默认为'json'

    返回:
    str: 格式化后的数据字符串

    异常:
    ValueError: 如果data不符合格式要求
    TypeError: 如果output_format不是'json'或'xml'
    """
    # 处理数据的代码...
    pass

(2) 文档字符串的编写建议

  1. 功能描述清晰:第一句应简洁明了地描述函数的主要功能
  2. 参数说明完整
    • 参数名称
    • 参数类型(可选)
    • 参数用途和含义
    • 参数的默认值(如果有的话)
  3. 返回值明确:说明返回值的类型、含义和可能的特殊情况
  4. 异常文档:列出函数可能抛出的异常及触发条件
  5. 示例代码:提供简单的使用示例,增强可理解性
  6. 保持一致性:在项目中统一使用一种 docstring 风格

(3) 文档字符串的编码规范

  1. 字符串引号:使用三引号( """ 或'''),即使内容只有一行
  2. 缩进规则:多行 docstring 的首行和末行的引号应与函数定义的缩进对齐
  3. 空行规则
    • 函数/方法的 docstring 前后不应有空行
    • 类的 docstring 后应有一个空行,以与类变量区分开
  4. 拼写与语法:确保 docstring 中的文本没有拼写错误和语法错误

python 函数是代码组织和抽象的核心工具,其定义、调用和返回值机制体现了 python 的简洁与高效。通过def关键字可以轻松定义函数,支持多种参数形式和灵活的返回值处理。函数的多返回值特性是 python 语言的一大亮点,它通过自动打包为元组的方式简化了返回多个值的操作。

文档字符串是 python 生态中代码文档化的重要机制,它不仅提高了代码的可读性,还支持自动化文档生成、代码测试和 IDE 提示等功能。

通过理解并正确使用函数和文档字符串,开发者可以编写出更加清晰、可维护和易于协作的 python 代码,这在长期项目开发和团队协作中尤为重要。