Python 文件操作¶
文件与路径¶
文件是存储在存储设备上的数据集合,是数据持久化的基本单位。文件的本质是一个字节序列,以及对这个文件本身的描述信息(如文件名、创建日期等)。
路径用于定位文件在文件系统中的位置:
- 绝对路径:从根目录开始的完整路径
- 相对路径:相对于当前工作目录的路径
.表示当前目录,如./data.txt..表示上级目录,如../output/result.csv
不同操作系统的路径差异:
| 系统 | 路径分隔符 | 绝对路径示例 |
|---|---|---|
| Windows | \ (反斜杠) |
C:\Users\name\data.txt |
| Linux/macOS | / (正斜杠) |
/Users/name/data.txt |
Python 使用 os.path.join() 或 pathlib 可自动处理跨平台路径。
In [1]:
import os
print("当前工作目录:", os.getcwd())
print("路径拼接:", os.path.join('folder', 'subfolder', 'file.txt'))
print("路径存在检查:", os.path.exists('.'))
当前工作目录: /Users/hanwentao/repo/teaching/cpct/lectures 路径拼接: folder/subfolder/file.txt 路径存在检查: True
打开文件:open() 函数¶
open() 是 Python 内置函数,用于打开文件并返回文件对象:
open(file, mode='r')
常用模式:
| 模式 | 说明 |
|---|---|
'r' |
读取(默认) |
'w' |
写入(覆盖) |
'a' |
追加 |
'x' |
创建(文件存在则报错) |
'b' |
二进制模式 |
't' |
文本模式(默认) |
with 语句¶
传统方式需要手动关闭文件,容易遗漏或出错:
In [2]:
f = open('demo.txt', 'w')
f.write('Hello, Python!\n')
f.write('文件操作示例\n')
f.close()
f = open('demo.txt')
print(f.read())
f.close()
Hello, Python! 文件操作示例
问题:如果 write() 过程中发生异常,close() 不会被调用,导致资源泄漏。
with 语句解决方案:
with open('file.txt') as f:
content = f.read()
# 文件在此自动关闭,即使发生异常
优点:
- 自动调用
f.close() - 代码更简洁
- 异常安全
In [3]:
with open('demo.txt', 'w') as f:
f.write('Hello, Python!\n')
f.write('文件操作示例\n')
print("文件已写入")
with open('demo.txt') as f:
content = f.read()
print("文件内容:")
print(content)
文件已写入 文件内容: Hello, Python! 文件操作示例
文件对象的常用方法¶
| 方法 | 说明 |
|---|---|
read(size) |
读取指定字节数,默认全部 |
readline() |
读取一行 |
readlines() |
读取所有行,返回列表 |
write(s) |
写入字符串 |
writelines(lines) |
写入字符串列表 |
seek(offset) |
移动文件指针 |
tell() |
返回当前指针位置 |
In [4]:
with open('demo.txt') as f:
print("逐行读取:")
for i, line in enumerate(f, 1):
print(f" 第{i}行: {line.strip()}")
逐行读取: 第1行: Hello, Python! 第2行: 文件操作示例
pathlib 模块¶
pathlib 提供面向对象的路径操作方式:
In [5]:
from pathlib import Path
p = Path('.')
print("当前目录文件:", [f.name for f in p.iterdir() if f.is_file()][:5])
file_path = Path('demo.txt')
print(f"\n文件名: {file_path.name}")
print(f"文件后缀: {file_path.suffix}")
print(f"父目录: {file_path.parent}")
print(f"是否存在: {file_path.exists()}")
当前目录文件: ['lec14.ipynb', 'demo.txt', '.DS_Store', 'lec10.ipynb', 'lec09.ipynb'] 文件名: demo.txt 文件后缀: .txt 父目录: . 是否存在: True
In [6]:
Path('output').mkdir(exist_ok=True)
output_file = Path('output') / 'result.txt'
output_file.write_text('使用 pathlib 写入数据\n')
print("读取内容:", output_file.read_text().strip())
读取内容: 使用 pathlib 写入数据
文件操作小结¶
- 使用
with语句确保文件正确关闭 - 推荐使用
pathlib进行路径操作 - 常用操作:读取 (
read)、写入 (write)、追加 (mode='a')
In [7]:
import os
os.remove('demo.txt')
os.remove('output/result.txt')
os.rmdir('output')
print("清理完成")
清理完成
离散与连续¶
In [8]:
# 准备工作
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx # 图(网络)处理包
from ipywidgets import interact, FloatSlider, IntSlider
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC']
离散数学¶
- 离散数学: 与有限或可数的孤立值相关联。
- 示例: 集合 $\{1,...,n\}$,整数集。
离散数学对象示例:
- 有限集合: $[1,2,...,100]$
- 无限离散集合: 整数 $\mathbb{Z} = \{\ldots,-2,-1,0,1,2,\ldots\}$
- 图结构 (Graphs)
In [9]:
# 生成并绘制 Barabási-Albert 图
G = nx.barabasi_albert_graph(150, 2)
plt.figure(figsize=(8, 8))
nx.draw(G, node_size=20, node_color='steelblue', edge_color='gray', alpha=0.7)
plt.title('Barabási-Albert 图 (n=150, m=2)')
plt.show()
连续数学¶
- 涉及整个区间或实数轴。
- 数学分析:严格处理连续。
连续实数轴:
包含所有实数,无缝隙地延伸。
In [10]:
# 绘制连续实数轴
fig, ax = plt.subplots(figsize=(8, 1))
ax.axhline(y=0, color='black', linewidth=3)
ax.set_xlim(-5, 5)
ax.set_ylim(-0.5, 0.5)
ax.set_yticks([])
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.set_xlabel('连续实数轴')
plt.show()
离散与连续的桥梁¶
- 离散和连续间的关系是数学的核心主题。
- 多数连续概念都有对应的离散形式。
案例 1:索引与函数求值¶
类比:$v_i$(v 的第 i 个元素)vs. $f(x)$(在 x 处求值 f):
- 索引:从序列中提取元素
- 函数求值:映射或计算
向量实际上是一个离散函数,其参数可以取值 i = 1,2,...,n,而求值结果就是 $v_i$。
事实上,考虑一个 range 对象如 range(2, 21, 2):
- 可以看作是向量 $[2,4,...,20]$ 的简写
- 对这个"类似向量"的东西进行索引时,实际上是在显式地求值一个函数,即 $i \to 2i$。
In [11]:
v = list(range(2, 21, 2))
print('range 展开为向量:', v)
range 展开为向量: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
In [12]:
print('从列表中提取第7个元素 (索引6):', v[6])
从列表中提取第7个元素 (索引6): 14
In [13]:
r = range(2, 21, 2)
print('从 range 对象中获取第7个元素:', r[6])
从 range 对象中获取第7个元素: 14
In [14]:
def f(x):
return 2 * x
print('f(7) =', f(7))
f(7) = 14
案例 2:逼近圆的面积¶
经典思想:使用离散的多边形,逼近连续的圆形边界。
In [15]:
def plot_inscribed_polygon(s):
"""绘制圆内接正多边形"""
fig, ax = plt.subplots(figsize=(8, 8))
# 绘制圆
theta = np.linspace(0, 2*np.pi, 1000)
ax.plot(np.cos(theta), np.sin(theta), 'k-', linewidth=2, label='圆')
# 绘制内接多边形
angles = np.linspace(0, 2*np.pi, s+1)
x = np.cos(angles)
y = np.sin(angles)
ax.fill(x, y, alpha=0.3, color='blue', label=f'{s}边形')
ax.plot(x, y, 'b-', linewidth=2)
# 计算面积
area = (s/2) * np.sin(2*np.pi/s)
error = np.pi - area
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)
ax.legend(loc='upper right')
ax.set_title(f'边数: {s}\n多边形面积: {area:.6f}\nπ = {np.pi:.6f}\n误差: {error:.6f}',
fontsize=12)
plt.tight_layout()
plt.show()
plot_inscribed_polygon(6)
In [16]:
# 创建交互式演示
interact(plot_inscribed_polygon,
s=IntSlider(min=3, max=100, step=1, value=6, description='边数:'));
interactive(children=(IntSlider(value=6, description='边数:', min=3), Output()), _dom_classes=('widget-interact'…
案例 3:正方形网格逼近¶
如果不用多边形,基于离散正方形网格叠加也能近似连续的圆面积。
In [17]:
def plot_inscribed_squares(s):
"""使用内接正方形逼近圆的面积"""
fig, ax = plt.subplots(figsize=(8, 8))
# 绘制圆
theta = np.linspace(0, 2*np.pi, 1000)
ax.plot(np.cos(theta), np.sin(theta), 'k-', linewidth=3)
# 绘制网格
h = 1/s
for i in range(-s, s+1):
ax.plot([i/s, i/s], [-1, 1], 'g-', linewidth=0.5, alpha=0.5)
ax.plot([-1, 1], [i/s, i/s], 'g-', linewidth=0.5, alpha=0.5)
# 计算并绘制内接正方形
a = 0 # 计数器
# 中心大正方形
xx = int(np.floor(np.sqrt(2)/(2*h)))
x = xx * h
y = x
square = plt.Rectangle((-x, -y), 2*x, 2*y, color='red', alpha=0.7)
ax.add_patch(square)
a += (2*xx)**2
# 其他小正方形
for i in range(-s, -xx):
for j in range(-s, 0):
x = i * h
y = j * h
if (x**2 + y**2 <= 1 and (x+h)**2 + (y+h)**2 <= 1 and
x**2 + (y+h)**2 <= 1 and (x+h)**2 + y**2 <= 1):
# 绘制8个对称位置的正方形
for xi, yi in [(x, y), (-x-h, y), (x, -y-h), (-x-h, -y-h),
(y, x), (y, -x-h), (-y-h, x), (-y-h, -x-h)]:
square = plt.Rectangle((xi, yi), h, h, color='blue', alpha=0.7)
ax.add_patch(square)
a += 8
area_approx = a * h**2
ax.set_xlim(-1.1, 1.1)
ax.set_ylim(-1.1, 1.1)
ax.set_aspect('equal')
ax.set_title(f's = {s}\n正方形数量: {a}\n面积估计: {area_approx:.6f} = {area_approx/np.pi:.6f}π',
fontsize=12)
plt.tight_layout()
plt.show()
plot_inscribed_squares(10)
In [18]:
# 创建交互式演示
interact(plot_inscribed_squares,
s=IntSlider(min=2, max=40, step=1, value=10, description='网格数:'));
interactive(children=(IntSlider(value=10, description='网格数:', max=40, min=2), Output()), _dom_classes=('widget…
案例 4:向连续运动过渡¶
- 离散状态: 随机游走 (Random Walk)
- 连续极限: 布朗运动 (Brownian Motion)
In [19]:
# 生成随机游走数据
N = 1024
h = 1/N
v = np.random.randn(N)
def plot_random_walk(j):
"""绘制不同采样率的随机游走"""
J = N // (2**j)
fig, ax = plt.subplots(figsize=(12, 6))
# 累积和
c = np.concatenate([[0], np.cumsum(v)]) * np.sqrt(h)
t = np.arange(N+1) / N
# 绘制完整路径
ax.plot(t, c, 'b-', linewidth=1, alpha=0.5)
# 绘制采样点
t_sample = t[::J]
c_sample = c[::J]
ax.scatter(t_sample, c_sample, color='red', s=50, zorder=5)
ax.plot(t_sample, c_sample, 'r-', linewidth=2)
ax.set_ylim(-2, 2)
ax.set_xlabel('时间', fontsize=12)
ax.set_ylabel('位置', fontsize=12)
ax.set_title(f'随机游走 (采样间隔: {J})', fontsize=14)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
plot_random_walk(6)
In [20]:
# 创建交互式演示
interact(plot_random_walk,
j=IntSlider(min=1, max=9, step=1, value=6, description='采样级别:'));
interactive(children=(IntSlider(value=6, description='采样级别:', max=9, min=1), Output()), _dom_classes=('widget-…
常见离散和连续的对应关系¶
| 离散形式 (Discrete) | 连续形式 (Continuous) |
|---|---|
| 累加求和 $\sum$ | 定积分 $\int_a^b$ |
| 累积和 | 不定积分 |
| 有限差分 | 导数与梯度 |
| 离散卷积 | 连续卷积 |
| 离散随机游走 | 连续布朗运动 |
| 二项分布 | 正态分布 |
本讲小结¶
- 离散与连续相辅相成,是计算数学和分析的核心。
- 关键思想: 可以利用易计算的离散近似高精度地估算复杂的连续量。
- 价值: 这些桥梁构建了整个数值分析与计算数学的基石。