aoi学院

Aisaka's Blog, School of Aoi, Aisaka University

Python-第16篇《个人税务助手》

导语

上篇我们解放了双手,批量处理Excel的效率立竿见影。但财务工作中还有个“每年一度”的重头戏——个人所得税!今天咱们打造一位贴身的税务管家:它懂得最新的个税算法,帮你管理七项专项附加扣除,年终还能一键生成汇算清单。


本篇目标

  • 理解个税累计预扣法的计算逻辑
  • 实现自动计算每月应缴个税
  • 管理专项附加扣除项目
  • 生成年度汇算清缴报告
  • 预测年度退税或补税金额

一、准备工作:安装库

1
2
3
4
5
6
7
8
# 命令行运行
# pip install pandas openpyxl

import pandas as pd
import json
from datetime import datetime
from typing import Dict
import os

二、个税计算原理(说人话版)

2025年的个税采用累计预扣法,听起来吓人,其实很讲道理:

  1. 今年赚了多少(累计收入)
  2. 能扣多少(累计五险一金 + 累计基本减除费用60,000 + 累计专项附加扣除)
  3. 差额部分按税率表算税
  4. 减去之前月份已交的税

税率表速查(2025年):

累计应纳税所得额税率速算扣除数
≤ 36,0003%0
36,000-144,00010%2,520
144,000-300,00020%16,920
…以此类推
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# ========== 个税计算引擎 ==========
# 2025年个税税率表(综合所得适用)
TAX_BRACKETS = [
(0, 36000, 0.03, 0),
(36000, 144000, 0.10, 2520),
(144000, 300000, 0.20, 16920),
(300000, 420000, 0.25, 31920),
(420000, 660000, 0.30, 52920),
(660000, 960000, 0.35, 85920),
(960000, float('inf'), 0.45, 181920)
]

def calculate_monthly_tax(
monthly_income: float,
monthly_insurance: float,
special_deductions: Dict[str, float],
months_passed: int = 1,
previous_tax: float = 0
) -> Dict:
"""
计算单月个税(累计预扣法)

参数:
monthly_income: 本月收入
monthly_insurance: 本月五险一金
special_deductions: 专项附加扣除累计
months_passed: 已过月份数(1-12)
previous_tax: 之前月份已缴税额
"""
# 1. 计算累计值
cumulative_income = monthly_income * months_passed
cumulative_insurance = monthly_insurance * months_passed
cumulative_basic_deduction = 5000 * months_passed # 基本减除费用5000/月
cumulative_special = sum(special_deductions.values()) * months_passed

# 2. 计算累计应纳税所得额
taxable_income = cumulative_income - cumulative_insurance - cumulative_basic_deduction - cumulative_special

if taxable_income <= 0:
return {
"本月应缴个税": 0,
"累计应缴个税": 0,
"累计已缴个税": previous_tax,
"累计应纳税所得额": taxable_income,
"适用税率": 0,
"速算扣除数": 0
}

# 3. 查找适用税率
for low, high, rate, deduction in TAX_BRACKETS:
if low < taxable_income <= high:
# 4. 计算累计应缴税额
cumulative_tax = taxable_income * rate - deduction
current_month_tax = cumulative_tax - previous_tax

return {
"本月应缴个税": round(current_month_tax, 2),
"累计应缴个税": round(cumulative_tax, 2),
"累计已缴个税": round(previous_tax, 2),
"累计应纳税所得额": round(taxable_income, 2),
"适用税率": f"{int(rate*100)}%",
"速算扣除数": deduction
}

return {"error": "计算异常"}

# 测试个税计算
def test_tax():
print("🧮 个税计算测试:")

# 案例:小王月薪20,000,五险一金3,000,有房贷利息专项扣除
result = calculate_monthly_tax(
monthly_income=20000,
monthly_insurance=3000,
special_deductions={"住房贷款利息": 1000},
months_passed=6, # 计算6月份个税
previous_tax=4320 # 前5个月已缴4320元
)

for key, value in result.items():
print(f" {key}: {value}")

# test_tax()

三、专项附加扣除管理

七项扣除项:子女教育、继续教育、大病医疗、住房贷款利息、住房租金、赡养老人、3岁以下婴幼儿照护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# ========== 专项附加扣除管理 ==========
class SpecialDeductionManager:
"""专项附加扣除管理器"""

# 扣除标准(2025年)
DEDUCTION_STANDARDS = {
"子女教育": 2000, # 每个子女每月
"继续教育": 400, # 学历教育每月
"大病医疗": 0, # 年度汇算时据实扣除
"住房贷款利息": 1000, # 每月
"住房租金": 1500, # 直辖市标准,其他1100或800
"赡养老人": 3000, # 独生子女每月
"3岁以下婴幼儿照护": 2000 # 每个婴幼儿每月
}

def __init__(self, data_file="deductions.json"):
self.data_file = data_file
self.deductions = self.load_deductions()

def load_deductions(self):
"""加载扣除项"""
if os.path.exists(self.data_file):
try:
with open(self.data_file, 'r', encoding='utf-8') as f:
return json.load(f)
except:
pass
return {}

def save_deductions(self):
"""保存扣除项"""
with open(self.data_file, 'w', encoding='utf-8') as f:
json.dump(self.deductions, f, ensure_ascii=False, indent=2)

def add_deduction(self, category: str, details: str, amount: float = None):
"""添加扣除项"""
if category not in self.DEDUCTION_STANDARDS:
print(f"⚠️ 无效的扣除类别:{category}")
print(f"可选类别: {list(self.DEDUCTION_STANDARDS.keys())}")
return

if amount is None:
amount = self.DEDUCTION_STANDARDS[category]

# 验证金额合理性
if amount > self.DEDUCTION_STANDARDS[category] * 1.2:
print(f"⚠️ 注意:{category}标准金额为{self.DEDUCTION_STANDARDS[category]}元")
confirm = input("是否继续?(y/n): ")
if confirm.lower() != 'y':
return

self.deductions[category] = {
"金额": amount,
"详情": details,
"添加时间": datetime.now().strftime('%Y-%m-%d')
}
self.save_deductions()
print(f"✅ 已添加 {category}: ¥{amount}/月 - {details}")

def remove_deduction(self, category: str):
"""删除扣除项"""
if category in self.deductions:
del self.deductions[category]
self.save_deductions()
print(f"✅ 已删除 {category}")
else:
print(f"⚠️ 未找到 {category}")

def list_deductions(self):
"""列出所有扣除项"""
print("\n" + "=" * 50)
print("专项附加扣除清单")
print("=" * 50)

if not self.deductions:
print("📭 暂无扣除项")
return {}

total_monthly = 0
for cat, info in self.deductions.items():
print(f"\n🏷️ {cat}:")
print(f" 金额: ¥{info['金额']}/月")
print(f" 详情: {info['详情']}")
print(f" 添加时间: {info['添加时间']}")
total_monthly += info['金额']

print(f"\n💡 合计每月可扣除: ¥{total_monthly}")
print(f"💰 年度可扣除: ¥{total_monthly * 12}")

return self.deductions

def manage_deductions():
"""交互式管理扣除项"""
manager = SpecialDeductionManager()

while True:
print("\n" + "=" * 45)
print("专项附加扣除管理")
print("=" * 45)
print("1. 添加扣除项")
print("2. 删除扣除项")
print("3. 查看所有扣除项")
print("4. 返回上级菜单")
print("=" * 45)

choice = input("请选择: ").strip()

if choice == "1":
print("\n📝 添加扣除项")
print("可选类别:")
for i, cat in enumerate(manager.DEDUCTION_STANDARDS.keys(), 1):
print(f" {i}. {cat}")

cat_input = input("\n请输入类别名称: ").strip()
if cat_input not in manager.DEDUCTION_STANDARDS:
print("❌ 类别名称错误!")
continue

details = input("详细说明: ").strip()
custom_amount = input("金额(直接回车使用标准): ").strip()

if custom_amount:
try:
amount = float(custom_amount)
manager.add_deduction(cat_input, details, amount)
except:
print("❌ 金额格式错误")
else:
manager.add_deduction(cat_input, details)

elif choice == "2":
manager.list_deductions()
cat = input("\n要删除的类别名称: ").strip()
manager.remove_deduction(cat)

elif choice == "3":
manager.list_deductions()
input("\n按回车键继续...")

elif choice == "4":
break

四、年度汇算准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# ========== 年度汇算清缴 ==========
class TaxYearCalculator:
"""年度个税计算器"""

def __init__(self, deduction_manager: SpecialDeductionManager):
self.dm = deduction_manager
self.records = []

def add_monthly_record(self, month: int, income: float, insurance: float, tax_paid: float):
"""添加月度数据"""
self.records.append({
"月份": month,
"收入": income,
"五险一金": insurance,
"已缴个税": tax_paid
})
print(f"✅ 已记录 {month}月数据")

def generate_annual_report(self):
"""生成年度汇算报告"""
if not self.records:
print("⚠️ 暂无月度数据")
return

print("\n" + "=" * 55)
print("年度个税汇算清缴报告")
print("=" * 55)

df = pd.DataFrame(self.records)

# 计算累计值
total_income = df['收入'].sum()
total_insurance = df['五险一金'].sum()
total_tax_paid = df['已缴个税'].sum()

# 获取专项附加扣除
special_deductions = self.dm.list_deductions()
total_special = sum(v['金额'] for v in special_deductions.values()) * 12

# 计算应纳税所得额
taxable_income = total_income - total_insurance - 60000 - total_special

print(f"\n💰 收入情况:")
print(f" 年度总收入: ¥{total_income:,.2f}")
print(f" 年度五险一金: ¥{total_insurance:,.2f}")
print(f" 年度专项附加扣除: ¥{total_special:,.2f}")
print(f" 基本减除费用: ¥60,000.00")

print(f"\n📊 应纳税所得额: ¥{taxable_income:,.2f}")

if taxable_income <= 0:
print("🎉 恭喜!您无需缴纳个税")
return

# 计算应缴个税
for low, high, rate, deduction in TAX_BRACKETS:
if low < taxable_income <= high:
annual_tax = taxable_income * rate - deduction
break

print(f"\n💸 个税计算:")
print(f" 适用税率: {int(rate*100)}%")
print(f" 速算扣除数: ¥{deduction:,}")
print(f" 年度应缴个税: ¥{annual_tax:,.2f}")
print(f" 年度已缴个税: ¥{total_tax_paid:,.2f}")

# 退税/补税判断
diff = annual_tax - total_tax_paid

if diff > 0:
print(f"\n⚠️ 需要补税: ¥{diff:,.2f}")
print(" 请在次年3-6月通过个税APP办理汇算并补缴")
elif diff < 0:
print(f"\n🎉 可以退税: ¥{abs(diff):,.2f}")
print(" 请在次年3-6月通过个税APP申请退税")
else:
print(f"\n✅ 已缴税额与应缴税额一致,无需补退")

# 生成报表文件
report_data = {
"项目": ["年度总收入", "年度五险一金", "专项附加扣除", "基本减除费用", "应纳税所得额",
"年度应缴个税", "年度已缴个税", "退税/补税金额"],
"金额": [total_income, total_insurance, total_special, 60000, taxable_income,
annual_tax, total_tax_paid, abs(diff) if diff != 0 else 0],
"说明": ["", "", "", "", "", "", "", "正数为补税,负数为退税"]
}

report_df = pd.DataFrame(report_data)
filename = f"个税汇算报告_{datetime.now().strftime('%Y%m%d')}.xlsx"
report_df.to_excel(filename, index=False)

print(f"\n✅ 详细报告已保存: {filename}")

def input_monthly_data():
"""交互式输入月度数据"""
calculator = TaxYearCalculator(deduction_manager)

while True:
print("\n" + "=" * 40)
print("📅 录入月度工资数据")
print("=" * 40)

month = input("月份(1-12,或q退出): ").strip()
if month.lower() == 'q':
break

try:
month = int(month)
if not 1 <= month <= 12:
print("月份必须在1-12之间!")
continue
except:
print("请输入数字!")
continue

try:
income = float(input("本月收入: ").strip())
insurance = float(input("本月五险一金: ").strip())
tax_paid = float(input("本月已缴个税: ").strip())

calculator.add_monthly_record(month, income, insurance, tax_paid)
except:
print("❌ 金额格式错误!")

another = input("\n继续录入下一个月?(y/n): ").strip()
if another.lower() != 'y':
break

return calculator

五、完整项目代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# ========== 个人税务助手 - 完整版 ==========
import json
import pandas as pd
from datetime import datetime
from typing import Dict
import os

# 2025年个税税率表
TAX_BRACKETS = [
(0, 36000, 0.03, 0),
(36000, 144000, 0.10, 2520),
(144000, 300000, 0.20, 16920),
(300000, 420000, 0.25, 31920),
(420000, 660000, 0.30, 52920),
(660000, 960000, 0.35, 85920),
(960000, float('inf'), 0.45, 181920)
]

class SpecialDeductionManager:
"""专项附加扣除管理"""

DEDUCTION_STANDARDS = {
"子女教育": 2000, "继续教育": 400, "大病医疗": 0,
"住房贷款利息": 1000, "住房租金": 1500,
"赡养老人": 3000, "3岁以下婴幼儿照护": 2000
}

def __init__(self, data_file="deductions.json"):
self.data_file = data_file
self.deductions = self.load_deductions()

def load_deductions(self):
if os.path.exists(self.data_file):
try:
with open(self.data_file, 'r', encoding='utf-8') as f:
return json.load(f)
except:
pass
return {}

def save_deductions(self):
with open(self.data_file, 'w', encoding='utf-8') as f:
json.dump(self.deductions, f, ensure_ascii=False, indent=2)

def add_deduction(self, category: str, details: str, amount: float = None):
if category not in self.DEDUCTION_STANDARDS:
print(f"❌ 无效类别: {category}")
return False

if amount is None:
amount = self.DEDUCTION_STANDARDS[category]

self.deductions[category] = {
"金额": amount,
"详情": details,
"添加时间": datetime.now().strftime('%Y-%m-%d')
}
self.save_deductions()
print(f"✅ 已添加 {category}: ¥{amount}/月")
return True

def remove_deduction(self, category: str):
if category in self.deductions:
del self.deductions[category]
self.save_deductions()
print(f"✅ 已删除 {category}")
else:
print(f"⚠️ 未找到 {category}")

def list_deductions(self):
if not self.deductions:
print("📭 暂无扣除项")
return {}

total = 0
print("\n" + "=" * 45)
print("专项附加扣除清单")
print("=" * 45)

for cat, info in self.deductions.items():
print(f"\n🏷️ {cat}:")
print(f" 金额: ¥{info['金额']}/月")
print(f" 详情: {info['详情']}")
total += info['金额']

print(f"\n💡 合计: ¥{total}/月 (年度¥{total*12})")
return self.deductions

class TaxYearCalculator:
"""年度个税计算器"""

def __init__(self, deduction_manager: SpecialDeductionManager):
self.dm = deduction_manager
self.records = []

def add_monthly_record(self, month: int, income: float, insurance: float, tax_paid: float):
self.records.append({
"月份": month,
"收入": income,
"五险一金": insurance,
"已缴个税": tax_paid
})

def generate_report(self):
if not self.records:
print("⚠️ 暂无数据")
return

print("\n" + "=" * 55)
print("年度个税汇算清缴报告")
print("=" * 55)

df = pd.DataFrame(self.records)
total_income = df['收入'].sum()
total_insurance = df['五险一金'].sum()
total_tax_paid = df['已缴个税'].sum()

special_deductions = self.dm.list_deductions()
total_special = sum(v['金额'] for v in special_deductions.values()) * 12

taxable_income = total_income - total_insurance - 60000 - total_special

print(f"\n💰 收入情况:")
print(f" 年度总收入: ¥{total_income:,.2f}")
print(f" 年度五险一金: ¥{total_insurance:,.2f}")
print(f" 专项附加扣除: ¥{total_special:,.2f}")
print(f" 应纳税所得额: ¥{taxable_income:,.2f}")

if taxable_income <= 0:
print("🎉 无需缴纳个税")
return

for low, high, rate, deduction in TAX_BRACKETS:
if low < taxable_income <= high:
annual_tax = taxable_income * rate - deduction
break

print(f"\n💸 年度应缴个税: ¥{annual_tax:,.2f}")
print(f"💵 年度已缴个税: ¥{total_tax_paid:,.2f}")

diff = annual_tax - total_tax_paid

if diff > 0:
print(f"\n⚠️ 需要补税: ¥{diff:,.2f}")
elif diff < 0:
print(f"\n🎉 可以退税: ¥{abs(diff):,.2f}")
else:
print("\n✅ 无需补退")

# 保存报告
report_data = {
"项目": ["年度总收入", "五险一金", "专项扣除", "基本减除", "应纳税所得额",
"应缴个税", "已缴个税", "补税/退税"],
"金额": [total_income, total_insurance, total_special, 60000, taxable_income,
annual_tax, total_tax_paid, diff],
"说明": ["", "", "", "", "", "", "", "正数补税,负数退税"]
}

report_df = pd.DataFrame(report_data)
filename = f"个税汇算报告_{datetime.now().strftime('%Y%m%d')}.xlsx"
report_df.to_excel(filename, index=False)
print(f"\n✅ 报告已保存: {filename}")

def calculate_single_month():
"""单月个税计算"""
print("\n" + "=" * 40)
print("📅 单月个税计算器")
print("=" * 40)

try:
income = float(input("本月收入: ").strip())
insurance = float(input("本月五险一金: ").strip())

dm = SpecialDeductionManager()
special = dm.list_deductions()

months_passed = int(input("已过月份数: ").strip())
previous_tax = float(input("之前累计已缴个税: ").strip())

result = calculate_monthly_tax(
income, insurance, special, months_passed, previous_tax
)

print("\n" + "=" * 40)
for key, value in result.items():
print(f"{key}: {value}")

except Exception as e:
print(f"❌ 输入错误: {e}")

def main():
dm = SpecialDeductionManager()

while True:
print("\n" + "=" * 50)
print("个人税务助手 v1.0")
print("=" * 50)
print("1. 管理专项附加扣除")
print("2. 计算单月个税")
print("3. 录入年度工资数据")
print("4. 生成年度汇算报告")
print("5. 退出")
print("=" * 50)

choice = input("请选择功能: ").strip()

if choice == "1":
while True:
print("\n" + "=" * 40)
print("专项附加扣除管理")
print("=" * 40)
print("1. 添加扣除项")
print("2. 删除扣除项")
print("3. 查看扣除项")
print("4. 返回上级")

c = input("请选择: ").strip()

if c == "1":
cats = list(dm.DEDUCTION_STANDARDS.keys())
print("\n可选类别:")
for i, cat in enumerate(cats, 1):
print(f" {i}. {cat}")

cat_name = input("\n类别名称: ").strip()
if cat_name not in cats:
print("❌ 类别错误!")
continue

details = input("详情: ").strip()
custom = input("金额(回车使用标准): ").strip()

if custom:
try:
amt = float(custom)
dm.add_deduction(cat_name, details, amt)
except:
print("❌ 金额格式错误")
else:
dm.add_deduction(cat_name, details)

elif c == "2":
dm.list_deductions()
cat = input("\n删除的类别: ").strip()
dm.remove_deduction(cat)

elif c == "3":
dm.list_deductions()
input("\n按回车继续...")

elif c == "4":
break

elif choice == "2":
calculate_single_month()
input("\n按回车继续...")

elif choice == "3":
calc = TaxYearCalculator(dm)
while True:
print("\n录入月度数据(q退出)")
month = input("月份: ").strip()
if month == 'q':
break
try:
m = int(month)
inc = float(input("收入: ").strip())
ins = float(input("五险一金: ").strip())
tax = float(input("已缴个税: ").strip())
calc.add_monthly_record(m, inc, ins, tax)
except Exception as e:
print(f"❌ 错误: {e}")

elif choice == "4":
calc = TaxYearCalculator(dm)
# 重新加载records
for record in calc.records:
calc.add_monthly_record(record['月份'], record['收入'],
record['五险一金'], record['已缴个税'])
calc.generate_report()
input("\n按回车继续...")

elif choice == "5":
print("👋 再见!")
break

else:
print("请输入1-5!")

if __name__ == "__main__":
main()

六、使用方法

  1. 首次使用:运行程序,先进入“专项附加扣除管理”添加你的扣除项
  2. 单月计算:输入单月数据进行个税试算
  3. 年度汇算:逐月录入工资单数据,最后生成汇算报告
  4. 保存文件:所有数据保存在deductions.json中,可备份

七、知识点加油站

7.1 JSON数据持久化

1
2
3
4
5
6
7
8
9
# 保存字典到文件
import json
data = {"key": "value"}
with open('file.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)

# 从文件读取
with open('file.json', 'r', encoding='utf-8') as f:
loaded = json.load(f)

7.2 类型注解让代码更健壮

1
2
3
def func(name: str, age: int) -> bool:
# 参数: 类型 -> 返回值: 类型
return age >= 18

八、总结

  • ✅ 符合2025年税法的个税计算器
  • ✅ 专项附加扣除的增删查管理
  • ✅ 年度汇算清缴报告生成
  • ✅ 退税/补税预测

下篇预告

第17篇《简单的网页爬虫》——自动抓取财经新闻、提取关键信息,打造你的专属资讯雷达!


🤖 Powered by Kimi K2 Thinking 💻 内容经葵葵🌻审核与修改