3.0 课前导入与学习路线¶
在真实的财务/会计场景里,我们几乎每天都要做两类事情:
- 分情况处理:例如不同逾期天数采用不同的坏账计提规则
- 批量处理:例如对一整张交易明细表逐行汇总、筛选、检查
这些任务背后对应的就是本章三件套:
- 条件语句:让程序做选择
- 循环与遍历:让程序批量执行
- 异常处理:让程序在遇到脏数据时仍然可控
本章学习目标¶
完成本章后,你应当能够:
- 读懂并写出常见的
if / elif / else结构,并能用布尔表达式组合条件 - 用
for遍历列表、元组、字符串、字典,并用range / enumerate / zip提升代码可读性 - 理解
while的适用场景:直到……为止,并能用break / continue控制循环 - 用
try / except处理常见报错,例如ValueError、ZeroDivisionError,让程序更稳健
# 示例:应收账款(Accounts Receivable)逾期天数与风险标签
days_past_due = 75 # 逾期天数
is_overdue = days_past_due > 0 # 请注意,这里的比较运算符是 `>`,而不是赋值运算符 `=`
is_high_risk = days_past_due >= 90
is_medium_risk = (days_past_due >= 60) and (days_past_due < 90)
is_medium_risk = (90 > days_past_due >= 60) # 也可以这样写,效果一样
print("is_overdue:", is_overdue)
print("is_high_risk:", is_high_risk)
print("is_medium_risk:", is_medium_risk)
3.1.2 “真值”规则(Truthiness)¶
在 if 条件: 里,条件不一定要写成 x > 0 这种比较式。很多对象会被自动当成“真/假”:
- 会被当成 False:
0、0.0、""(空字符串)、[](空列表)、{}(空字典)、None - 其他多数情况会被当成 True
这在“有没有数据”“列表是否为空”等场景非常常用。
# 示例:用“真值规则”判断是否有数据
transactions = [] # 空列表 => False
if transactions:
print("有交易数据,需要汇总。")
else:
print("没有交易数据,跳过汇总。")
note = "" # 空字符串 => False
if not note:
print("备注为空。")
3.1.3 if / elif / else 结构¶
基本语法:
if 条件1:
代码块1
elif 条件2:
代码块2
else:
代码块3
注意两点:
- 条件语句后面以冒号
:结尾 - 代码块要缩进(通常 4 个空格)
下面用“应收账款逾期分层”做一个完整的 if-elif-else 例子。
# 示例:应收账款逾期分层(aging bucket)
days_past_due = 120
if days_past_due <= 0:
bucket = "未逾期"
elif days_past_due <= 30:
bucket = "1-30天"
elif days_past_due <= 60:
bucket = "31-60天"
elif days_past_due <= 90:
bucket = "61-90天"
else:
bucket = "90天以上"
print("逾期天数:", days_past_due, "=> 分层:", bucket)
# if语句可以没有else部分
score = 85
if score >= 60:
print("及格")
# if-elif语句也可以没有else部分
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
print("分数:", score, "=> 等级:", grade)
3.1.4 嵌套判断(Nested if)¶
当“先判断大类,再判断子类”时,嵌套 if 很自然,例如:
先判断是否逾期
再判断是否需要计提(例如金额很小可忽略)
通过缩进来区分层级关系,注意不要混淆不同层级的条件语句。
# 示例:先判断是否逾期,再判断金额是否需要计提
days_past_due = 35
amount = 180 # 金额(元)
if days_past_due > 0:
# 已逾期
if amount >= 200:
action = "需要关注并计提/催收"
else:
action = "已逾期,但金额较小,记录即可"
else:
action = "未逾期"
print(action)
3.1.5 条件表达式(Conditional Expression)¶
有些“非常短的二选一”可以写成一行,也称为三元表达式(ternary expression):
结果 = A if 条件 else B
适合用在简单赋值上,不建议滥用;表达式过长会显著降低可读性。
# 示例:根据利润正负给出标签
profit = -12.5
# if profit >=0:
# label = '盈利'
# else:
# label = '亏损'
# print(profit, label)
# 以上四行代码与以下两行代码起到完全一模一样的效果
label = "盈利" if profit >= 0 else "亏损"
print(profit, label)
练习 3-1:费用报销规则判断¶
假设一笔报销包含两个字段:
category(类别):"travel"、"meal"、"other"amount(金额,元)
规则:
- travel 且金额 > 3000:标记为“需要主管审批”
- meal 且金额 > 500:标记为“需要说明”
- 其他情况:标记为“正常”
请补全下面代码,让它输出正确的 flag。
category = 'travel'
amount = 3200
if category == 'travel' and amount > 3000:
print('需要主管审批')
elif category == 'meal' and amount >500:
print('需要说明')
else:
print('正常')
需要主管审批
双击阅读参考答案
3.2 循环与遍历(Loops & Iteration)¶
本节先讲 for(更常用、遍历更自然),再讲 while(“直到……为止”的停止条件更灵活)。
在会计/财务的数据处理里,循环最常见的任务是:
- 对一批明细逐笔汇总(例如交易金额、费用报销)
- 对一张表逐行检查与标记(例如异常值、缺失值)
- 生成按期序列(例如按月折旧、分期还款表)
3.2.1 可迭代对象(Iterable)是什么?¶
能被 for ... in ... 逐个取出元素的对象,称为可迭代对象(iterable)。常见的有:
- 列表
list、元组tuple、字符串str、集合set- 字符串作为可迭代对象时,每次迭代会得到一个字符(长度为1的字符串)
- 字典
dict(默认遍历 key;也可以遍历values()、items())
你只需要记住一句话:只要能“一个一个拿出来处理”,就可以用 for。
# 遍历列表:逐个访问元素
amounts = [120.0, 88.5, 230.0] # 一组金额
for x in amounts:
print(x)
amounts = [120.0, 88.5, 230.0] # 一组金额
x = amounts[0]
print(x)
x = amounts[1]
print(x)
x = amounts[2]
print(x)
print('循环结束。')
# 但是这种方法在列表更新后无法自动更新遍历内容。相比之下,for循环的功能更加强大,可以随列表的变化进行实时更新。
120.0 88.5 230.0 循环结束。
amounts = [120.0, 88.5, 230.0] # 一组金额
for x in amounts:
x += 1
print(x)
121.0 89.5 231.0
# 遍历元组:逐个访问元素
t = ("Sales", "COGS", "Rent")
for x in t:
print(x)
3.2.2.3 遍历字典 dict:keys / values / items¶
字典也是可迭代对象。默认遍历的是“键”(keys)。常见三种遍历方式:
for k in d:或for k in d.keys():遍历键for v in d.values():遍历值for k, v in d.items():同时遍历键和值(最常用)
# 示例字典
d = {"Sales": 1700.50, "COGS": 630.20, "Rent": 15000.00}
# 1) 遍历键(默认就是键)
for k in d:
print("key =", k)
print("---")
# 也可以写得更明确:遍历键
for k in d.keys():
print("key =", k)
print("---")
# 2) 遍历值
for v in d.values():
print("value =", v)
print("---")
# 3) 同时遍历键和值(最常用)
for k, v in d.items():
print(k, "->", v)
# 遍历集合(了解):集合是无序的,遍历顺序不保证一致
s = {"b", "a", "c"}
for x in s:
print(x)
print("sorted iteration:")
for x in sorted(s): # sorted(...) 返回排好序的列表,顺序稳定
print(x)
3.2.3 for 循环¶
基础语法:
for 变量 in 可迭代对象:
循环体(重复执行)
for 的优势是:不容易写出死循环,适合“对一批数据逐个处理”。
这里的 变量是一个临时变量,可以用任意变量名。 依然需要注意冒号和缩进。
# 示例 1:批量汇总交易金额(收入为正,支出为负)
transactions = [100, -20, 300, -50]
total = 0
for x in transactions:
total += x # 逐笔累加
print("总额 total =", total)
transactions = [120, -30, 60, 80, -10, -50, 200, -40]
inflows = []
outflows = []
for amt in transactions:
if amt >= 0:
inflows.append(amt)
else:
outflows.append(amt)
print("inflows =", inflows)
print("outflows =", outflows)
print("净额 net =", sum(inflows) + sum(outflows))
例题:生成“累计余额曲线”(running balance)¶
很多财务图表/审计检查需要“逐笔累计”的余额序列。
给定交易列表 tx,生成一个新列表 balance_curve:
balance_curve[i]表示处理到第i+1笔交易后的累计余额
tx = [1000, -200, -150, 500, -120]
balance = 0
balance_curve = []
for amt in tx:
balance += amt
balance_curve.append(balance)
print("balance_curve =", balance_curve)
3.2.3.1 range:生成整数序列(常用于“固定次数”)¶
range 常见三种写法:
range(stop):0 到 stop-1 (左闭右开)range(start, stop):start 到 stop-1range(start, stop, step):步长为 step
注意: range 生成的序列从0开始,默认步长为1。
典型用途:12 个月、5 次迭代、从第 1 行到第 N 行。
list(range(5)), list(range(2, 8)), list(range(1, 10, 2))
total = 0
for i in range(1, 101):
total += i
print("1~100 总和 =", total)
3.2.3.2 enumerate:同时拿到索引和元素¶
当你既需要元素,也需要它的位置(索引 index)时,用 enumerate 更清晰:
for idx, value in enumerate(iterable, start=1):
...
下面用“逐笔打印交易编号”举例。
transactions = [100, -20, 300, -50]
for idx, amt in enumerate(transactions, start=1):
print(f"第{idx}笔金额 = {amt}")
--->
3.2.3.3 zip:并行遍历多个序列当两列数据需要“一一对应”处理时,用 zip 很方便(例如:科目名和金额一一对应)。¶
accounts = ["Cash", "A/R", "Inventory"]
amounts = [5000, 12000, 8000]
for acc, amt in zip(accounts, amounts):
print(acc, "=>", amt)
--->
例题:部门报销汇总(用 dict 累加)¶
输入:报销明细(部门、金额)
输出:每个部门的汇总金额(部门→合计)
提示:dict.get(key, default) 可以让“第一次出现的部门”更好处理。
records = [
{"dept": "Sales", "amount": 1200},
{"dept": "Sales", "amount": 800},
{"dept": "R&D", "amount": 2000},
{"dept": "HR", "amount": 500},
{"dept": "R&D", "amount": 700},
]
dept_sum = {}
for r in records:
dept = r["dept"]
amt = r["amount"]
dept_sum[dept] = dept_sum.get(dept, 0) + amt
print("dept_sum =", dept_sum)
3.2.3.4 break / continue / for-else:控制循环¶
break:立刻退出循环,需注意若有循环嵌套,break只会退出当前所在的那一层循环continue:跳过本轮,进入下一轮for-else:如果循环“没有被 break 打断”,就执行else
for-else 常用于“查找”:找到就 break;没找到就走 else。
# 示例:在列表中“找到第一个奇数”就停止(break)
nums = [2, 4, 6, 9, 10, 17] # 一组数字
for x in nums: # 逐个遍历 nums
if x % 2 == 1: # 判断 x 是否为奇数(除以 2 余 1)
print("找到奇数:", x) # 找到后输出
break # 立刻退出循环(不再继续往后找)
# 示例:跳过缺失值,汇总有效金额
amounts = [100, "N/A", 200, None, 50]
total = 0
for x in amounts:
if (x is None) or (x == "N/A"):
continue
total += x
print("total =", total)
# 示例:for-else(查找是否存在负数余额)
balances = [120, 80, 50, 0, 30]
for b in balances:
if b < 0:
print("发现负数余额:", b)
break
else:
print("未发现负数余额")
练习 3-2:用 for 计算 12 个月累计现金流¶
已知每个月的现金流(正数为流入,负数为流出),请用 for 计算:
- 全年累计现金流
total_cf - 现金流为正的月份个数
n_positive
要求:只用 for + if(不使用 sum() 直接求和)。
# 假设已有 12 个月现金流列表
cash_flows = [1200, -300, 500, -800, 0, 600, -200, 100, -50, 900, -400, 700]
total_cf = 0 # 全年累计现金流
n_positive = 0 # 现金流为正的月份个数
双击阅读参考答案
例题:现金余额逐月扣减,直到不足以支付(while)¶
cash = 1000 # 初始现金余额
monthly_payment = 230 # 每月支付金额
month = 0 # 定义初始值,记录循环的次数
while cash >= monthly_payment:
cash -= monthly_payment
month += 1 # 每次循环都要记得更新 month 的值,否则会死循环
print("可支付月数 =", month)
print("最后余额 =", cash)
例题:按月计提折旧,直到达到残值(while)¶
直线法折旧(straight-line depreciation)常见的循环思路是:
- 每月把账面价值(book value)减少一笔折旧
- 当继续计提会低于残值(salvage value)时停止
cost = 120000
salvage = 20000
life_months = 60
monthly_dep = (cost - salvage) / life_months
book_value = cost
m = 0
while book_value - monthly_dep >= salvage:
book_value -= monthly_dep
m += 1
print("已计提月数 =", m)
print("期末账面价值 =", round(book_value, 2))
balance = 10000
monthly_rate = 0.01 # 月利率 1%
payment = 300 # 每月固定还款
month = 0
max_months = 600
while (balance > 0) and (month < max_months):
interest = balance * monthly_rate
balance = balance + interest - payment
month += 1
if balance <= 0:
balance = 0
break
print("已还月数 =", month)
print("剩余余额 =", round(balance, 2))
if month == max_months:
print("提示:达到 max_months,说明还款额可能过小或需要调整参数。")
while True:
s = input("请输入金额,输入 q 退出:")
if s == "q":
break
amt = float(s)
print("你输入的金额是:", amt)
while 的执行顺序¶
- 先判断条件是否为真(True)
- 条件为真:执行循环体一次,然后回到第 1 步
- 条件为假:退出循环
写 while 时要同时想清楚两件事:
- 什么时候继续(条件)
- 什么时候一定会停(让条件变为 False,或用
break)
练习 3-3:用 while 模拟“库存清零”¶
仓库期初库存 stock,每周销量序列 sales(按顺序发生)。
请用 while 计算:库存最多能撑多少周(week)。
规则:
- 只要
stock > 0就继续循环 - 每循环一次:库存减少当周销量,周数加 1
- 若销量超过库存,则库存归零并停止(不出现负库存)
stock = 120
sales = [15, 20, 10, 25, 30, 40] # 每周销量(不固定)
双击阅读参考答案
3.2.5 for vs while:怎么选?¶
经验法则:
- 你有“一批数据”(列表/字典/表格行)要逐条处理 → 优先用
for - 你只知道“什么时候停”(余额不足、达到阈值、输入有效) → 更可能用
while
写 while 时要特别留意:循环体里是否会改变条件相关变量。
在批量处理中,如果你需要“无论成功/失败都要记录进度或做清理”,可以配合 try/finally,见 3.3.4。
3.3 异常处理语句(Exception Handling)¶
现实数据常常“不完美”:
- 金额字段里混入了
"N/A"、空字符串、None - 分母为 0(比如做比例时)
- 字典缺少某个键(字段缺失)
如果不做处理,程序可能直接报错并停止。异常处理的目标不是“隐藏问题”,而是:
- 让程序在可控范围内继续运行
- 把问题记录下来,便于后续清洗/回溯
3.3.1 最常见的几类错误¶
ValueError:类型转换失败(例如int("abc"))ZeroDivisionError:除以 0KeyError:字典没有这个键TypeError:类型不匹配(例如把字符串和数字直接相加)
下面用 try / except 来“兜底”。
# 示例:把字符串金额转成 float,并捕获 ValueError
raw_amount = "12.5a"
try:
amt = float(raw_amount)
print("转换成功:", amt)
except ValueError:
print("转换失败:raw_amount =", raw_amount)
raw_amounts = ["100", "20.5", "N/A", "", None, "30"]
valid = []
invalid = []
for x in raw_amounts:
try:
amt = float(x)
valid.append(amt)
except (ValueError, TypeError):
invalid.append(x)
print("valid:", valid)
print("invalid:", invalid)
print("sum(valid) =", sum(valid))
3.3.3 捕获“具体异常” vs 捕获“所有异常”¶
课堂建议:
- 尽量捕获你能预期到的异常类型(例如
ValueError、TypeError) - 不要随手写
except Exception:然后什么都不做;这会把真正的程序错误也“吞掉”,后续很难排查
下面给出一个“安全除法”的例子:利润率 = profit / revenue。
def safe_margin(profit, revenue):
"""安全计算利润率:遇到分母为0则返回 None"""
try:
return profit / revenue
except ZeroDivisionError:
return None
print(safe_margin(10, 100))
print(safe_margin(10, 0))
3.3.4 try/except/else/finally¶
else:当try块没有报错时才执行(用于把“成功路径”写得更清楚)finally:无论是否报错都会执行(常用于资源释放与收尾工作,例如关闭文件、写进度、回滚临时状态)
先用一个很短的示例熟悉结构。
raw = "88"
try:
x = int(raw)
except ValueError:
print("raw 不是整数")
else:
print("转换成功,x =", x)
finally:
print("finally:这句一定会执行")
finally 在循环中的常见用法:每轮处理都做“收尾动作”¶
批量处理时常见的需求是:
- 本轮成功/失败都要记录进度(否则中途报错就不知道处理到哪)
- 本轮结束都要做一次清理(例如重置临时变量)
下面的例子演示:即使遇到异常值或触发 break,finally 也会执行。
items = [10, 5, 0, 2]
for x in items:
try:
print("处理 x =", x)
if x == 0:
print("遇到关键值:停止处理")
break
y = 100 / x
print("结果 y =", y)
finally:
# 真实业务里这里可能是:写进度日志、关闭资源、提交/回滚等
print("finally:完成本轮收尾")
练习 3-4:批量计算“单笔税额”,并记录问题行¶
你有一批报销记录,每条记录包含:
amount(金额,字符串或数字)tax_rate(税率,例如 0.06)
要求:
- 计算每条记录的
tax = amount * tax_rate - 遇到无法转换的金额(例如
"N/A"、空值)时,不报错,改为把该条记录加入bad_records - 输出
tax_list(每条记录的税额,坏记录可跳过)
双击阅读参考答案
3.4 本章小结与自测¶
小结(你现在应该会)¶
- 会写
if / elif / else,并能用and / or / not组合条件 - 会用
for遍历列表/字典,能用range / enumerate / zip - 理解
while的适用场景,并能用break / continue控制循环 - 会用
try / except处理常见异常,让批量处理更稳健
快速自测¶
if []:会进入 if 分支吗?为什么?break和continue的区别是什么?- 为什么不建议无脑写
except Exception: pass? for k, v in d.items():中,items()返回的是什么结构? --->