04|函数封装:消除重复代码的工程实践
场景引入:报表生成中的重复逻辑
你正在开发一个月度销售报表生成工具。计算总销售额、平均订单金额、同比增长率——这些逻辑在日报、周报、月报中都会用到。如果每次都复制粘贴同一段代码,一旦计算公式需要调整,你就得在多个地方同时修改,遗漏一处就会导致数据不一致。
解决方案是函数:将一段逻辑封装起来,命名后可在任意位置调用。函数是代码复用的基本单元,也是构建大型程序的基石。
核心原则:定义一次,调用多次。封装一个 calc_growth_rate(prev, curr) 函数后,所有需要计算增长率的地方只需调用它。如果公式有误,修改一处即可全局生效。
内置函数速览
Python 提供了 70 多个内置函数,以下是开发中使用频率最高的:
# abs() —— 绝对值
print(abs(-42)) # 42
# max() / min() —— 极值
print(max(7, 3, 9, 1)) # 9
print(min(7, 3, 9, 1)) # 1
# len() —— 长度
print(len("automation")) # 10
print(len([10, 20, 30])) # 3
# round() —— 四舍五入
print(round(2.71828, 2)) # 2.72
# sum() —— 求和
print(sum([100, 200, 300])) # 600
# sorted() —— 排序(返回新列表)
print(sorted([4, 2, 7, 1])) # [1, 2, 4, 7]
print(sorted([4, 2, 7], reverse=True)) # [7, 4, 2]
# type() —— 类型检查
print(type(3.14)) # <class 'float'>
类型转换函数:
print(int("256")) # 256
print(int(9.7)) # 9(截断)
print(float(7)) # 7.0
print(str(256)) # "256"
print(bool(0)) # False
print(bool("content")) # True
定义自定义函数
基本语法
def function_name(param1, param2, ...):
"""文档字符串(可选):描述函数的用途"""
# 函数体
return result
第一个自定义函数
def welcome(username):
"""向用户发送欢迎消息"""
print(f"欢迎回来,{username}!系统已就绪。")
welcome("operator_a") # 欢迎回来,operator_a!系统已就绪。
welcome("operator_b") # 欢迎回来,operator_b!系统已就绪。
welcome("admin") # 欢迎回来,admin!系统已就绪。
逻辑只写了一次,却能对不同用户重复使用。
带返回值的函数
def compute_tax(revenue, rate):
"""计算税额"""
return revenue * rate
tax_amount = compute_tax(10000, 0.13)
print(tax_amount) # 1300.0
print(compute_tax(5000, 0.06)) # 300.0
# 返回值可以参与后续运算
total_tax = compute_tax(10000, 0.13) + compute_tax(5000, 0.06)
print(total_tax) # 1600.0
return 语句的两个作用:
- 将值传回给调用方
- 立即终止函数执行
没有 return 语句(或 return 后无值)的函数返回 None:
def log_event(msg):
print(f"[LOG] {msg}")
result = log_event("启动完成") # 输出:[LOG] 启动完成
print(result) # None
返回多个值
Python 函数可以返回多个值(本质是返回元组):
def analyze_dataset(data):
"""返回数据集的最大值和最小值"""
ceiling = max(data)
floor = min(data)
return ceiling, floor
hi, lo = analyze_dataset([23, 7, 45, 12, 38])
print(f"最大值:{hi},最小值:{lo}")
# 最大值:45,最小值:7
占位函数:pass
在规划代码结构时,可以用 pass 占位:
def export_report():
pass # 暂未实现
# 不会报错,后续再填充具体逻辑
函数参数详解
参数机制让函数具备高度灵活性。Python 支持多种参数类型。
位置参数
最基本的参数形式,按声明顺序传入:
def calc_power(base, exp):
"""计算 base 的 exp 次方"""
result = 1
while exp > 0:
result *= base
exp -= 1
return result
print(calc_power(3, 4)) # 81
print(calc_power(10, 3)) # 1000
参数顺序很重要——calc_power(3, 4) 与 calc_power(4, 3) 结果不同。
默认参数
给参数预设默认值,调用时可省略:
def calc_power(base, exp=2):
"""默认计算平方"""
result = 1
while exp > 0:
result *= base
exp -= 1
return result
print(calc_power(7)) # 49(默认 exp=2)
print(calc_power(7, 3)) # 343(显式传入 exp=3)
**规则:**默认参数必须放在非默认参数之后。
def format_log(message, level="INFO"):
print(f"[{level}] {message}")
format_log("服务启动") # [INFO] 服务启动
format_log("连接超时", "WARNING") # [WARNING] 连接超时
关键陷阱:默认值不要使用可变对象
# 错误做法
def append_record(record, history=[]):
history.append(record)
return history
print(append_record("log_1")) # ['log_1']
print(append_record("log_2")) # ['log_1', 'log_2'] ← 共享了同一个列表
# 正确做法
def append_record(record, history=None):
if history is None:
history = []
history.append(record)
return history
print(append_record("log_1")) # ['log_1']
print(append_record("log_2")) # ['log_2']
这是 Python 中一个经典的陷阱,务必牢记。
可变位置参数(*args)
不确定调用方会传入多少个参数时,使用 *args:
def total_revenue(*amounts):
"""计算任意数量金额的总和"""
running_sum = 0
for val in amounts:
running_sum += val
return running_sum
print(total_revenue(100, 200)) # 300
print(total_revenue(100, 200, 300, 400)) # 1000
print(total_revenue()) # 0
# 已有列表可用 * 解包传入
monthly = [1200, 1500, 1100, 1800]
print(total_revenue(*monthly)) # 5600
函数内部,amounts 是一个元组。
可变关键字参数(**kwargs)
使用 **kwargs 接收任意数量的关键字参数:
def create_profile(uid, display_name, **metadata):
"""创建用户档案"""
print(f"用户ID:{uid}")
print(f"显示名:{display_name}")
for field, value in metadata.items():
print(f"{field}:{value}")
print()
create_profile("u001", "运营组长")
create_profile("u002", "技术主管", dept="研发部", location="上海")
# 已有字典可用 ** 解包传入
extra = {"dept": "市场部", "rank": "P7"}
create_profile("u003", "市场经理", **extra)
函数内部,metadata 是一个字典。
参数组合顺序
多种参数可以组合使用,但顺序有固定要求:**位置参数 -> 默认参数 -> *args -> 关键字专用参数(keyword-only)-> kwargs
def dispatch(action, target, *extra_targets, retries=3, **options):
print(f"action={action}, target={target}, retries={retries}")
print(f"extra_targets={extra_targets}")
print(f"options={options}")
dispatch("deploy", "server_1")
dispatch("deploy", "server_1", "server_2", "server_3", retries=5, region="east")
注意这里 retries=3 放在了 *extra_targets 之后。如果把默认参数放在 *args 之前,第三个位置参数会被赋给 retries 而非进入 extra_targets,容易造成混淆。将默认参数放在 *args 之后,调用方必须以关键字形式传入,语义更加明确。
实际开发中,最常用的组合是位置参数 + 默认参数。*args 和 **kwargs 主要用于设计需要高灵活性的接口。
变量作用域
变量的可访问范围取决于它在哪里被定义。
局部变量与全局变量
# 全局变量
app_version = "3.2.1"
def show_version():
# 局部变量
build_tag = "release"
print(app_version) # 可以读取全局变量
print(build_tag) # 可以读取局部变量
show_version()
print(app_version) # 全局变量在函数外可访问
# print(build_tag) # 报错:局部变量在函数外不可见
作用域规则:
- 函数外部定义的变量是全局变量
- 函数内部定义的变量是局部变量
- 函数内部可以读取全局变量
- 函数内部要修改全局变量,需用
global声明
connection_count = 0
def new_connection():
global connection_count
connection_count += 1
new_connection()
new_connection()
print(connection_count) # 2
**最佳实践:**尽量避免使用 global。函数应通过参数接收输入、通过返回值产出结果,不依赖外部状态。这样的函数更容易测试和复用。
# 不推荐
running_total = 0
def accumulate(val):
global running_total
running_total += val
# 推荐
def accumulate(current, val):
return current + val
running_total = accumulate(running_total, 50)
同名变量隔离
config = "global_config"
def load():
config = "local_config" # 新的局部变量,不影响全局
print(f"函数内:{config}")
load() # 函数内:local_config
print(f"函数外:{config}") # 函数外:global_config
递归函数
函数在内部调用自身,称为递归。递归适合”大问题可分解为结构相同的小问题”的场景。
阶乘示例
n 的阶乘定义:n! = n * (n-1) * ... * 1
递归视角:n! = n * (n-1)!
def factorial(n):
"""计算 n 的阶乘"""
if n <= 1:
return 1 # 基础条件(递归出口,0! = 1, 1! = 1)
return n * factorial(n - 1) # 递归步骤
print(factorial(6)) # 720
print(factorial(10)) # 3628800
递归的两个要素:
- 基础条件:终止递归的边界
- 递归步骤:将问题缩小为同结构的子问题
执行追踪 factorial(5):
factorial(5)
= 5 * factorial(4)
= 5 * 4 * factorial(3)
= 5 * 4 * 3 * factorial(2)
= 5 * 4 * 3 * 2 * factorial(1)
= 5 * 4 * 3 * 2 * 1
= 120
斐波那契数列
1, 1, 2, 3, 5, 8, 13, 21…(每项等于前两项之和)
def fib(n):
"""返回斐波那契数列第 n 项"""
if n <= 2:
return 1
return fib(n - 1) + fib(n - 2)
for pos in range(1, 12):
print(fib(pos), end=" ")
# 1 1 2 3 5 8 13 21 34 55 89
递归的局限与替代
Python 默认递归深度上限为 1000 层。超出会触发 RecursionError。对于需要深度递归的场景,建议改用循环:
def factorial_iter(n):
product = 1
for multiplier in range(1, n + 1):
product *= multiplier
return product
print(factorial_iter(1000)) # 正常运行
lambda 匿名函数
当需要一个简单的、一次性使用的小函数时,lambda 表达式比 def 更简洁:
基本语法
# lambda 参数: 表达式
# 等价于 def compute_margin(cost, revenue): return revenue - cost
compute_margin = lambda cost, revenue: revenue - cost
print(compute_margin(80, 120)) # 40
lambda 只能包含一个表达式,不支持复杂逻辑。
典型应用场景
排序:
products = [
{"label": "键盘", "cost": 299},
{"label": "鼠标", "cost": 79},
{"label": "显示器", "cost": 1899},
]
# 按价格升序
products.sort(key=lambda p: p["cost"])
for p in products:
print(f"{p['label']}: {p['cost']} 元")
# 鼠标: 79 元
# 键盘: 299 元
# 显示器: 1899 元
# 按价格降序
products.sort(key=lambda p: p["cost"], reverse=True)
# 按字符串长度排序
tags = ["deploy", "ci", "monitoring", "log"]
tags.sort(key=lambda t: len(t))
print(tags) # ['ci', 'log', 'deploy', 'monitoring']
# 函数工厂
def make_converter(factor):
"""返回一个乘以 factor 的转换函数"""
return lambda val: val * factor
km_to_miles = make_converter(0.621371)
kg_to_lbs = make_converter(2.20462)
print(km_to_miles(100)) # 62.1371
print(kg_to_lbs(75)) # 165.3465
常用高阶函数
map()——批量转换
readings = [1, 4, 9, 16, 25]
roots = list(map(lambda v: v ** 0.5, readings))
print(roots) # [1.0, 2.0, 3.0, 4.0, 5.0]
# 批量类型转换
raw_values = ["10", "20", "30"]
numeric = list(map(int, raw_values))
print(numeric) # [10, 20, 30]
filter()——条件筛选
signals = [12, -3, 7, -8, 15, 0, -1, 9]
positive_only = list(filter(lambda s: s > 0, signals))
print(positive_only) # [12, 7, 15, 9]
# 过滤空字符串
tokens = ["GET", "", "POST", "", "PUT"]
valid_tokens = list(filter(None, tokens))
print(valid_tokens) # ['GET', 'POST', 'PUT']
zip()——并行配对
departments = ["研发", "市场", "运维"]
headcounts = [45, 22, 18]
for dept, count in zip(departments, headcounts):
print(f"{dept}部:{count} 人")
# 快速构建字典
dept_map = dict(zip(departments, headcounts))
print(dept_map) # {'研发': 45, '市场': 22, '运维': 18}
enumerate()——带序号遍历
milestones = ["需求评审", "开发完成", "测试通过", "正式发布"]
for seq, milestone in enumerate(milestones, start=1):
print(f"阶段 {seq}:{milestone}")
综合实战:单位换算工具箱
def convert_temperature(value, direction):
if direction == "c2f":
return value * 9 / 5 + 32
elif direction == "f2c":
return (value - 32) * 5 / 9
else:
return "不支持的转换方向"
def convert_distance(value, direction):
if direction == "km2mi":
return value * 0.621371
elif direction == "mi2km":
return value / 0.621371
else:
return "不支持的转换方向"
def convert_weight(value, direction):
if direction == "kg2lb":
return value * 2.20462
elif direction == "lb2kg":
return value / 2.20462
else:
return "不支持的转换方向"
def unit_converter():
"""单位换算主程序"""
categories = {
"1": ("温度", convert_temperature, [("c2f", "摄氏→华氏"), ("f2c", "华氏→摄氏")]),
"2": ("距离", convert_distance, [("km2mi", "千米→英里"), ("mi2km", "英里→千米")]),
"3": ("重量", convert_weight, [("kg2lb", "千克→磅"), ("lb2kg", "磅→千克")]),
}
print("====== 单位换算工具 ======")
while True:
print("\n选择类别:")
for key, (name, _, _) in categories.items():
print(f" {key}. {name}")
print(" q. 退出")
choice = input("\n你的选择:")
if choice == "q":
print("再见!")
break
if choice not in categories:
print("无效选择")
continue
cat_name, converter, directions = categories[choice]
print(f"\n{cat_name}转换方向:")
for idx, (code, desc) in enumerate(directions, 1):
print(f" {idx}. {desc}")
dir_choice = int(input("选择方向:")) - 1
if dir_choice < 0 or dir_choice >= len(directions):
print("无效选择")
continue
direction_code = directions[dir_choice][0]
try:
input_val = float(input("输入数值:"))
except ValueError:
print("请输入有效数字")
continue
output_val = converter(input_val, direction_code)
print(f"\n结果:{input_val} → {output_val:.4f}")
unit_converter()
本章回顾
| 要点 | 内容 |
|---|---|
| 内置函数 | abs() max() len() sum() sorted() 等 |
| 定义函数 | def name(params): body |
| return | 返回值并终止函数,支持多返回值 |
| 位置参数 | 按声明顺序传入 |
| 默认参数 | 带默认值,调用时可省略 |
| *args | 接收任意数量位置参数(元组) |
| **kwargs | 接收任意数量关键字参数(字典) |
| 作用域 | 局部变量 vs 全局变量,global 声明 |
| 递归 | 函数调用自身,须有基础条件 |
| lambda | 匿名函数:lambda params: expr |
| 高阶函数 | map() filter() zip() enumerate() |
补充:文档字符串与函数帮助
Python 的文档字符串(docstring)不只是注释——它会被内置的 help() 函数读取并展示:
def calc_growth_rate(previous, current):
"""计算同比增长率。
Args:
previous: 上一期数值
current: 当前期数值
Returns:
增长率(浮点数),例如 0.15 表示增长 15%
"""
if previous == 0:
return float('inf')
return (current - previous) / previous
# 查看函数文档
help(calc_growth_rate)
养成编写 docstring 的习惯,不仅方便团队协作,也是专业代码的标志。
补充:函数作为一等公民
在 Python 中,函数是”一等公民”——它们可以赋值给变量、存入列表或字典、作为参数传递给其他函数:
def greet_cn(name):
return f"你好,{name}"
def greet_en(name):
return f"Hello, {name}"
# 函数赋值给变量
current_greeter = greet_cn
print(current_greeter("运维组")) # 你好,运维组
# 函数存入字典
greetings = {"cn": greet_cn, "en": greet_en}
lang = "en"
print(greetings[lang]("Team")) # Hello, Team
这种特性是 Python 强大灵活性的来源之一,也是理解后续装饰器、回调函数等高级概念的基础。
实践任务
- 编写
is_palindrome(text)函数,判断字符串是否为回文 - 编写
convert_temp(celsius)函数,将摄氏度转为华氏度 - 用递归实现斐波那契数列第 n 项的计算
- 编写
word_count(paragraph)函数,统计文本中的单词数 - 使用 lambda 和 sorted() 对商品列表按价格降序排列
- 编写
safe_div(numerator, denominator, fallback=0)函数,除以零时返回 fallback 值
下一章,我们将从一个数据管理项目出发,深入掌握列表、字典、元组和集合——Python 中组织和处理批量数据的核心工具。
购买课程解锁全部内容
零基础到独立开发:Python 自动化与 Web 实战
¥29.90