ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python基础语法(下):从文件读写到面向对象实战指南

Python基础语法(下):从文件读写到面向对象实战指南 1. 这一部分其实是 Python 入门最容易“感觉会了又不会用”的地方如果把 Python 基础语法拆成上下两讲上一讲通常解决的是“Python 长什么样”变量、数据类型、条件判断、循环、函数、列表和字典这些最核心的语法骨架。到了基础语法下处理的问题会更接近实际写代码时真正会遇到的东西文件读写、异常处理、模块与包、字符串处理、推导式、函数的高级用法以及面向对象编程的初步接触。很多人学到这里会出现一种状态单独看每个知识点都能看懂但要自己动手写一个小脚本或者把几个知识点串起来处理一份数据就会卡住。原因很简单前半部分学的是“单词和句型”后半部分学的是“段落和文章结构”。语法下的每一个主题几乎都是为了让代码能够真正跑进真实场景读取一个文件、处理一批数据、把报错接住、把代码拆成多个模块、把重复逻辑封装成类。适合看这部分内容的人主要是三类第一类是已经学完 Python 基础语法前半部分正在往“能独立写脚本”方向过渡的初学者。第二类是学过其他编程语言比如 C 语言、Java、C想快速了解 Python 在这些基础主题上的写法差异。第三类是准备做数据分析、自动化脚本、Web 后端、爬虫或量化交易策略验证的人这些方向后续都会高频用到语法下里的知识点。这一部分最值得关注的价值不在于某个语法点本身有多难而在于它能帮你把之前零散学的语法真正串起来。现在先把整部分内容按实际落地顺序拆一遍方便后续查找和复习。2. 文件读写是最该优先掌握的“实战型语法”2.1 为什么文件读写是基础语法下的分水岭上一讲里的练习基本都是程序内部的数据操作定义一个列表遍历它做条件判断调用函数返回结果。这类练习的好处是环境干净缺点是离真实任务太远。真实任务里数据几乎不可能写在代码里更多是从文本文件、日志文件、CSV 表格、JSON 配置或 Excel 导出的数据里读进来处理完再写回新的文件。所以文件读写是基础语法下里第一个需要彻底掌握的模块。它解决的不是“文件能不能打开”这种单点问题而是“数据从哪来、处理后去哪”的完整通路。判断自己是否真正掌握了文件读写标准很简单能读取一个 UTF-8 编码的文本文件并逐行处理内容。能处理文件不存在、路径错误、编码不一致导致的报错。能把处理结果写入新文件并确认内容完整、格式正确。能区分文本模式和二进制模式知道什么时候用r、w、a、rb、wb。如果上面的标准都能达到说明你已经具备写自动化脚本的基本能力了。2.2 最稳的读写方式用 with 语句管理文件Python 里打开文件最推荐的写法是with open(...) as f:原因是它能在代码块执行完后自动关闭文件。很多人刚开始写代码时习惯用f open(...)然后手动f.close()一旦中间代码抛出异常close()不会执行文件句柄就泄漏了。在单次运行的小脚本里问题不明显但写批量处理脚本或服务程序时文件句柄累积到一定数量会直接导致“Too many open files”这类系统级报错。下面是一个最基础的读取示例# reading_demo.py with open(data.txt, r, encodingutf-8) as f: for line in f: print(line.strip())这里关键点有两个一是encodingutf-8。Windows 系统默认编码可能是 GBK如果不指定编码读取包含中文的文件经常会出现UnicodeDecodeError。写代码时显式指定编码能减少不同系统之间的差异。二是for line in f这种方式是按行迭代读取适合处理大文件。如果文件很大一次性用f.read()读取到内存里很容易把内存占满。文本文件超过几百 MB 时逐行读取基本是默认选择。写入文件的写法类似# writing_demo.py lines [第一行, 第二行, 第三行] with open(output.txt, w, encodingutf-8) as f: for line in lines: f.write(line \n)w模式会清空原文件后重新写入。如果不想覆盖要改用a追加模式。这个细节看起来简单但实际很容易踩坑批量任务里如果多次运行同一个脚本w会把上一次结果直接清掉导致输出文件只有最后一次运行的内容。注意路径和权限问题比语法本身更容易让人卡住。文件读写报错时先确认文件是不是真的在那个路径下程序有没有权限读写该目录再检查编码和模式。2.3 读取 CSV 和 JSON 是数据类任务的高频场景处理表格数据时CSV 是最常见的文件格式之一。Python 标准库里的csv模块比直接按逗号切分字符串更稳。因为 CSV 中可能出现在引号内部的逗号、换行等特殊结构用简单的split(,)处理会出错。import csv with open(data.csv, r, encodingutf-8-sig, newline) as f: reader csv.DictReader(f) for row in reader: print(row[姓名], row[成绩])这里用utf-8-sig编码可以自动去掉有些 CSV 文件开头的 BOM 头避免第一列列名出现\ufeff这种不可见字符。JSON 配置文件在 Python 项目里更是随处可见。读取和写入的写法也很直接import json with open(config.json, r, encodingutf-8) as f: config json.load(f) print(config.get(host)) print(config.get(port))写入 JSON 时如果希望文件内容对阅读更友好可以使用ensure_asciiFalse和indent2data {name: Python, level: beginner} with open(output.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)ensure_asciiFalse的作用是让中文直接显示在文件里而不是变成\uXXXX这种转义序列。3. 异常处理不是等到报错才处理而是提前想好出错路径3.1 为什么异常处理要单独拆出来讲初级代码里程序正常运行看起来什么问题都没有。但真实使用中用户可能传入空文件、路径不存在、网络请求超时、API 返回空值、Excel 表格里某个单元格没有数据。这些情况如果不在代码里提前处理程序会在运行时抛出异常并直接中断。异常处理解决的核心问题不是“避免报错”而是“当错误发生时程序应该怎么响应”。是直接停止还是记录日志后跳过还是给用户返回一个友好提示不同场景需要不同处理方式。初学者常见的误区是把try...except当成万能保险把所有代码都塞进去然后遇到异常时只写一个pass。这样问题会被掩盖程序看起来“正常跑完”了但结果可能完全是错的。更合理的做法是只圈住可能出错的代码范围捕获到异常后至少把信息打出来让日志可以帮助后续排查。3.2 一个带异常处理的文件处理示例下面这个示例模拟了一个批量处理场景读取一个包含文件名的列表依次读取这些文件如果某个文件不存在不终止程序而是记录到错误列表里继续处理。import os file_names [data_01.txt, data_02.txt, data_03.txt] failed [] for name in file_names: try: with open(name, r, encodingutf-8) as f: content f.read() print(f{name} 读取成功长度 {len(content)}) except FileNotFoundError: failed.append(name) print(f警告{name} 不存在已跳过) if failed: print(以下文件处理失败, failed)这个写法比不写异常处理更贴近实际生产。批量任务里一个文件读不到就中断全部任务代价很高。更稳妥的方式是记录失败、跳过、继续处理最后统一看失败日志。3.3 捕获异常时要从具体到通用Python 的异常类是有层级关系的。捕获时建议先写更具体的异常类型最后再写通用异常Exception方便判断问题到底出在哪一层。下面给出一个常见顺序顺序异常类型适用场景1FileNotFoundError文件或目录不存在2PermissionError没有读写权限3UnicodeDecodeError编码不一致4json.JSONDecodeErrorJSON 内容不合法5Exception兜底记录未知错误代码里可以这样组织try: with open(data.json, r, encodingutf-8) as f: data json.load(f) except FileNotFoundError: print(配置文件不存在请检查路径) except json.JSONDecodeError: print(配置文件不是合法 JSON请检查格式) except Exception as e: print(f未知错误{e})这里最想强调的一点是异常处理也要分级不能拿一个大except把所有情况全吞掉。否则线上出问题时日志里只有一条“程序出错”具体是什么错、在哪个文件、哪一行、输入数据是什么全都看不到。4. 模块与包让代码从“一坨”变成“可维护”4.1 为什么要拆模块刚开始写代码所有内容放在一个文件里很容易运行也方便。但代码超过几百行或者有些逻辑要在好几个脚本之间复用就会很痛苦。改一个函数要翻半天文件复用一段逻辑还要复制粘贴。模块化解决的就是“复用、组织、隔离”这三个问题。Python 里一个.py文件就是一个模块一个包含__init__.py文件的目录就是一个包。通过import可以引入其他模块里的函数、变量和类。常见的项目目录结构参考project/ ├── main.py ├── utils/ │ ├── __init__.py │ ├── file_helper.py │ └── text_helper.py └── config/ └── settings.pyutils/file_helper.py里定义读取文件和处理路径的函数main.py里通过from utils.file_helper import read_text_file引入。这样职责更清晰后期也更容易定位问题。4.2 import 的几种常见写法# 方式一导入整个模块 import os path os.path.join(data, test.txt) # 方式二导入模块中某个函数 from datetime import datetime now datetime.now() # 方式三导入包中的子模块 from utils.file_helper import read_text_file写import时不建议使用from module import *这种通配方式因为会污染当前命名空间不容易看清代码到底用了哪些名字。尤其项目变大以后*引入很可能导致冲突或者变量被意外覆盖。4.3 自己写模块时要注意相对导入和主入口写模块时有个常见问题在main.py里直接运行没问题但当main.py作为模块被其他脚本导入时某些路径或导入语句可能失效。避免这类问题的通用做法是在作为程序入口的文件末尾加一段判断。def main(): print(程序入口) if __name__ __main__: main()这个写法的含义是当前文件被直接运行时__name__的值是__main__所以会执行main()当前文件作为模块被导入时__name__是模块名不会直接执行。这样既可以用命令行运行也可以被其他脚本安全导入。5. 字符串处理几乎每个脚本都会用到的实用能力5.1 字符串不是简单拼接就够Python 字符串处理是日常脚本里最常用的能力之一。初级代码里常见拼接比如Hello name !。这种写法在小场景里没问题但需要拼接的项很多时代码又长又容易漏空格。更推荐的做法是使用 f-string 格式化。name Python version 3.x # 推荐写法f-string message f当前学习的是 {name}常见版本是 {version} print(message)f-string 从 Python 3.6 开始支持现在几乎可以放心使用。它的优点是写法直观、性能也不差还能在花括号内写简单的表达式。5.2 高频字符串方法实际写脚本时下面这些方法出现频率很高strip()去掉字符串首尾的空格、换行符。split()按指定字符拆成列表。join()把列表拼成字符串。replace()替换字符串中的子串。startswith()/endswith()判断开头和结尾。find()/index()查找子串位置。lower()/upper()大小写转换。一个典型的文本清洗片段raw_text Hello, Python World! cleaned raw_text.strip() parts cleaned.split(,) print(parts) # 输出[Hello, Python World!]处理日志文件、CSV 数据、用户输入时几乎都会用到上面的组合。字符串处理的关键不是记住所有方法而是知道几个核心方法之后能根据实际需求组合使用。5.3 字符串和列表的转换关系split()和join()是一对经常配合使用的操作。split()把字符串切成列表join()把列表合成字符串。line apple,banana,orange fruits line.split(,) print(fruits) new_line | .join(fruits) print(new_line)这里有一个容易忽略的点join()是字符串的方法不是列表的方法。写法是分隔符.join(列表)不要写成列表.join(分隔符)。这个顺序问题很多初学者都会搞反。6. 推导式与函数高级用法让代码更简洁但有边界6.1 列表推导式先看懂再考虑用列表推导式是 Python 里很有特色的写法。它用一行表达式替代一部分循环逻辑。示例# 普通写法 squares [] for i in range(10): squares.append(i * i) # 推导式写法 squares [i * i for i in range(10)]带条件的写法even_squares [i * i for i in range(10) if i % 2 0]除了列表推导式还有字典推导式和集合推导式。写法思路一致。但对初学者来说更稳妥的建议是先确保能写出普通循环再用推导式做简化。如果表达式太长比如一行推导式超过 80 个字符或者嵌套多层循环反而会影响阅读。代码首先是给人看的其次才是让机器运行。6.2 默认参数、可变参数和关键字参数函数高级用法里比较实用的是默认参数和*args、**kwargs。默认参数示例def greet(name, greetingHello): print(f{greeting}, {name}) greet(Python) greet(Python, Hi)使用可变参数时可以接收不定数量的位置参数def add_all(*args): return sum(args) print(add_all(1, 2, 3)) print(add_all(1, 2, 3, 4, 5))接收关键字参数时可以用**kwargsdef print_info(**kwargs): for key, value in kwargs.items(): print(f{key}: {value}) print_info(namePython, levelbeginner)这里有一个需要注意的原则默认参数不要设置为可变对象比如列表或字典。原因可以简单记成默认参数在函数定义时只创建一次多次调用时如果修改默认参数会影响到后续调用。遇到这种需求更稳的写法是把默认值设为None在函数内部判断并创建新对象。# 不太推荐默认参数是可变对象 def add_item(item, items[]): items.append(item) return items # 更稳妥 def add_item(item, itemsNone): if items is None: items [] items.append(item) return items这个坑在面试题里出现过很多次实际项目中偶尔也会遇到。提前理解比事后排查轻松得多。6.3 匿名函数 lambda 的适用场景lambda适合用在需要一个简单函数的场景比如排序时指定键值。示例students [ {name: Tom, score: 82}, {name: Jerry, score: 91}, ] students.sort(keylambda stu: stu[score], reverseTrue) print(students)如果逻辑复杂应该定义普通函数而不是硬塞进lambda里。lambda的优势是简洁缺点也是简洁因为可读性会随着逻辑变复杂而迅速下降。7. 面向对象从“用别人的类”到“写自己的类”7.1 为什么要学类很多初学者觉得类很难其实 Python 里的类使用频率非常高。之前用的open()返回的文件对象、csv.DictReader创建的读取器、json.load()返回的字典本质上都是对象。你早就在使用别人的类只是还没意识到。学习面向对象的价值在于当你需要把一组数据和方法绑定在一起时类可以提供比散装变量和函数更清晰的组织方式。7.2 一个最简单的类class Student: def __init__(self, name, score): self.name name self.score score def show_info(self): print(f{self.name} 的成绩是 {self.score}) stu Student(Tom, 90) stu.show_info()这里__init__是初始化方法用来给对象设置初始属性。self代表对象本身所有实例方法第一个参数都要写self。7.3 类与实例的关系类是模板实例是根据模板创建出来的具体对象。类中定义的变量叫类属性通过实例或类访问。类中定义的方法叫实例方法第一个参数是self。创建对象时调用__init__但这个过程中间还有__new__只是多数场景下不需要自己写。面向对象内容如果要深入还有继承、多态、私有属性、类方法、静态方法、特殊方法等。但在基础语法阶段先掌握“类定义、实例化、属性和方法调用”这几个核心点就够了。后续做项目时再根据需求补充更复杂的用法。8. 常见的三个理解误区和一条稳定上手的练习路径8.1 误区一文件读写里的路径和当前运行目录混淆很多人明明代码写在src目录下文件放在data目录下代码里却写相对路径data.txt。运行时系统实际查找路径是“当前命令执行时所在的目录”而不是代码文件所在目录。所以建议在脚本开头输出当前工作目录来定位import os print(当前工作目录, os.getcwd())如果需要更稳定的路径可以使用基于代码文件所在目录的写法from pathlib import Path BASE_DIR Path(__file__).resolve().parent file_path BASE_DIR / data / test.txtPath是 Python 3.4 之后提供的路径处理模块比直接用字符串拼接路径要健壮很多能在不同操作系统间兼容。8.2 误区二异常处理里只写 pass导致错误被隐藏前面提过except: pass会吞掉所有异常信息。问题排查时如果没有日志根本不知道哪里出错。至少应该用logging或print把异常信息打出来。8.3 误区三模块导入失败就以为是代码错了导入失败时有可能是当前环境没安装对应第三方包也有可能是包的版本不兼容还有可能是同目录下存在同名文件导致导入了错误的模块。排查顺序建议是先看报错信息里的模块名。确认当前环境下是否安装了这个包pip list或pip show 包名。检查是否安装了多个 Python 版本命令行里用的 Python 是否和你认为的一致。检查当前项目里是否有文件与导入的模块同名。最后再考虑代码逻辑问题。8.4 稳定上手的练习路径如果要把这部分内容真正转化为自己的代码能力建议按下面的顺序练习先写一个“读取文本文件、统计每行单词数、把结果写入新文件”的脚本。再加一层异常处理某个文件不存在时跳过并记录日志。然后拆模块把文件读取、数据处理、结果写入放到不同模块里。接着用命令行参数传入文件名而不是在代码里写死路径。最后把一批文件放到目录里写一个批量处理的循环。每步都先跑通再叠加不要一上来就把所有知识点全塞进一个脚本。9. 把这些语法点串起来的一个综合示例下面这个示例不会太长但覆盖了这一次梳理里的大多数知识点模块导入、文件读取、字符串处理、异常处理、字典操作和结果写入。import csv from pathlib import Path BASE_DIR Path(__file__).resolve().parent INPUT_FILE BASE_DIR / data / scores.csv OUTPUT_FILE BASE_DIR / output / scores_summary.csv OUTPUT_FILE.parent.mkdir(parentsTrue, exist_okTrue) failed_rows [] try: with open(INPUT_FILE, r, encodingutf-8-sig, newline) as f: reader csv.DictReader(f) rows list(reader) except FileNotFoundError: print(f文件不存在{INPUT_FILE}) raise SystemExit(1) except csv.Error as e: print(fCSV 解析失败{e}) raise SystemExit(1) for idx, row in enumerate(rows, start2): try: score float(row.get(score, )) row[level] 良好 if score 80 else 待提升 except ValueError: failed_rows.append(idx) row[level] 无效数据 with open(OUTPUT_FILE, w, encodingutf-8-sig, newline) as f: fieldnames [name, score, level] writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(rows) print(f处理完成输出到 {OUTPUT_FILE}) if failed_rows: print(有问题的行号, failed_rows)这个脚本的目标是读取成绩 CSV给每行加一列等级把结果写回新文件并收集没有分数的行号。它足够小适合用来验证文件读写、异常处理、路径处理、CSV 操作和基本输出逻辑的熟练度。10. 学习到这一步下一步该做什么基础语法下覆盖完文件读写、异常处理、模块与包、字符串处理、推导式、函数高级用法和面向对象初步之后你就拥有了一个很重要的能力把零散语法组合成可运行脚本。这和只会在交互环境里写几行代码完全不是一个层次。接下来比较自然的进阶路线取决于你的目标方向做数据分析方向可以开始补pandas、matplotlib的基础用法。做自动化脚本方向可以重点掌握os、pathlib、subprocess、time等标准库组合。做 Web 后端方向可以开始了解 Web 框架的基础路由和请求处理。做量化策略验证方向可以先练习用 Python 读取历史数据、计算指标、回测简单策略。但无论哪个方向都建议先把这一部分基础打牢。文件路径处理、异常处理、字符串清洗、代码模块化这四样能力在任何方向都会反复用到。我自己的经验是基础语法阶段不需要追求代码写得多花哨也不需要把所有 Python 技巧都一次性学会。真正重要的是能把一个真实的小任务从头到尾跑通哪怕过程笨拙一些。跑通一次完整流程后再回过头优化代码结构、加异常处理、拆模块这时候你才会真正理解模块化和异常处理的价值。基础语法下只是路标真正的掌握发生在你开始用它们解决实际问题的时刻。
返回列表