Python寒假作业实战复盘:函数模块化与数据可视化
2026/9/17 5:35:54 网站建设 项目流程

寒假刚开始的时候,我收到了Python寒假第二次作业,点开题目那一瞬间说实话有点慌。第一次作业还是练练变量、列表、循环,交上去的时候觉得Python也不过如此;第二次作业直接升了一个维度:要求用函数组织代码,做一个带文件读写的统计程序,还要从网页上获取数据进行可视化。从"能跑"到"像样",差距一下子拉开了。

这篇文章就把我完成这份作业的全过程复盘一遍:题目怎么拆解、函数模块化怎么写、数据和文件的坑在哪里、爬虫和可视化怎么落地,以及寒假深夜被报错反复折磨后沉淀下来的排查链路。无论你是刚学完Python基础的大学生,还是转行自学编程的初学者,这份复盘应该都能帮你避开不少弯路。

1. 面对寒假第二次作业,第一件事不是打开编辑器

很多人拿到这种综合性作业,习惯性直接打开PyCharm或者VS Code开始敲代码,我的经历告诉我这是最容易翻车的开场方式。第二次作业和第一次最大的区别,是它不再是"一道题",而是"一个小项目"。如果不先把需求拆干净,后面写着写着就会发现各模块互相打架,改一处崩三处。

1.1 先给作业画一张"能力地图"

我拿到作业后做的第一件事,是把题目里的每个动词圈出来。比如"统计""读取""爬取""展示",每个动词背后对应的都是明确的技术栈。我当时把作业要求整理成了四个大块:

  • 数据获取:从本地文件(CSV/TXT/JSON)读数据,部分题目要求从网页爬取。
  • 数据处理:去重、过滤、类型转换,统计最大值、最小值、平均值等。
  • 程序结构:所有核心逻辑必须封装成函数,不能全写在一个脚本里。
  • 输出展示:控制台输出结果,进阶要求用matplotlib画统计图。

画完这张图我才发现,作业的真正考点不是某个语法点,而是结构化编程的意识:把大问题拆小、把小块拼成整体。这也是为什么很多人第一次作业考满分、第二次作业却写成一团乱麻的原因。没有拆题这一步,后面所有努力都是在给一个歪掉的地基添砖加瓦。

1.2 第二次作业的"隐藏考点":结构化思维

我辅导过一个学弟,他的代码能跑出结果,但整个文件400多行,没有函数,没有注释,全靠一个接一个的循环暴力往下堆。交上去虽然功能实现了,但老师让他加一个新统计维度,他花了一整个晚上才找到该在哪插代码。这就是缺少结构化思维的典型症状。

第二次作业的隐藏考点,就是看你有没有意识到:代码不只是写给计算机看的,更是写给下一个自己看的。函数拆得好不好,模块边界清不清晰,决定了这个程序是"能跑"还是"能维护"。我当时强制自己遵守一个规则:每个函数只做一件事,函数名必须能直接读出它的作用。后面所有调试的顺畅,都得益于这个一开始看起来有点费时间的决定。

2. 函数与模块化:这份作业逼我改掉的编码习惯

第一次作业我大量使用print来观察中间结果,俗称"面向输出编程"。到了寒假第二次作业,数据规模上来了,步骤也多了,如果不做函数封装,每一段调试都要从头跑一遍整个脚本,效率低得让人崩溃。

2.1 为什么作业要求里反复强调"必须封装成函数"

一个非常直观的原因:可复用。统计功能要同时应用在本地文件和网页下载的数据上,没有函数,就得复制粘贴两遍代码。有了函数,传入不同的数据源路径,同一套逻辑直接跑两遍,结果一致还不需要维护副本。

第二个原因是可测试。函数是天然的测试单元,只要有明确的输入和预期输出,就可以单独验证。我当时写了一个清洗数据的函数,直接传进去一条脏数据,看返回结果是否符合预期,不需要读整个程序逻辑。这种调试体验是写面条代码完全感受不到的。

第三个原因说出来有点扎心:老师看作业也是看结构的。一个函数列表清清楚楚,说明你真的理解了这道题的设计意图,而不是靠运气和暴力堆出来。在作业和面试之间,这份意识是连贯的。

2.2 默认参数、返回值、作用域:三个绕不开的细节

封装函数的时候,很多初学者会踩一个特别隐蔽的坑:把可变对象当成默认参数。我当时就写过这样的代码:

def add_item(name, item_list=[]): item_list.append(name) return item_list

第一次调用返回['苹果'],第二次调用返回['苹果', '香蕉'],看起来没问题,可一旦在循环里反复调用,列表会不断累积,结果完全错乱。原因是Python的默认参数只会在定义时求值一次,后续每次调用复用的都是同一个列表对象。正确的写法是:

def add_item(name, item_list=None): if item_list is None: item_list = [] item_list.append(name) return item_list

另一个常见问题就是returnprint搞混。一个函数如果只打印结果却不返回,外层代码拿到的是None,后面所有对这个结果的运算都会直接报错。我的习惯是:函数内部尽量不print,所有计算结论通过return传递,打印统一放到调用方。这样函数既可以在程序里用,也可以在命令行单独调试时看输出,职责单一,排查起来快得多。

作用域的问题也很典型:在函数内部直接操作全局变量,或者修改了一个和全局变量同名的局部变量,然后发现外层数据根本没变。Python里通过global声明可以改全局变量,但我个人的建议是尽量别用。数据流应该通过参数传进去、通过返回值送出来,这样每个函数都是独立的黑盒,组合起来才不容易出错。

2.3 一个作业项目该有的目录组织

第二次作业的推荐结构,我当时参考了一个老学长给的模板,亲测好用:

winter_assignment/ ├── main.py # 程序入口,负责调度各模块 ├── data_loader.py # 文件读写、网页请求相关函数 ├── data_cleaner.py # 数据清洗、去重、类型转换 ├── stats_calculator.py# 统计计算逻辑 ├── plotter.py # 可视化相关函数 ├── output/ # 程序运行导出的结果 │ └── report.txt └── data/ └── sales.csv

这样的组织方式,每个文件只关心一类事情。main.py读入数据,调用清洗、统计、绘图,整个流程一目了然。别觉得一个作业搞这个阵仗是小题大做,从第二次作业开始建立的项目感,在后面做毕业设计、做开源项目时,价值会被不断放大。

3. 数据获取链路:从本地文件读到网页爬虫

寒假第二次作业里,数据获取占了整整一大块。单纯读一个写死的列表已经不算数了,必须从一个真实的数据来源里把数据拿进来,然后才能谈统计和展示。这也是很多同学卡住的地方:逻辑全都对,就是数据进不来。

3.1 文件读写里的编码暗坑

第一份数据是CSV文件,我当时直接写了:

with open("data/sales.csv", "r") as f: for line in f: print(line)

结果Windows上跑得好好的,把同样的代码放到Mac上,直接报UnicodeDecodeError。原因很简单:Windows中文环境下,很多工具默认用GBK编码保存文件,而Python 3里open函数默认用的是系统编码,换一台机器就水土不服。

正确的做法是在打开文件时就显式指定编码,并且明确换行符的处理:

with open("data/sales.csv", "r", encoding="utf-8", newline="") as f: reader = csv.DictReader(f) for row in reader: print(row)

csv.DictReader能直接把每行转成字典,按列名取值,比如row["price"],比手动split(",")安全得多,能自动处理字段里的逗号和引号。JSON文件则直接用json.load(),Python会帮你把结构解析成字典和列表,但要注意如果文件特别大,一次性加载可能会爆内存,那时候得用ijson这类流式解析库。

这里给一个我后来养成的习惯:所有读文件的操作,一律显式写encoding="utf-8",宁可写全,不能省略。跨平台、跨机器的时候,这个习惯能省掉一大堆玄学报错。

3.2 requests加BeautifulSoup的温和爬虫实践

作业里的爬虫题,要求抓一个公开网页上的表格数据。爬虫在很多初学者眼里很酷,但也很容易翻车。我当时的做法比较克制,也比较稳妥。

首先明确法律和道德边界:只爬允许爬的公开数据,优先看网站有没有提供API,其次看清楚robots.txt允许哪些路径,设置一个合理的请求间隔,不做任何对服务器有压力的事情。一个合格的学习型爬虫,不应该对目标站点造成任何负担。

核心代码分三步走。第一步,用requests获取页面:

import requests headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() resp.encoding = "utf-8"

这里有三件小事特别重要。headers里带个User-Agent,是为了不要一上来就用默认的python-requests标识,很多站点对陌生UA请求直接拒绝。timeout=10是防止某个请求卡死,整个作业hang住。resp.encoding要主动声明,否则遇到中文网页,Python自动检测编码偶尔会翻车,页面一上来就是乱码。

第二步,用BeautifulSoup解析HTML:

from bs4 import BeautifulSoup soup = BeautifulSoup(resp.text, "html.parser") rows = soup.select("table tr")

第三步,把表格里的每行数据提取出来,清洗后再进入统计模块。这就接上文件数据处理的同一套逻辑了。我当时写完后,把爬下来的数据和本地CSV数据跑同一个统计函数,一对比发现两边结论一致,那一瞬间真的理解了"数据流统一"的意义。

3.3 脏数据清洗:让程序开始"捡垃圾"

数据拿回来并不等于能用,因为真实数据里充满了各种意外:缺了某个字段的值、数字列里混进了单位、同一项有两个完全不同的写法。清洗数据的过程,几乎占了我整个作业开发时间的三分之一,这一点都不夸张。

去重是最基础的操作。热搜词里有一条"python筛选一样的",说的就是这个场景。如果只是找出重复项,可以用集合:

seen = set() duplicates = set() for item in items: if item in seen: duplicates.add(item) else: seen.add(item)

如果数据更复杂,比如是按多个字段判断是否重复,可以直接用pandas:

df = df.drop_duplicates(subset=["date", "product_id"], keep="first")

类型转换也是高频需求。从CSV里读出来的数字,默认是字符串,总和、平均值算出来全是错的。我的踩坑记录是:没有转换类型之前算出来的平均值特别离谱,检查了很久才想起来CSV读出来全是字符串。给字段统一做转换时,可以用一个函数包一下:

def parse_price(raw): if isinstance(raw, str): raw = raw.replace("元", "").strip() return float(raw)

对于缺失值,常见的策略包括:直接删除、填充平均值、填充为0。选择哪种策略,取决于业务背景和分析目的,不能无脑套。我当时把缺失值处理也封装成了函数,并且用注释写明"此处选择填充平均值的理由",这份注释后来被老师单独标红,说是整个作业最亮眼的地方。

4. 用pandas和matplotlib完成统计与可视化

"Python数据分析与可视化"作为搜索热词不是没有原因的。寒假第二次作业里,统计部分用纯手写循环也能做,但一旦数据量上到几百上千行,手写循环的效率和表达能力都捉襟见肘。pandas在这一步几乎是必选项。

4.1 用pandas把统计结果算出来

读取CSV变成DataFrame,一行:

import pandas as pd df = pd.read_csv("data/sales.csv", encoding="utf-8") print(df.describe())

describe()会直接输出各数值列的计数、均值、标准差、最小值、四分位数、最大值,作业要求里的统计量基本全覆盖了。如果还要按类别统计,比如按商品分组求和:

summary = df.groupby("category")["amount"].sum().sort_values(ascending=False) print(summary)

这一步的爽感在于:我原本写的五六个自定义统计函数,用pandas不到十行就实现了。但这里必须提醒一句,如果作业明确要求手写统计函数,建议还是按要求实现一遍,然后在进阶部分用pandas对照验证。两头你都做,既保证了作业得分点,又展示了工具运用能力。

作业里如果涉及到商品销量、评分这类连续型数据,还可以算一个分组均值来观察趋势。除此之外,pandas还有一个很好用的方法:value_counts(),可以统计一个字段的频次分布,比如最热销的商品Top10,一行代码就出来。

4.2 matplotlib中文乱码和横坐标密集的现场修复

可视化的第一道坎,是中文和负号乱码。网上能找到各种版本的答案,我实际操作下来最可靠的方案是设置字体为系统自带的黑体或宋体:

import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei"] # Windows # plt.rcParams["font.sans-serif"] = ["Arial Unicode MS"] # macOS plt.rcParams["axes.unicode_minus"] = False

这里有一个细节,字体设置必须放在绘图代码之前,而且如果在Jupyter里跑,建议放到第一个单元格。设置完字体后负号依然乱码,就是因为没有设置axes.unicode_minus为False。

第二个高频问题就是"python画图横坐标太密集"。当数据点有100个,横轴标签挤成一团黑线,根本没法看。我做作业时的解决办法是按步长抽样显示标签,数据点保留:

import matplotlib.ticker as ticker ax = plt.gca() ax.xaxis.set_major_locator(ticker.MaxNLocator(nbins=10))

MaxNLocator会自动帮你在坐标轴上挑大约10个位置显示刻度,既不会把标签挤成黑条,又能保留数据分布的整体趋势。如果时间序列很长,还可以用pd.date_range生成指定间隔的刻度位置,配合ax.set_xticklabels格式化日期显示。

4.3 作业要求之外的可视化加分项

完成基本柱状图和折线图之后,我顺手加了一个箱线图,展示每个类别下销售额的分布情况。这一步让"统计结果"变得更立体:柱状图告诉我平均值,箱线图则让我看到每一组数据的离散程度和离群点。

df.boxplot(column="amount", by="category", figsize=(10, 6)) plt.xticks(rotation=45) plt.savefig("output/boxplot.png", dpi=300, bbox_inches="tight")

bbox_inches="tight"这个参数我很推荐:保存图片时会自动裁掉多余的留白,放进作业报告里比默认效果好看不少。OBS截图和直接plt.show()保存分辨率也不一样,用dpi=300保存出来的图,即使放大看也足够清楚。

5. 寒假深夜排错实录:完整排查链路

按我的经验,这份作业真正的"老师"不是教材,而是那些半夜弹出的报错。以下三个问题是我和身边同学踩得最惨的,每一个都有完整的排查链路,分享出来希望大家不用重走这一遍。

5.1 环境配置问题:为什么"装好了"却"不能用"

我有个同学卡在最开始:明明按教程装了Python,终端敲python --version却提示找不到命令。排查链路是这样的。

第一步,确认安装时是否勾选了"Add Python to PATH"。这一步默认不勾选,没勾选的情况下Windows不会把Python的可执行文件路径加入环境变量。最简单的验证方式:

where python

如果这条命令什么都查不到,说明PATH里没有Python。解决办法两种:一是重装时勾选Add Python to PATH;二是在系统设置里手动添加Python安装目录和Scripts子目录到PATH。

第二步,如果是多版本Python共存,还要注意pythonpython3可能指向不同的解释器。我的建议是写项目时始终用虚拟环境,避免全局环境里版本混乱。

python -m venv venv venv\Scripts\activate # Windows source venv/bin/activate # macOS / Linux pip install pandas matplotlib requests beautifulsoup4

虚拟环境能让你锁死项目依赖的Python版本和第三方库版本,作业写完把requirements.txt一导出,换台机器也能直接复现。这既是作业要求里的加分点,也是真实项目里的基本素养。

第三步,国内网络环境下pip install慢到让人怀疑人生,解决方案是换国内镜像源:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas

或者直接配置全局默认源,一劳永逸。这条经验在我后面装numpy、cv2等一堆库的时候,帮我省了大量等待时间。

5.2 UnicodeDecodeError:一朝解决,终身感谢

这个报错出现的时候,我的代码本身没有任何逻辑问题,纯粹是"读取方式"和"文件保存方式"不一致。排查链路如下。

首先看报错信息指向哪一个文件、哪一个open调用。我当时的报错是:

UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 123: illegal multibyte sequence

说的是Python默认用GBK去解码这个文件,但文件里有一个字节不符合GBK规则。接下来我需要知道文件本身是什么编码。用VS Code或者Notepad++打开文件,右下角就能看到当前文件的编码格式。如果是UTF-8,那问题就清楚了:我读取文件时没有告诉Python这是UTF-8,它自作主张用了Windows默认的GBK。

修复方式就是我前面提到的那一行:

with open(filepath, "r", encoding="utf-8") as f:

如果文件本身就是GBK保存的,那就把encoding改成"gbk"。最稳妥的是在保存文件时统一用UTF-8,这样跨平台不会出问题。爬虫拿到HTML文本后,也一样要检查resp.encoding是否和页面实际编码一致,不一致就手动指定。

5.3 爬虫超时与空数据的层层排查

爬虫题翻车概率最高的两个现象:一是请求超时,二是解析结果为空。

超时的排查链路:先用浏览器手动访问目标网站,看看是否正常。如果浏览器正常,而脚本超时,原因多半出在请求头缺失或者出杀软拦截。加上完整的User-Agent还是不行,就试一下直接带Cookie或者增加重试逻辑:

for attempt in range(3): try: resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() break except requests.RequestException as e: print(f"第{attempt + 1}次请求失败: {e}") time.sleep(2)

解析结果为空的排查链路,第一步是打印响应内容:

print(resp.text[:500])

看看HTML里是不是真的有目标表格。如果有,说明选择器写错了。我一开始用的是soup.find("table"),但页面里有很多个table,取到的那一个恰好是别的模块,数据当然为空。改成soup.select_one("table#sales-table")之后,问题立刻解决。选不中元素的时候,优先检查idclassbs4对动态加载的JS内容是无能为力的,这也是一个排查方向:如果表格是JavaScript异步渲染出来的,requests拿到的HTML里根本没有这些数据,要么找接口,要么用Playwright之类的无头浏览器。

6. 交作业前的自检清单与未来延伸

完成了功能和可视化,并不意味着作业可以交了。我花了大概两个小时,做了一遍自检和优化,这些步骤看起来琐碎,却在最终评价里起了决定性作用。

6.1 代码可读性自检:让另一个自己看一遍

我交作业前做的事,是隔一天再打开自己的代码,模拟一个完全陌生的人去阅读。问自己几个问题:每个函数一眼能看出职责吗?变量名是否一看就懂?有没有多余的重复代码?

注释我会写,但不会每行都写。我倾向于在快函数上面写三行docstring,说明这个函数的输入、输出、做了什么。比如:

def calculate_average_price(df): """计算商品均价。 参数: df: 包含price列的DataFrame。 返回: 均价数值,保留两位小数。 """

真正有价值的注释,是解释"为什么这样做"的注释,而不是"这一行在做加法"的废话注释。把意图写清楚,代码自己的逻辑用可读的函数名已经能表达,两者配合才高效。

6.2 异常处理与边界测试

"能跑"和"健壮"之间的差距,就差在异常处理和边界测试上。比如统计函数传入一个空列表、文件路径不存在、网络请求超时,程序是优雅退出还是直接抛出一大段栈溢出疑云?

我的做法是给关键入口加上try-except

def main(): try: df = load_and_clean_data("data/sales.csv") except FileNotFoundError: print("找不到数据文件,请检查路径") return except Exception as e: print(f"数据加载失败: {e}") return report = generate_report(df) print(report)

边界测试则是想一些刁钻的输入:只有一个数据点怎么算方差?所有字段都是空值怎么处理?日期格式五花八门怎么统一?这些问题提前想清楚,作业的质量会比"正常输入能跑通"高出一个档次。这也顺便对应了热搜词里"01背包动态规划python"这类算法的本质要求:边界条件永远是算法题和工程题共同的核心。

6.3 从作业到作品:后续还能怎么演进

寒假第二次作业做完之后,我的体会是作业只是起点,这句话不是口号。代码已经拆成了清晰的模块,换数据源就能跑出完全不同的分析结果,这种可扩展性本身就意味着潜力。

如果还有时间,值得尝试的几个方向包括:把控制台输出的报告改成自动生成的HTML文件,用简单的模板拼接就能实现;把所有关键步骤用argparse做成命令行参数,实现"改一改参数就能换文件路径和图表配置";或者更进一步,把清洗和统计逻辑写成一个小型工具包,供后续课程反复使用。甚至还可以把这几百行代码整理整理,写成一篇带说明文档的开源小项目,放上GitHub。从作业到作品,很多时候只是多走半步的距离。

这次寒假作业给我最大的教训是:Python编程的进阶从来不是记住更多函数,而是学会组织代码、处理不可控的外部输入、在报错面前保持系统性的排查思路。这些东西在第一次作业里学不到,却在实战里处处要用。如果你也正在跟这份寒假作业较劲,别着急,慢一点拆题、稳一点排查,把它当成第一个真正的项目来做,你会收获远比分数更多的东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询