6. 把代码打包(上)——函数基础
读完本节,你能在 20 分钟内,用 def 把重复代码封装成函数,掌握位置 / 默认值 / 关键字 3 种参数,并用 docstring 写函数文档。
函数是“有名字的代码块”,核心目的是减少重复。
6.1 为什么用函数?
假设要打印 5 遍九九乘法表的一行:
print(f"1 × 1 = 1")
print(f"1 × 2 = 2")
print(f"1 × 3 = 3")
print(f"1 × 4 = 4")
print(f"1 × 5 = 5")
如果改成函数:
defprint_99(n):
print(f"1 × {n} = {n}")
for n inrange(1, 6):
print_99(n)
要改公式只改函数体,不用满世界找重复代码。
用函数的 3 个好处:
- 可读性:
greet(“小明”) 比 3 行 print 清楚得多 - 可维护
- 可复用
💡 小贴士:一个函数只做一件事。如果你在写函数时用了一大段注释解释“这个函数做什么”,大概率应该拆成多个函数。
6.2 定义与调用
defsay_hello(name): # def 关键字定义函数
"""打印问候语"""# docstring,后面 6.5 节会详细讲
print(f"你好,{name}!")
say_hello("小明") # 调用:传一个位置参数 name="小明"
输出:
你好,小明!
函数命名也有一些约定:
- 避免跟内置函数同名(
listdictstrid 等)
defcalculate_total(): # 好
defprocess_data(): # 好
deflist(): # 糟(覆盖了内置 list!)
defList(): # 合法但糟(易混)
💡 小贴士:函数先定义、后调用。把 say_hello(“小明”) 放到 def 上面会报 NameError。Python 自上而下执行,变量和函数都得先定义再使用。
6.3 没有 return 默认返回 None
defadd(a, b):
result = a + b
print(add(1, 2)) # None(函数没 return,默认返回 None)
要返回值必须用 return:
defadd(a, b):
return a + b
print(add(1, 2)) # 3
return 一执行,函数立即结束,后面的代码不跑:
defcheck(age):
if age < 0:
return"不合法"
if age < 18:
return"未成年"
return"成年"
print(check(25)) # 成年
一次返回多个值其实是元组(S4.3 节会展开):
defget_stats(numbers):
"""返回 (和, 平均, 最大, 最小)"""
returnsum(numbers), sum(numbers) / len(numbers), max(numbers), min(numbers)
total, avg, hi, lo = get_stats([1, 2, 3, 4, 5])
print(f"和={total} 平均={avg} 最大={hi} 最小={lo}")
# 和=15 平均=3.0 最大=5 最小=1
📌 对比 print vs return:
print(x)return x —— 把 x 作为函数的“答案”交回给调用者,不显示
新手最常犯的错:在函数里 print 完不 return,然后期望调用者拿到那个值 —— 拿到的是 None。记住:函数想“给外面东西”就用 return,屏幕显示只是副作用。
6.4 参数三种形态
形态 1:位置参数(按顺序传)
defgreet(name, msg):
print(f"{name},{msg}")
greet("小明", "你好") # 小明,你好
形态 2:默认值参数(不传就用默认值)
defgreet(name, msg="你好"): # 默认值参数必须放最后
print(f"{name},{msg}")
greet("小明") # 小明,你好
greet("小明", "早上好") # 小明,早上好
形态 3:关键字参数(按名字传,顺序无所谓)
defgreet(name, msg):
print(f"{name},{msg}")
greet(msg="早上好", name="小明") # 小明,早上好
📌 对比:位置参数必须按顺序,关键字参数按名字。混用时,关键字参数必须在位置参数后面:
greet("小明", msg="你好") # ✓ 合法
greet(name="小明", "你好") # ✗ SyntaxError
进阶:不定参数(*args / **kwargs)和仅关键字参数(* 之后只能关键字传):
defgreet(name, *, msg="你好"): # * 之后只能关键字传
print(f"{name},{msg}")
greet("小明") # 小明,你好
greet("小明", msg="嗨") # 小明,嗨
greet("小明", "嗨") # TypeError
这个写法常用于“必须显式标明”的参数,避免误用。
实参解包:用 * / ** 把列表 / 字典“展开”作为参数:
args = ["小明", "早上好"]
greet(*args) # 等价于 greet("小明", "早上好")
kwargs = {"name": "小明", "msg": "嗨"}
greet(**kwargs) # 等价于 greet(name="小明", msg="嗨")
这种写法在“配置参数”场景特别常见。
6.5 docstring 与类型提示
defcelsius_to_fahrenheit(c: float) -> float:
"""把摄氏度转华氏度
Args:
c: 摄氏度数值
Returns:
华氏度数值
"""
return c * 9 / 5 + 32
print(celsius_to_fahrenheit(25)) # 77.0
help(celsius_to_fahrenheit) # 在终端打印 docstring
Python 3.5+ 还可以加类型提示(type hint)—— : float 表示参数期望是浮点数,-> float 表示返回值是浮点数。类型提示不影响运行,但能让 IDE 帮你检查错误:
defadd(a: int, b: int) -> int:
return a + b
把鼠标悬停到 add("hi", "you") 上,VS Code 会提示“期望 int,实际 str”。这是大型项目里避免低级 bug 的利器。
类型提示还能写复杂结构(用 typing 模块):
from typing importList, Dict, Optional
defprocess_scores(scores: List[int], user: Optional[Dict] = None) -> float:
"""算平均分,user 可选"""
if user isNone:
user = {}
returnsum(scores) / len(scores) if scores else0.0
初学阶段只用最简单的标注(intstrfloatboolListDict)就够了。
lambda 表达式(匿名函数,适合一次性的小函数):
add = lambda a, b: a + b
print(add(3, 5)) # 8
# 常见用法:排序时传 key
nums = [(1, "b"), (3, "a"), (2, "c")]
nums.sort(key=lambda x: x[1]) # 按第二个元素排序
print(nums) # [(3, 'a'), (1, 'b'), (2, 'c')]
💡 小贴士:在 VS Code 里把鼠标悬停在函数名上,会自动显示 docstring。写函数时养成写 docstring + 类型提示的习惯,3 个月后回来看代码会感谢自己。Lambda 只用于“短小的一次性函数”,复杂逻辑还是用 def。
🎯 动手练
写一个函数 bmi(weight_kg, height_m),计算并返回 BMI 指数(公式:体重(kg) / 身高(m)²)。加 docstring 说明参数和返回值。再写一个函数 bmi_category(bmi),根据 BMI 返回分类(“偏瘦” / “正常” / “偏胖” / “肥胖”)。
预期:
print(bmi(70, 1.75)) # 22.857...
print(bmi_category(22.86)) # 正常
练习参考答案
defbmi(weight, height):
"""计算 BMI"""
return weight / height ** 2
defbmi_category(b):
if b < 18.5: return"偏瘦"
if b < 24: return"正常"
if b < 28: return"偏胖"
return"肥胖"
本节小结
- 函数 = 有名字的代码块,用
def 定义;函数命名用动词 / 动词短语,小写 + 下划线 - 一个函数只做一件事(Single Responsibility),复杂逻辑要拆
- 没有
return 默认返回 None;return 一执行函数立即结束 print 是副作用(显示),return 是返回值;两者用途不同,新手最常搞混- 三种参数:位置参数、默认值参数(放最后)、关键字参数
* 之后只能关键字传;*args / **kwargs 处理任意多参数(下节详讲)- docstring 写在函数第一行(三引号),
help(函数名) 查看 - Python 3.5+ 类型提示
: int-> float 让 IDE 帮你检查错误,不影响运行 - lambda 是匿名函数,适合一次性的小函数,复杂逻辑用
def