查看: 263|回复: 0

Python write与writelines写入技巧与报错排查

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
Python写文件有两个基础入口:write()写单个字符串,writelines()写字符串序列。两者都要求传入字符串或字符串序列,且都不会自动补换行符。实际开发中,内容粘连、TypeError和日志丢失,往往不是语法问题,而是对返回值、写入模式和缓冲刷新理解不到位。

一、write()的返回值与换行行为
write(s)接收一个字符串,返回本次写入的字符数。它不会自动加换行,连续写入两段文本会直接拼在一起。下面代码中,n1、n2、n3分别返回5、6、1:
  1. with open('output.txt', 'w', encoding='utf-8') as f:
  2.     n1 = f.write('Hello')
  3.     n2 = f.write(' World')
  4.     n3 = f.write('\n')
  5.     print(n1, n2, n3)
复制代码
如果写成f.write('第一行')再f.write('第二行'),读取结果会是'第一行第二行'。需要换行时必须显式写入'\n'。

二、write()只接受字符串
write()的参数必须是str。直接执行f.write(42)或f.write([1,2,3])会抛出TypeError: write() argument must be str, not int或类似错误。常见做法是用f-string转换,或用join、map处理列表,也可以用json.dumps统一序列化:
  1. import json
  2. value = 42
  3. name = '张三'
  4. data = {'name': '张三', 'active': True}
  5. with open('data.txt', 'w', encoding='utf-8') as f:
  6.     # f.write(42) 会报 TypeError: write() argument must be str, not int
  7.     f.write(f'姓名: {name}\n')
  8.     f.write(f'数值: {value}\n')
  9.     f.write(json.dumps(data, ensure_ascii=False, indent=2))
复制代码
格式化写入适合生成报表。f-string支持宽度和对齐,例如{name:<10}表示左对齐占10个字符,{salary:>10,.0f}表示右对齐并带千分位。原文的报表函数就是按标题、表头、数据行、汇总行逐段write(),累计total_salary后输出总计。

三、writelines()的参数与换行陷阱
writelines(seq)接收字符串可迭代对象,返回None。它同样不会自动添加换行符。如果传入['行1','行2','行3'],文件内容会变成“行1行2行3”。需要先给每个元素补'\n':
  1. lines = ['行1', '行2', '行3']
  2. with open('bad.txt', 'w', encoding='utf-8') as f:
  3.     f.writelines(lines)  # 内容为 行1行2行3
  4. lines_with_newline = [line + '\n' for line in lines]
  5. with open('good.txt', 'w', encoding='utf-8') as f:
  6.     f.writelines(lines_with_newline)
复制代码
writelines()和循环write()效果相同,但writelines()可以配合生成器逐条产生行,不必先把全部内容加载到内存。下面的生成器只逐行yield,适合数据量较大的报告输出:
  1. def generate_report_lines(data):
  2.     yield '=' * 40 + '\n'
  3.     yield '数据报告\n'
  4.     for i, item in enumerate(data, 1):
  5.         yield f'{i}. {item}\n'
  6.     yield f'总计: {len(data)}条\n'
  7. data = ['项目A', '项目B']
  8. with open('report.txt', 'w', encoding='utf-8') as f:
  9.     f.writelines(generate_report_lines(data))
复制代码
需要确保迭代对象中的每个元素都是字符串;writelines()只是批量写入,不会替调用方做类型转换。

四、print(..., file=f)的便利性
print()可以通过file参数写入文件,并且会自动添加换行符,还能用sep控制分隔符。对于逐行输出字典数据的场景,比手动拼接'\n'更直接:
  1. data = [{'name': '张三', 'age': 25}]
  2. with open('print_output.txt', 'w', encoding='utf-8') as f:
  3.     for user in data:
  4.         name = user['name']
  5.         age = user['age']
  6.         print(f'姓名: {name}, 年龄: {age}', file=f)
  7.     print('A', 'B', 'C', sep='|', file=f)
复制代码
三者的关键差异可以这样记:write(s)参数为字符串,返回写入字符数,不自动换行;writelines(seq)参数为字符串序列,返回None,不自动换行;print(..., file=f)可写任意对象,返回None,自动换行。

五、安全写入:临时文件加os.replace
重要数据写入时,如果程序在写一半时崩溃,原文件可能损坏。原文给出的safe_write()先把内容写入filepath + '.tmp',调用f.flush()和os.fsync(f.fileno())刷新到磁盘,再用os.replace(temp_path, filepath)做替换。异常时删除临时文件并raise,避免吞掉错误:
  1. import os
  2. def safe_write(filepath, content):
  3.     temp_path = filepath + '.tmp'
  4.     try:
  5.         with open(temp_path, 'w', encoding='utf-8') as f:
  6.             f.write(content)
  7.             f.flush()
  8.             os.fsync(f.fileno())
  9.         os.replace(temp_path, filepath)
  10.     except Exception:
  11.         if os.path.exists(temp_path):
  12.             os.remove(temp_path)
  13.         raise
复制代码
这里的适用场景是配置文件、关键数据文件或需要避免半写状态的文件。普通临时输出可以直接用with open(),但重要文件应采用临时文件加替换的流程。

六、追加写入与简易日志器
日志需要保留旧内容时,应使用a模式追加,而不是w模式覆盖。SimpleLogger在每次log()中生成时间戳和级别,写入后调用flush(),让日志尽快落盘:
  1. from datetime import datetime
  2. class SimpleLogger:
  3.     def __init__(self, filepath):
  4.         self.filepath = filepath
  5.     def log(self, message, level='INFO'):
  6.         timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  7.         line = f'[{timestamp}] [{level}] {message}\n'
  8.         with open(self.filepath, 'a', encoding='utf-8') as f:
  9.             f.write(line)
  10.             f.flush()
  11.     def info(self, message):
  12.         self.log(message, 'INFO')
  13.     def error(self, message):
  14.         self.log(message, 'ERROR')
复制代码
调用logger.info('服务启动')、logger.error('数据库连接超时')时,文件会按时间顺序追加,不会覆盖旧日志。

总结:写文件的核心是三件事。第一,始终用with open(...)管理文件句柄;第二,记住write()和writelines()都不自动换行,需要手动补'\n';第三,重要数据先写临时文件,再用os.replace()替换原文件。涉及日志时使用a模式并配合flush(),涉及多行输出时可用生成器配合writelines()控制内存占用。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-21 12:59 , Processed in 0.020312 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部