跳到主要内容

定义函数

函数是一段有名称、可以重复调用的代码。把一项任务定义成函数,可以减少重复代码,并让程序更容易阅读、测试和维护。

本文使用 Python 3.10 及以上版本。读完后,你应该能够:

  • 使用 def 定义和调用函数;
  • 区分打印结果与返回结果;
  • 理解局部作用域、函数对象和嵌套函数;
  • 使用文档字符串与类型注解说明函数用途。

定义并调用第一个函数​

使用 def 关键字定义函数:

def greet():
print("你好,Python!")


greet() # 你好,Python!

这段代码包含两个阶段:

  1. def greet(): 创建函数,并把它绑定到名称 greet;
  2. 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 后的同层代码不可达,应删除或调整控制流程。

一个函数承担太多职责​

同时读取文件、处理数据、打印结果并发送网络请求的函数很难复用和测试。可以按职责拆分成多个小函数,让输入和输出更明确。

练习​

  1. 定义 is_even(number),判断整数是否为偶数并返回布尔值。
  2. 定义 convert_minutes(minutes),返回由小时和剩余分钟组成的元组。
  3. 为计算圆面积的函数添加文档字符串和类型注解。
  4. 定义 make_prefixer(prefix),返回一个能给文本添加前缀的闭包。
  5. 把一个包含输入、计算和输出的长函数拆成三个职责单一的函数。

小结​

  • def 创建函数,调用时才执行函数体;函数体通过缩进确定范围。
  • 参数接收输入,return 返回结果并结束调用;没有返回值时结果为 None。
  • 局部变量只在函数中可见,优先通过参数和返回值传递数据。
  • 函数是对象,可以赋值、传递和返回;嵌套函数可以形成闭包。
  • 文档字符串描述用途,类型注解表达预期接口,但不会自动完成运行时校验。
  • 清晰的函数名称和单一职责比追求复杂写法更重要。