定义函数
函数是一段有名称、可以重复调用的代码。把一项任务定义成函数,可以减少重复代码,并让程序更容易阅读、测试和维护。
本文使用 Python 3.10 及以上版本。读完后,你应该能够:
- 使用
def定义和调用函数; - 区分打印结果与返回结果;
- 理解局部作用域、函数对象和嵌套函数;
- 使用文档字符串与类型注解说明函数用途。
定义并调用第一个函数
使用 def 关键字定义函数:
def greet():
print("你好,Python!")
greet() # 你好,Python!
这段代码包含两个阶段:
def greet():创建函数,并把它绑定到名称greet;greet()调用函数,函数体才会真正执行。
函数定义的基本结构如下:
def 函数名(参数列表):
函数体
return 返回值
- 函数名后必须有圆括号和冒号;
- 函数体必须缩进,通常使用 4 个空格;
- 参数和
return都可以省略; - 函数体暂时没有内容时,可以使用
pass占位。
def todo():
pass
函数名应该表达行为
Python 函数名通常使用小写字母和下划线,即 snake_case 风格:
def calculate_total():
pass
def send_email():
pass
名称应说明函数“做什么”。calculate_total 比 handle 更具体,is_valid、has_permission 等名称则适合返回布尔值的判断函数。
函数名必须遵守变量命名规则,不能以数字开头,也不能使用 for、class 等关键字。不要用 list、str、sum 等名称覆盖常用内置对象。
参数让函数接收数据
函数可以通过参数接收调用方提供的数据:
def greet(name):
return f"你好,{name}!"
message = greet("小林")
print(message) # 你好,小林!
定义中的 name 是形参,调用时传入的 "小林" 是实参。参数的种类、默认值和传递规则将在“函数的参数”一文中详细介绍。
使用 return 返回结果
return 把计算结果交给调用方,并立即结束本次函数调用:
def absolute(number):
if number >= 0:
return number
return -number
result = absolute(-8)
print(result) # 8
返回值可以被保存、参与运算,也可以继续传给其他函数:
print(absolute(-3) + 10) # 13
打印不等于返回
print() 只负责把内容显示到终端,return 才会把值交给调用方:
def show_total(left, right):
print(left + right)
result = show_total(2, 3) # 先输出 5
print(result) # 再输出 None
没有执行到 return 的函数会返回 None。需要复用计算结果时,应返回数据,而不是只打印数据。
返回多个值
逗号分隔的多个返回值会被组合成元组:
def get_min_max(numbers):
return min(numbers), max(numbers)
lowest, highest = get_min_max([8, 3, 12])
print(lowest, highest) # 3 12
函数实际上只返回了一个元组,左侧的两个变量通过解包分别接收其中的值。相关数据较多或含义不够明显时,返回字典、数据类或其他对象会更清晰。
函数体中的变量作用域
函数内部创建的变量通常是局部变量,只能在本次调用中使用:
def calculate_price():
subtotal = 100
discount = 20
return subtotal - discount
print(calculate_price()) # 80
# print(subtotal) # NameError
函数可以读取外层变量:
tax_rate = 0.06
def add_tax(price):
return price * (1 + tax_rate)
如果在函数内给同名变量赋值,Python 默认把它视为新的局部变量:
count = 10
def reset_count():
count = 0
return count
print(reset_count()) # 0
print(count) # 10
global 可以声明要修改模块级变量,但它会增加隐藏状态,使函数更难测试。通常更推荐通过参数接收数据并返回新值:
def increment(count):
return count + 1
count = increment(count)
定义发生在运行时
Python 从上到下执行模块。执行到 def 时才创建函数对象,因此调用通常要放在定义之后:
def main():
print("程序开始")
main()
函数体中的名称会在函数被调用时查找,所以函数可以调用定义在模块后面的另一个函数,但必须等相关定义都执行完再开始调用:
def main():
show_message()
def show_message():
print("准备完成")
main()
函数也是对象
函数可以像字符串或列表一样赋值给变量、放入容器、作为实参传递或作为返回值返回:
def double(number):
return number * 2
operation = double
print(operation(5)) # 10
注意 double 与 double() 的区别:前者表示函数对象,后者表示调用函数并取得结果。
把函数传给另一个函数,是回调和高阶函数的基础:
numbers = [3, 1, 2]
numbers.sort(key=absolute)
print(numbers) # [1, 2, 3]
嵌套函数和闭包
函数可以定义在另一个函数内部。内部函数通常只服务于外层函数:
def format_user(name):
def clean(text):
return text.strip().title()
return clean(name)
print(format_user(" alice ")) # Alice
内部函数还可以记住外层函数中的变量,这种组合称为闭包:
def make_multiplier(factor):
def multiply(number):
return number * factor
return multiply
triple = make_multiplier(3)
print(triple(4)) # 12
如果内部函数需要修改外层函数的局部变量,要使用 nonlocal。不过共享可变状态容易增加理解成本,简单场景优先返回新值。
使用文档字符串说明用途
函数体的第一条字符串字面量是文档字符串(docstring):
def area_of_rectangle(width, height):
"""计算并返回矩形面积。"""
return width * height
可以通过 help(area_of_rectangle) 或 area_of_rectangle.__doc__ 查看它。简短函数用一句话说明用途即可;公共函数还可补充参数、返回值、异常和示例。
注释通常解释某段实现为什么这样写,文档字符串则说明函数提供什么能力。函数本身足够清楚时,不必重复代码内容。
使用类型注解表达接口
类型注解可以说明参数和返回值的预期类型:
def calculate_average(scores: list[float]) -> float:
return sum(scores) / len(scores)
注解能帮助编辑器、读者和类型检查工具理解代码,但 Python 默认不会在运行时强制检查它:
def repeat(text: str, times: int) -> str:
return text * times
类型注解不是输入校验。来自用户、文件或网络的数据仍需在运行时验证。
简短函数与 lambda
lambda 可以创建只包含一个表达式的匿名函数:
words = ["pear", "fig", "watermelon"]
words.sort(key=lambda word: len(word))
它适合一次性的简短操作。逻辑需要多行、文档、类型注解或复用时,应使用普通 def,并给函数起清楚的名称。
程序入口的常见写法
可执行模块通常把主流程放进 main(),再判断模块是否被直接运行:
def main():
print("运行主程序")
if __name__ == "__main__":
main()
直接执行文件时,条件成立;其他模块导入该文件时,函数会被定义,但 main() 不会自动运行。这能避免导入行为与主程序执行混在一起。
常见错误
忘记调用括号
result = calculate_price # 函数对象
result = calculate_price() # 函数调用结果
缩进不一致
函数体必须保持一致缩进。不要混用制表符和空格,否则可能产生 IndentationError 或意外的代码层级。
在 return 后继续写有效逻辑
def example():
return "完成"
print("不会执行")
return 后的同层代码不可达,应删除或调整控制流程。
一个函数承担太多职责
同时读取文件、处理数据、打印结果并发送网络请求的函数很难复用和测试。可以按职责拆分成多个小函数,让输入和输出更明确。
练习
- 定义
is_even(number),判断整数是否为偶数并返回布尔值。 - 定义
convert_minutes(minutes),返回由小时和剩余分钟组成的元组。 - 为计算圆面积的函数添加文档字符串和类型注解。
- 定义
make_prefixer(prefix),返回一个能给文本添加前缀的闭包。 - 把一个包含输入、计算和输出的长函数拆成三个职责单一的函数。
小结
def创建函数,调用时才执行函数体;函数体通过缩进确定范围。- 参数接收输入,
return返回结果并结束调用;没有返回值时结果为None。 - 局部变量只在函数中可见,优先通过参数和返回值传递数据。
- 函数是对象,可以赋值、传递和返回;嵌套函数可以形成闭包。
- 文档字符串描述用途,类型注解表达预期接口,但不会自动完成运行时校验。
- 清晰的函数名称和单一职责比追求复杂写法更重要。