相信大家肯定遇到这种情况:写了个程序,数据跑得飞快,程序关掉,数据就跟着灰飞烟灭。
比如我想写一个简单的记账系统,让用户记录支出和收入,代码如下:
python
data = []
def add_transaction(transaction):
data.append(transaction)
print("交易记录成功!")
def view_transactions():
for i, transaction in enumerate(data, 1):
print(f"{i}. {transaction}")
def main():
while True:
print("\n1. 添加交易记录")
print("2. 查看交易记录")
print("0. 退出")
choice = input("请选择操作:")
if choice == "1":
transaction = input("请输入交易记录(比如:收入100,支出50):")
add_transaction(transaction)
elif choice == "2":
view_transactions()
elif choice == "0":
print("退出程序,数据清零啦!")
break
else:
print("无效选项!")
main()
运行后,我们可以愉快地添加记录、查看记录,直到程序退出。
再次打开程序后,你会发现......记录都不见了!
这是因为程序运行时数据都保存在 **内存**
中,一旦终止程序,内存数据都会被释放!
1. 文件操作
为了让数据在程序关闭后也能保留下来,文件 是我们最简单直接的选择,文件是记录在外存(硬盘)
中,即便我们关闭程序,文件依然在那里,这样就实现了数据的持久化。
1.1 如何用文件保存数据?
让我们把交易记录保存到一个文件里。
python
def add_transaction(transaction):
with open("交易记录.txt", "a") as f: # 追加模式
f.write(transaction + "\n")
print("交易记录成功!")
def view_transactions():
print("\n历史交易记录:")
try:
with open("transactions.txt", "r") as f: # 读取模式
for line in f:
print(line.strip())
except FileNotFoundError:
print("还没有任何记录!")
# 其他代码保持不变
我们使用了文件操作:
使用 **with open**
with open("文件路径", "模式")
是文件操作的推荐方式。它会自动管理文件的打开和关闭,即使程序中途出错,也能确保文件被正确关闭。- 替代了
f = open(...)
和f.close()
的繁琐写法。
操作模式
"r"
:只读模式,如果文件不存在会报错。"w"
:写入模式,会覆盖文件内容。"a"
:追加模式,保留原有内容,在末尾添加数据。
运行后,程序会把每次记录保存到 交易记录.txt
文件中,下次打开还能查看历史记录。
1.2 文件操作常踩"坑"
程序能跑不代表就万无一失,文件操作也有一些坑:
- 文件路径问题
文件可能不在当前目录,或者路径不对,导致找不到文件。解决方法是:
- 使用绝对路径。
- 或者用
os.path
动态构建路径:
lua
import os
file_path = os.path.join(os.getcwd(), "transactions.txt")
- 文件未关闭
忘记调用 close()
方法,文件可能占用资源。用 with open(...)
结构可以自动关闭文件:
python
with open("file.txt", "w") as f:
f.write("Hello, World!")
- 模式选择错误
w
模式会覆盖文件内容,容易导致数据丢失。- 使用
a
模式追加时,不小心多写了回车符,读取会看到空行。
1.3 为什么需要 os 库?
os
是 Python 操作系统接口的库,提供了丰富的功能:
- 检查文件是否存在
lua
import os
if os.path.exists("transactions.txt"):
print("文件已存在!")
else:
print("文件不存在,程序会自动创建。")
- 创建目录
如果需要将文件保存在特定目录,先检查目录是否存在,再决定创建:
lua
dir_name = "records"
if not os.path.exists(dir_name):
os.mkdir(dir_name)
print(f"目录 {dir_name} 已创建!")
- 获取绝对路径
动态获取当前目录,防止路径错误:
lua
print("当前工作目录是:", os.getcwd())
2. 完善记账小工具
之前我们已经讲解了函数和封装,接下来我们就用模块化设计的思想,逐步来完善这个小工具。
2.1. 定义文件存储路径
ini
DATA_FILE = "data/accounting.json"
我们先定义了一个文件路径,用于存储和读取数据。文件路径是 "data/accounting.json"
,其中:
"data/"
是新建的文件夹,用于组织文件。
"accounting.json"
是具体的文件,存储记账数据。我们之前提到过 json,与 python 的字典格式一致,记账软件需要记录各种资产和支出分类,用 json 格式方便管理数据。
2.2. 初始化数据
如果我们是第一次运行程序,就需要定义一个初始数据结构,包含以下内容:
assets
:资产账户,记录各账户的余额(初始为 0),可以自定义。
categories
:分类,分为收入(income
)和支出(expense
)。
transactions
:交易记录,初始为空列表。
makefile
DEFAULT_DATA = {
"assets": {
"微信钱包": 0,
"支付宝": 0,
"银行账户": 0
},
"categories": {
"income": ["工资", "副业", "其他"],
"expense": ["日用", "房租", "食品", "娱乐", "交通"]
},
"transactions": []
}
我们是第一次运行程序,数据文件不存在,用这个结构初始化数据就能确保程序能正常运行,而不会因为数据缺失而崩溃。
2.3. 加载数据
那假如我们之前运行过呢?那就需要读取已经存在的文件中的数据,而不是重新初始化。
python
def load_data():
"""从 JSON 文件加载数据"""
if not os.path.exists(DATA_FILE):
os.makedirs(os.path.dirname(DATA_FILE), exist_ok=True)
save_data(DEFAULT_DATA) # 如果文件不存在,创建默认数据
with open(DATA_FILE, "r", encoding="utf-8") as file:
return json.load(file)
我们用刚刚提到的 os 库来检查文件是否存在 os.path.exists(DATA_FILE)
。
- 如果文件不存在则创建文件夹:
os.makedirs
,并保存数据:save_data(DEFAULT_DATA)
。 - 如果文件存在打开文件读取内容:
open(DATA_FILE, "r", encoding="utf-8")
。使用json.load
将 JSON 文件内容加载为 Python 字典。
**这样写可以充分考虑各种情况,**避免数据丢失,例如直接打开一个不存在的文件引发错误;
并能灵活处理文件,如果文件缺失,可以动态创建,而不需要手动创建。
2.4. 保存数据
那么当我们运行结束时,就需要把文件保存
python
def save_data(data):
"""将数据保存到 JSON 文件"""
with open(DATA_FILE, "w", encoding="utf-8") as file:
json.dump(data, file, ensure_ascii=False, indent=4)
我们先打开文件(写入模式):open(DATA_FILE, "w", encoding="utf-8")
。再使用 json.dump
将 Python 数据结构保存为 JSON 格式的字符串,并写入文件。
代码中的ensure_ascii=False
可以确保保存的 JSON 文件中可以使用非 ASCII 字符(如中文)。
2.5. 记账逻辑
接下来就是我们基本的处理逻辑了!我们需要记录收入/支出,并要记录交易记录,使用time
库来记录时间,并将交易记录与我们的资产关联,收入增加资产,支出减少。
python
def add_transaction(data, is_income):
"""记录收入或支出"""
print("\n请选择资产账户:")
asset = choose_from_list(list(data["assets"].keys()), "请输入选项编号:")
if asset is None:
return
category_type = "income" if is_income else "expense"
print(f"\n请选择{'收入' if is_income else '支出'}分类:")
category = choose_from_list(data["categories"][category_type], "请输入选项编号:")
if category is None:
return
while True:
try:
amount = float(input("请输入金额:"))
if amount <= 0:
print("错误:金额必须大于 0!")
continue
break
except ValueError:
print("错误:请输入有效的数字!")
# 更新资产余额
if is_income:
data["assets"][asset] += amount
else:
if amount > data["assets"][asset]:
print("错误:余额不足!")
return
data["assets"][asset] -= amount
# 记录交易
transaction = {
"type": "收入" if is_income else "支出",
"asset": asset,
"category": category,
"amount": amount,
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 添加时间戳
}
data["transactions"].append(transaction)
print("交易记录成功!")
datetime.now()
是记录当前时间的常用函数,方便我们查询记账时间
2.6. 完整代码
最后我们加入交互菜单,再写个循环,就完成了这个记账小工具。
python
import os
import json
from datetime import datetime
# 数据文件路径
DATA_FILE = "data/accounting.json"
# 初始化数据
DEFAULT_DATA = {
"assets": {
"微信钱包": 0,
"支付宝": 0,
"银行账户": 0
},
"categories": {
"income": ["工资", "副业", "其他"],
"expense": ["日用", "房租", "食品", "娱乐", "交通"]
},
"transactions": []
}
def load_data():
"""从 JSON 文件加载数据"""
if not os.path.exists(DATA_FILE):
os.makedirs(os.path.dirname(DATA_FILE), exist_ok=True)
save_data(DEFAULT_DATA) # 如果文件不存在,创建默认数据
with open(DATA_FILE, "r", encoding="utf-8") as file:
return json.load(file)
def save_data(data):
"""将数据保存到 JSON 文件"""
with open(DATA_FILE, "w", encoding="utf-8") as file:
json.dump(data, file, ensure_ascii=False, indent=4)
def display_assets(data):
"""显示所有资产及余额"""
print("\n当前资产余额:")
for i, (asset, balance) in enumerate(data["assets"].items(), 1):
print(f"{i}. {asset}: {balance:.2f} 元")
print()
def choose_from_list(options, prompt):
"""从选项列表中选择"""
while True:
for i, option in enumerate(options, 1):
print(f"{i}. {option}")
print("B. 返回上一页")
choice = input(prompt).strip().upper()
if choice == "B":
return None
if choice.isdigit() and 1 <= int(choice) <= len(options):
return options[int(choice) - 1]
print("无效选择,请重试!")
def add_transaction(data, is_income):
"""记录收入或支出"""
print("\n请选择资产账户:")
asset = choose_from_list(list(data["assets"].keys()), "请输入选项编号:")
if asset is None:
return
category_type = "income" if is_income else "expense"
print(f"\n请选择{'收入' if is_income else '支出'}分类:")
category = choose_from_list(data["categories"][category_type], "请输入选项编号:")
if category is None:
return
while True:
try:
amount = float(input("请输入金额:"))
if amount <= 0:
print("错误:金额必须大于 0!")
continue
break
except ValueError:
print("错误:请输入有效的数字!")
# 更新资产余额
if is_income:
data["assets"][asset] += amount
else:
if amount > data["assets"][asset]:
print("错误:余额不足!")
return
data["assets"][asset] -= amount
# 记录交易
transaction = {
"type": "收入" if is_income else "支出",
"asset": asset,
"category": category,
"amount": amount,
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 添加时间戳
}
data["transactions"].append(transaction)
print("交易记录成功!")
def add_asset(data):
"""新增资产账户"""
asset_name = input("请输入新资产名称:")
if asset_name in data["assets"]:
print("错误:资产已存在!")
return
try:
initial_balance = float(input("请输入初始余额:"))
except ValueError:
print("错误:请输入有效的数字!")
return
data["assets"][asset_name] = initial_balance
print("资产新增成功!")
def add_category(data, is_income):
"""新增分类"""
category_type = "income" if is_income else "expense"
new_category = input(f"请输入新{'收入' if is_income else '支出'}分类名称:")
if new_category in data["categories"][category_type]:
print("错误:分类已存在!")
return
data["categories"][category_type].append(new_category)
print("分类新增成功!")
def view_transactions(data):
"""查看交易记录"""
print("\n历史交易记录:")
if not data["transactions"]:
print("暂无记录。")
for i, t in enumerate(data["transactions"], 1):
print(f"{i}. [{t['type']}] 时间: {t['time']}, 账户: {t['asset']}, 分类: {t['category']}, 金额: {t['amount']:.2f} 元")
print()
def main():
"""主函数"""
data = load_data()
while True:
print("\n======== 记账软件菜单 ========")
print("1. 查看资产余额")
print("2. 记录收入")
print("3. 记录支出")
print("4. 新增资产账户")
print("5. 新增收入分类")
print("6. 新增支出分类")
print("7. 查看交易记录")
print("0. 退出程序")
choice = input("请选择操作:").strip()
if choice == "1":
display_assets(data)
elif choice == "2":
add_transaction(data, is_income=True)
elif choice == "3":
add_transaction(data, is_income=False)
elif choice == "4":
add_asset(data)
elif choice == "5":
add_category(data, is_income=True)
elif choice == "6":
add_category(data, is_income=False)
elif choice == "7":
view_transactions(data)
elif choice == "0":
save_data(data)
print("数据已保存,感谢使用!")
break
else:
print("无效选项,请重试!")
if __name__ == "__main__":
main()
运行截图:
3. 小结
在本节中我们使用文件的方式来实现数据的持久化💾,主要需要学习了文件的相关操作,我们简单回顾一下。
文件操作
- 使用
open
打开文件,可选择模式(r
、w
、a
等)。 - 常用方法包括
read
、write
、readline
和writelines
。 - JSON 文件操作使用
json.dump
和json.load
。
**os**
库功能
- 路径操作:检查路径是否存在、拼接路径、获取目录部分等。
- 文件夹操作:创建多级文件夹、删除空文件夹。
- 文件操作:删除文件、重命名文件或文件夹。