☰
Python 基础文件 I/O 实战:用 Advanced Python Mastery 练习 1.3 编写 pcost.py 计算股票投资组合成本
2026/10/4 11:50:09 网站建设 项目流程
  • 示例工程
  • 教程

【免费下载链接】python-mastery

Advanced Python Mastery (course by @dabeaz)

项目地址:https://gitcode.com/gh_mirrors/py/python-mastery
点击查看免费下载

导读

本篇围绕 David Beazley 的 Advanced Python Mastery 课程 练习 1.3 展开,带你从零编写一个名为pcost.py的小程序:读取课程自带的 Data/portfolio.dat 数据文件,逐行解析每只股票的持股数量与买入单价,最终计算出整个投资组合的总成本。读完本文,你将掌握 Python 文件打开与读取、with上下文管理、逐行迭代、字符串split()切分以及int/float类型转换这一整套基础 I/O 编程套路,并能够把同样的模式迁移到其他文本格式的数据处理任务中。

练习定位:Python Review 阶段的第一道文件处理题

在课程结构中,练习 1.3 属于 "1. Python Review" 章节,是 练习索引 中排在基础语法复习(练习 1.1、1.2)之后的第一道涉及文件输入输出的题目。它的目标非常明确——回顾基本文件 I/O(Review basic file I/O)。课程假设学习者已经具备一定的 Python 基础(参见 README.md 的前置条件说明),因此这道练习用最小规模的代码,把文件操作中最核心的几个知识点串起来:

  • 用open()打开文件并读取内容;
  • 用with语句确保文件被正确关闭;
  • 把文件对象当作可迭代对象,用for line in f逐行处理;
  • 用str.split()把一行文本切分成字段;
  • 用int()、float()把字符串转换为数值并累加。

练习完成后生成的文件是pcost.py,该文件在后续练习中还会被反复改造(重构为函数、添加异常处理等),因此它实际上是整门课程早期的一条"主线代码"。

数据文件格式:理解 portfolio.dat 的列结构

练习使用的输入数据位于 Data/portfolio.dat,文件内容为 7 行空格分隔的文本:

AA 100 32.20 IBM 50 91.10 CAT 150 83.44 MSFT 200 51.23 GE 95 40.37 MSFT 50 65.10 IBM 100 70.44

每一行代表一次股票买入记录,共三列:

列含义示例
第 1 列股票代码(stock name)AA、IBM
第 2 列持股数量(number of shares)100、50
第 3 列每股买入价(purchase price of a single share)32.20、91.10

注意两个细节:同一只股票可能出现在多行(如MSFT和IBM各出现两次),因此不能按股票代码去重,而是要对每一行单独累加;第二列是整数(股数),第三列是浮点数(价格),解析时必须分别使用int()和float()转换。

计算总成本的方法在练习中写得很直白:求第二列乘以第三列的累加和,即total_cost = Σ (shares × price)。

需求拆解:一个"读文件 + 算总和"的最小流水线

练习 1.3 的任务可以拆成以下四步:

  1. 打开文件:读取Data/portfolio.dat的全部内容;
  2. 逐行读取:文件对象天然支持迭代,for line in f每次取出一行(含末尾换行符\n);
  3. 解析字段:用line.split()按空白把一行切成字符串列表,例如'AA 100 32.20\n'→['AA', '100', '32.20'];
  4. 转换并累加:nshares = int(fields[1])、price = float(fields[2]),然后total_cost += nshares * price。

最终打印总成本即可。

逐步实现 pcost.py

下面按照课程练习要求,一步步写出pcost.py。

第一步:打开文件并逐行迭代

Python 打开文件的标准写法是open()配合with语句。with会在代码块结束时自动关闭文件,即使中途抛出异常也能保证资源释放,这是课程推荐并一直沿用的最佳实践:

with open('Data/portfolio.dat', 'r') as f: for line in f: print(line) # 先看看每一行的原始内容

这里的第二个参数'r'表示只读模式(read),也是open()的默认模式,因此可以省略不写。文件对象f是一个可迭代对象,for line in f会按行产出内容,无需一次性把整个文件读入内存——这种逐行处理方式对大数据文件尤其友好。

第二步:解析每一行

对当前行调用split(),默认按任意连续的空白字符(空格、制表符等)切分,返回字段列表:

fields = line.split() # 'AA 100 32.20\n' -> ['AA', '100', '32.20']

随后从列表中取出对应位置的数据并完成类型转换:

nshares = int(fields[1]) # 字符串 '100' -> 整数 100 price = float(fields[2]) # 字符串 '32.20' -> 浮点数 32.2

第三步:累加并输出

把每一行的shares × price累加到total_cost变量中:

total_cost = 0.0 ... total_cost = total_cost + nshares * price

完整代码

结合课程官方解答 soln1_3.md 与仓库中可直接运行的实现 Solutions/1_3/pcost.py,完整的pcost.py如下:

# pcost.py total_cost = 0.0 with open('Data/portfolio.dat', 'r') as f: for line in f: fields = line.split() nshares = int(fields[1]) price = float(fields[2]) total_cost = total_cost + nshares * price print(total_cost)

注意:仓库内的 Solutions/1_3/pcost.py 由于位于Solutions/1_3/子目录,其文件路径写作'../../Data/portfolio.dat';而当你把pcost.py放在仓库根目录(课程 README.md 假设所有练习工作在顶层python-mastery目录中进行)时,应使用'Data/portfolio.dat'这一相对路径。

运行与验证

在仓库根目录执行:

bash % python3 pcost.py

正确输出应为:

44671.15

该数值与后续 练习 1.4 中交互式调用函数的验证结果完全一致(portfolio_cost('Data/portfolio.dat')返回44671.15),可以用于交叉校验你的实现是否正确。

如果希望手动核算,可以验证其中部分行:AA 100 × 32.20 = 3220.0,IBM 50 × 91.10 = 4555.0,全部 7 行累加之和即为44671.15。

关键知识点深挖

1. 为什么for line in f而不是f.read()后再 split?

文件对象本身是迭代器,for line in f按需逐行读取,内存占用恒定;而f.read()会把整个文件一次性读入内存,再手动split('\n')。对于本练习这种小文件两者皆可,但从源码习惯和后续大数据处理的角度,课程给出的逐行迭代写法是更通用、更省内存的模式,也是后面练习(如 练习 2.1 中的readrides.py、练习 8.x 的实时日志跟随)一直沿用for line in f的原因。

2. 换行符去哪了?

for line in f取出的每一行包含末尾的\n。在本练习中,line.split()会忽略换行符等空白字符,因此无需手动strip();但如果后续用fields[0]直接比较字符串或拼接输出,就要留意换行符的存在。练习 1.4 的报错示例"Couldn't parse: 'C - 53.08\n'"中就可以看到repr(line)输出的换行符,这正是对原始行内容的直观展示。

3. 类型转换的边界:遇到坏数据会怎样?

int()和float()在无法转换时会抛出ValueError。例如 Data/portfolio3.dat 中混入了C - 53.08这类第二列为-的坏行,直接转换会崩溃。这一"崩溃点"正是 练习 1.4 的主题:把pcost.py重构为函数portfolio_cost(filename),并用try/except ValueError捕获坏行、打印警告后继续处理。仓库中的演进版本 Solutions/1_4/pcost.py 展示了这一改造的完整代码,可作为"从脚本走向健壮程序"的过渡参考。

进阶延伸:从脚本到可复用函数

虽然练习 1.3 只要求写出可运行的脚本,但紧接着的 练习 1.4 会要求你把它改造成带参函数:

def portfolio_cost(filename): total_cost = 0.0 with open(filename) as f: for line in f: fields = line.split() nshares = int(fields[1]) price = float(fields[2]) total_cost = total_cost + nshares * price return total_cost print(portfolio_cost('Data/portfolio.dat'))

改造后的函数可以直接在交互解释器中调用,例如用python3 -i pcost.py进入交互模式后执行:

>>> portfolio_cost('Data/portfolio.dat') 44671.15 >>> portfolio_cost('Data/portfolio2.dat') 19908.75

其中 Data/portfolio2.dat 是另一份四行记录的测试数据(AA 50 27.10、HPQ 250 43.15、MSFT 25 50.15、GE 125 52.10),可作为函数正确性的第二个验证样例。

仓库中的可参考资源

  • 练习原文:Exercises/ex1_3.md
  • 官方解答:Exercises/soln1_3.md
  • 可运行实现:Solutions/1_3/pcost.py
  • 练习输入数据:Data/portfolio.dat、Data/portfolio2.dat、Data/portfolio3.dat(含坏数据,供练习 1.4 使用)
  • 后续演进:Solutions/1_4/pcost.py(函数化 + 异常处理)、Exercises/ex1_4.md
  • 课程总览:README.md、PythonMastery.pdf(配套讲义)

小结

练习 1.3 用 10 行左右的代码,完整覆盖了 Python 文件 I/O 的最小闭环:open→ 逐行迭代 →split切分 → 数值转换 → 累加求和。这不仅是本课程所有数据处理类练习(后续的 CSV 读取、实时日志跟踪等)的基础,也是日常脚本开发中使用频率最高的核心模式。建议亲手在仓库根目录写下pcost.py并跑通44671.15这个结果,再按 练习 1.4 的要求把它演进为带异常处理的函数,完成从"能跑"到"健壮"的第一次升级。

  • 示例工程
  • 教程

【免费下载链接】python-mastery

Advanced Python Mastery (course by @dabeaz)

项目地址:https://gitcode.com/gh_mirrors/py/python-mastery
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询