import os, pythoncom, win32com.client as win32
---------------------- 工具函数 ----------------------
def get_or_add_style(doc, name):
try:
return doc.Styles(name)
except:
return doc.Styles.Add(Name=name, Type=1) # 1=段落样式
---------------------- 主程序 ----------------------
pythoncom.CoInitialize()
try:
app = win32.Dispatch("Word.Application")
except:
app = win32.Dispatch("Kwps.Application")
app.Visible = True
doc = app.Documents.Add()
Word 常数
wdCollapseEnd = 0
wdStory = 6
wdWord9TableBehavior = 1
wdAutoFitContent = 1
wdFormatDocumentDefault = 12
---------- 1. 建立样式 ----------
def make_styles():
# 实验标题
s = get_or_add_style(doc, "濮工_实验标题")
s.Font.NameFarEast = s.Font.Name = "黑体"
s.Font.Size = 18
s.Font.Bold = True
pf = s.ParagraphFormat
pf.Alignment = 1 # 居中
pf.SpaceBefore = 18
pf.SpaceAfter = 12
pf.LineSpacingRule = 0 # 单倍
# 一级标题
s = get_or_add_style(doc, "濮工_一级标题")
s.Font.NameFarEast = s.Font.Name = "黑体"
s.Font.Size = 14
s.Font.Bold = True
pf = s.ParagraphFormat
pf.Alignment = 0
pf.SpaceBefore = 12
pf.SpaceAfter = 6
pf.LineSpacingRule = 0# 二级标题
s = get_or_add_style(doc, "濮工_二级标题")
s.Font.NameFarEast = s.Font.Name = "黑体"
s.Font.Size = 12
s.Font.Bold = True
pf = s.ParagraphFormat
pf.Alignment = 0
pf.SpaceBefore = 6
pf.SpaceAfter = 3
pf.LineSpacingRule = 0# 正文
s = get_or_add_style(doc, "濮工_正文")
s.Font.NameFarEast = s.Font.Name = "宋体"
s.Font.Size = 10.5
pf = s.ParagraphFormat
pf.FirstLineIndent = app.CentimetersToPoints(0.74) # 2 字符
pf.LineSpacingRule = 3 # 多倍
pf.LineSpacing = 1.25 # 1.25 倍
pf.Alignment = 0 # 默认居左# 图题注
s = get_or_add_style(doc, "濮工_图题注")
s.Font.NameFarEast = s.Font.Name = "宋体"
s.Font.Size = 9
pf = s.ParagraphFormat
pf.Alignment = 1
pf.SpaceAfter = 6
pf.LineSpacingRule = 0# 表题注
s = get_or_add_style(doc, "濮工_表题注")
s.Font.NameFarEast = s.Font.Name = "宋体"
s.Font.Size = 9
pf = s.ParagraphFormat
pf.Alignment = 1
pf.SpaceBefore = 6
pf.LineSpacingRule = 0
make_styles()
---------- 2. 顺序写内容 ----------
rng = doc.Range()
rng.Collapse(wdCollapseEnd)
def write(style, text, newline=True):
rng.Style = style
rng.Text = text
if newline:
rng.InsertParagraphAfter()
rng.Collapse(wdCollapseEnd)
标题
write(doc.Styles("濮工_实验标题"), "实验14 Python变量的内存模型")
一、实验目的
write(doc.Styles("濮工_一级标题"), "一、实验目的")
for t in ["1. 理解Python变量的“引用机制”,明确“变量名-引用-对象”的三层关系;",
"2. 掌握整数、字符串、列表等不同类型变量的内存分配规则(不可变/可变对象差异);",
"3. 观察Python垃圾回收机制(引用计数为0时对象销毁),理解del语句的作用;",
"4. 学会使用id()、sys.getrefcount()工具函数分析变量内存地址与引用计数;",
"5. 能通过实验现象区分浅拷贝与深拷贝的内存差异,解决实际编程中的内存问题。"]:
write(doc.Styles("濮工_正文"), t)
二、实验仪器
write(doc.Styles("濮工_一级标题"), "二、实验仪器")
for t in ["1. 硬件:带Python环境的计算机(Windows/macOS/Linux均可,内存≥4GB);",
"2. 软件:Python 3.8及以上版本、PyCharm/VS Code(含代码运行与调试功能);",
"3. 辅助工具:sys模块(获取引用计数)、copy模块(浅拷贝/深拷贝)、idle3终端;",
"4. 辅助资料:Python官方文档(数据模型章节)、内存地址可视化工具(可选)。"]:
write(doc.Styles("濮工_正文"), t)
write(doc.Styles("濮工_图题注"), "图14-1 Python变量内存模型示意图")
三、实验原理
write(doc.Styles("濮工_一级标题"), "三、实验原理")
write(doc.Styles("濮工_二级标题"), "1. Python变量的内存本质")
write(doc.Styles("濮工_正文"), "Python中变量本质是“引用”(内存地址指针),变量名存储在栈内存,指向堆内存中的对象(数据本体)。例如:a = 10中,a是栈中的引用,指向堆中值为10的整数对象,而非变量a直接存储10。")
write(doc.Styles("濮工_二级标题"), "2. 不可变与可变对象的内存差异")
write(doc.Styles("濮工_正文"), "不可变对象(int、str、tuple):对象创建后值不可修改,赋值操作会生成新对象并更新引用(如a = 10; a += 5,会创建新对象15,a指向新地址);")
write(doc.Styles("濮工_正文"), "可变对象(list、dict、set):对象值可修改,赋值操作不生成新对象,仅修改堆中数据(如lst = [1,2]; lst.append(3),lst引用地址不变)。")
write(doc.Styles("濮工_二级标题"), "3. 引用计数与垃圾回收")
write(doc.Styles("濮工_正文"), "每个Python对象都有引用计数器,记录当前指向该对象的引用个数(sys.getrefcount()可获取)。当引用计数为0时,对象占用的堆内存会被垃圾回收机制自动释放,避免内存泄漏。")
write(doc.Styles("濮工_二级标题"), "4. 核心公式(引用计数变化)")
write(doc.Styles("濮工_正文"), "对象引用计数变化遵循以下规则:")
公式段居中
formula_rng = rng.Duplicate
formula_rng.Style = doc.Styles("濮工_正文")
formula_rng.ParagraphFormat.Alignment = 1
formula_rng.Text = "新引用创建:count += 1;引用销毁:count -= 1;count = 0 → 对象销毁 ①"
formula_rng.InsertParagraphAfter()
光标恢复居左
rng.MoveEnd(Unit=wdStory)
rng.Collapse(wdCollapseEnd)
rng.ParagraphFormat.Alignment = 0
write(doc.Styles("濮工_正文"), "式中count为对象的引用计数,新变量赋值、函数传参等操作会增加计数;del语句、变量重赋值、函数执行结束等会减少计数。")
四、实验内容及步骤
write(doc.Styles("濮工_一级标题"), "四、实验内容及步骤")
write(doc.Styles("濮工_二级标题"), "1. 实验前准备")
for t in ["(1)环境配置:确认Python环境正常,安装PyCharm/VS Code,导入必要模块(import sys, copy);",
"(2)工具熟悉:在终端中测试id()函数(返回对象内存地址)、sys.getrefcount()函数(返回引用计数,自身调用会+1);",
"(3)创建实验目录:新建“Python内存模型实验”文件夹,保存实验代码文件(命名为test_memory.py)。"]:
write(doc.Styles("濮工_正文"), t)
write(doc.Styles("濮工_二级标题"), "2. 实验任务1:不可变对象的内存分配")
write(doc.Styles("濮工_正文"), "(1)编写代码,观察整数变量的内存地址变化:")
write(doc.Styles("濮工_正文"), "a = 10\nb = 10\nprint("a的地址:", id(a))\nprint("b的地址:", id(b))\nprint("a的引用计数:", sys.getrefcount(a))")
write(doc.Styles("濮工_正文"), "(2)运行代码,记录a和b的地址是否相同(验证小整数池缓存机制);")
write(doc.Styles("濮工_正文"), "(3)修改代码为a = 257; b = 257,再次运行,观察地址是否仍相同(验证大整数不缓存)。")
write(doc.Styles("濮工_二级标题"), "3. 实验任务2:可变对象的内存分配")
write(doc.Styles("濮工_正文"), "(1)编写代码,观察列表变量的内存变化:")
write(doc.Styles("濮工_正文"), "lst1 = [1,2,3]\nlst2 = lst1\nlst2.append(4)\nprint("lst1的地址:", id(lst1))\nprint("lst2的地址:", id(lst2))\nprint("lst1的值:", lst1)")
write(doc.Styles("濮工_正文"), "(2)运行代码,分析lst1和lst2地址相同的原因,以及lst1值变化的本质;")
write(doc.Styles("濮工_正文"), "(3)添加lst3 = copy.copy(lst1),观察lst3与lst1的地址差异(浅拷贝)。")
write(doc.Styles("濮工_二级标题"), "4. 实验任务3:引用计数与垃圾回收")
write(doc.Styles("濮工_正文"), "(1)编写代码,跟踪引用计数变化:")
write(doc.Styles("濮工_正文"), "import sys\nc = "hello"\nprint("初始引用计数:", sys.getrefcount(c))\nd = c\nprint("赋值后引用计数:", sys.getrefcount(c))\ndel d\nprint("删除d后引用计数:", sys.getrefcount(c))")
write(doc.Styles("濮工_正文"), "(2)运行代码,记录各步骤引用计数变化,理解del语句的作用;")
write(doc.Styles("濮工_正文"), "(3)通过任务管理器观察Python进程内存占用,分析垃圾回收是否释放内存。")
五、数据及处理结果
write(doc.Styles("濮工_一级标题"), "五、数据及处理结果")
write(doc.Styles("濮工_表题注"), "表14-1 不可变对象内存分配实验数据")
插入表格1
table_rng1 = rng.Duplicate
table1 = doc.Tables.Add(table_rng1, 4, 4, AutoFitBehavior=wdAutoFitContent)
table1.Borders.Enable = True
表头1
hdr1 = ["变量赋值语句", "内存地址(十进制)", "引用计数", "实验结论(地址是否相同)"]
for i, h in enumerate(hdr1, 1):
table1.Cell(1, i).Range.Text = h
数据1
rows1 = [["a=10; b=10", "", "", ""],
["a=257; b=257", "", "", ""],
["a="abc"; b="abc"", "", "", ""]]
for r, row_data in enumerate(rows1, 2):
for c, txt in enumerate(row_data, 1):
table1.Cell(r, c).Range.Text = txt
居中
for r in range(1, 5):
for c in range(1, 5):
table1.Cell(r, c).Range.ParagraphFormat.Alignment = 1
光标移到表格下方
app.Selection.EndKey(Unit=wdStory)
app.Selection.InsertParagraphAfter()
rng = app.Selection.Range
write(doc.Styles("濮工_表题注"), "表14-2 可变对象内存分配实验数据")
插入表格2
table_rng2 = rng.Duplicate
table2 = doc.Tables.Add(table_rng2, 4, 4, AutoFitBehavior=wdAutoFitContent)
table2.Borders.Enable = True
表头2
hdr2 = ["变量操作", "lst1地址", "lst2地址", "lst1最终值"]
for i, h in enumerate(hdr2, 1):
table2.Cell(1, i).Range.Text = h
数据2
rows2 = [["lst1=[1,2,3]; lst2=lst1", "", "", ""],
["lst2.append(4)", "", "", ""],
["lst3=copy.copy(lst1)", "", "", ""]]
for r, row_data in enumerate(rows2, 2):
for c, txt in enumerate(row_data, 1):
table2.Cell(r, c).Range.Text = txt
居中
for r in range(1, 5):
for c in range(1, 5):
table2.Cell(r, c).Range.ParagraphFormat.Alignment = 1
光标移到表格下方
app.Selection.EndKey(Unit=wdStory)
app.Selection.InsertParagraphAfter()
rng = app.Selection.Range
write(doc.Styles("濮工_正文"), "数据处理与分析:")
write(doc.Styles("濮工_正文"), "1. 对比表14-1数据,总结小整数池(-5~256)的缓存规则,解释地址相同/不同的原因;")
write(doc.Styles("濮工_正文"), "2. 结合表14-2数据,说明可变对象“多引用指向同一对象”的特性,以及浅拷贝对内存地址的影响;")
write(doc.Styles("濮工_正文"), "3. 分析引用计数变化规律,验证“del语句删除引用而非对象”的结论。")
六、思考题
write(doc.Styles("濮工_一级标题"), "六、思考题")
for t in ["1. 为什么Python中a = 10; b = a; a += 5后,b的值仍为10,而lst1 = [1,2]; lst2 = lst1; lst1.append(3)后,lst2的值变为[1,2,3]?从内存模型角度解释。",
"2. 当执行a = None时,原a指向的对象引用计数会如何变化?None在Python内存中有什么特殊意义?",
"3. 浅拷贝(copy.copy)和深拷贝(copy.deepcopy)在处理嵌套列表(如lst = [1, [2,3]])时,内存分配有何差异?请设计实验验证。",
"4. Python的垃圾回收机制除了引用计数,还有标记-清除、分代回收等策略,查阅资料说明这些策略的作用(针对循环引用问题)。"]:
write(doc.Styles("濮工_正文"), t)
---------- 保存 ----------
save_path = os.path.join(os.path.expanduser("~"), "Desktop", "Python变量的内存模型实验讲义.docx")
doc.SaveAs(save_path, FileFormat=wdFormatDocumentDefault)
print("已生成:", save_path)