目录
一、函数目的:把重复劳动打包成可复用的“乐高积木”
二、函数定义:给解释器一张“图纸”
三、函数声明:Python 的“声明即定义”
四、函数调用:把图纸交成成品
五、函数形参:占位符的三种姿态
六、函数实参:调用时的“真材实料”
七、函数返回值:把加工后的产品递回去
八、函数的参数类型:从鸭子类型到类型提示
九、匿名函数:轻装上阵的 lambda
十、综合案例:一个迷你计算器
十一、结语:函数思维的力量
一、函数目的:把重复劳动打包成可复用的“乐高积木”
想象一下,你每天都要把一份 CSV 拆成三列、清洗空值、再生成统计图。如果每次复制粘贴代码,一个月下来,同一段逻辑会散落在十几个脚本里,改一次需求就要全局搜索替换。函数的诞生就是为了解决“复制粘贴地狱”:把“怎么做”封装成“一个名字”,把“变化的部分”抽象成参数,把“结果”通过返回值交回给调用者。函数不仅让代码更短,更重要的是让“意图”浮出水面——看到函数名就能猜到它要干什么。
二、函数定义:给解释器一张“图纸”
在 Python 里,定义函数就像递交一张盖房子的图纸。关键词 def 后面紧跟函数名,圆括号里是将来可能用到的“空位”,冒号表示图纸开始,缩进块则是施工细节。
def greet(name):
print(f"Hello, {name}!")
上面三行代码只声明了“存在一种能力”,并不会立即执行。解释器读到这段代码时,会在当前作用域里绑定名字 greet,指向一块崭新的函数对象,但房子还没动工。
三、函数声明:Python 的“声明即定义”
在 C 或 Java 里,声明和定义可以分离,前者告诉编译器“我有这么个东西”,后者给出真正实体。Python 没有头文件,也没有前置声明语法,def 既是声明也是定义。因此,任何写在模块顶层的 def 都会随着模块导入而立刻生效。如果你非要模拟“先声明后实现”的感觉,可以把实现推迟到另一个模块,再在 init.py 里导入,但那只是文件层面的组织,语义上依旧是“定义即声明”。
四、函数调用:把图纸交成成品
要真正让房子立起来,需要把图纸交给施工队——这就是调用。调用发生在函数名后加圆括号,括号里填上“具体数值”。
greet("Alice")
greet("Bob")
第一次调用时,Python 创建新的栈帧,把实参 "Alice" 绑定到形参 name,执行函数体,打印完成后栈帧销毁;第二次调用重复此过程,却互不影响,这就是函数的“隔离性”。
五、函数形参:占位符的三种姿态
形参(parameter)是图纸上的空位,告诉阅读者“这里将来会塞进来某个值”。Python 支持三种常见姿态:
位置形参:按顺序一一对应。
默认值形参:给空位一个备选答案,调用者可填可不填。
可变长形参:当空位数量不确定时,用 *args 收集多余位置参数,用 **kwargs 收集多余关键字参数。
def concat(sep, *words, suffix=""):
return sep.join(words) + suffix
print(concat("-", "2025", "07", "14")) # 2025-07-14
print(concat("/", "a", "b", "c", suffix="!")) # a/b/c!
六、函数实参:调用时的“真材实料”
实参(argument)是真正搬进场地的砖块,可分为位置实参、关键字实参、解包实参。关键字实参让调用者不再死记硬背顺序,解包实参则把列表或字典“摊平”成位置或关键字参数。
params = {"sep": "|", "suffix": "END"}
print(concat(**params, *["x", "y"])) # x|yEND
七、函数返回值:把加工后的产品递回去
函数不一定需要返回,但大多数函数都会把结果通过 return 交回给调用者。return 同时隐含“结束函数”的语义,之后的语句不再执行。Python 可以一次返回多个值,实质是打包成元组再解包。
def divide(a, b):
if b == 0:
return None, "division by zero"
return a / b, "ok"
result, msg = divide(10, 0)
print(msg) # division by zero
八、函数的参数类型:从鸭子类型到类型提示
Python 运行时不强制检查参数类型,秉承“如果它像鸭子,就叫它鸭子”的哲学。但大型项目里,类型提示能显著提高可读性与工具链体验。
from typing import List
def total(nums: List[int]) -> int:
return sum(nums)
上述代码不会阻止你传入字符串列表,但 IDE 会划线提醒,静态分析工具如 mypy 也能在 CI 阶段捕获潜在错误。
九、匿名函数:轻装上阵的 lambda
有时我们只想在排序 key 或回调里写两行表达式,为如此微小的需求单独起名显得小题大做。Python 提供 lambda 关键字,让函数可以“匿名”诞生。
pairs = [(1, 9), (2, 8), (3, 7)]
pairs.sort(key=lambda p: p[1]) # 按元组第二元素升序
print(pairs) # [(3, 7), (2, 8), (1, 9)]
lambda 的主体只能是单个表达式,不能出现赋值、while、for 等多行语句。它返回表达式的值,无需写 return。在函数式工具如 map、filter、reduce 里,lambda 能让代码保持紧凑:
from functools import reduce
nums = [1, 2, 3, 4]
square_sum = reduce(lambda x, y: x + y**2, nums, 0)
print(square_sum) # 30
十、综合案例:一个迷你计算器
让我们把上述知识点揉进一个 20 行的计算器,演示如何拆分子问题、复用函数、利用匿名函数。
#!/usr/bin/env python3
from typing import Callable, Dict
def add(x: float, y: float) -> float:
return x + y
def sub(x: float, y: float) -> float:
return x - y
def mul(x: float, y: float) -> float:
return x * y
def div(x: float, y: float) -> float:
if y == 0:
raise ValueError("Cannot divide by zero")
return x / y
# 把操作符映射到函数
ops: Dict[str, Callable[[float, float], float]] = {
"+": add,
"-": sub,
"*": mul,
"/": div,
}
def calculate(expr: str) -> float:
"""解析形如 '3 + 4' 的表达式"""
x_str, op, y_str = expr.split()
x, y = float(x_str), float(y_str)
if op not in ops:
raise ValueError("Unsupported operator")
return ops[op](x, y)
# 利用匿名函数批量测试
tests = ["2 + 3", "10 - 4", "5 * 6", "9 / 3"]
for t in tests:
result = calculate(t)
print(f"{t} = {result}")
我们定义了四个具名函数完成原子运算,用字典把符号映射到函数对象,calculate 负责解析与调度。测试阶段又用 lambda 快速生成表达式列表。整个脚本无全局变量,每个函数职责单一,易于单元测试。
十一、结语:函数思维的力量
从 greet 的“Hello, Alice”到计算器的“9 / 3 = 3.0”,我们经历了一次完整的函数思维训练:
先问目的——为什么要封装?
再谈形式——如何定义、如何调用?
最后落地——参数、返回值、类型、匿名函数如何组合出高可读、高复用的代码。
当你把业务逻辑不断拆成一个个小函数,再把小函数像乐高积木一样自由拼装,你会发现代码开始有了“弹性”:需求变更只需替换或新增积木,而不用推翻整座大楼。祝你玩得开心,码得优雅。