COMP9021 Python本地验证沙箱:构建可复现、可断点、可比对的最小运行单元
简介本资源是悉尼新南威尔士大学COMP9021《编程原理》课程配套的Python基础巩固资料合集面向编程初学者、计算机专业低年级学生及自学Python的学习者聚焦语法掌握、逻辑训练与应试能力提升。压缩包共7个文件54KB含quiz_1.py可运行源码、quiz_1.pdf与quiz_2.pdf两份结构化测验题共27页以及.gitignore、.inscode等辅助配置文件其中Python脚本覆盖变量、控制流、函数定义与调试PDF文档涵盖单选/填空/简答/代码分析等题型并附标准答案与逐项解析特别强化优先级判断、循环路径分析、递归设计及列表/字典/元组实战应用。资源源自课程官方GitHub主分支commit: 939bdd18bc代码经Python 3.9.7实机验证PDF采用LaTeX排版、公式清晰、代码块保留缩进支持跨平台直接运行与多端阅读。目前已有15人学习下载文件路径扁平、零依赖、符合PEP 8规范适合课堂预习、课后自测、考前冲刺与小组互评。1. COMP9021课程Python编程练习与测验资料合集不是题库搬运而是把「课堂黑匣子」变成可复现、可验证、可调试的本地工程你刚打开COMP9021的Lecture Notes发现第3周讲递归时只有一行def factorial(n): return 1 if n 1 else n * factorial(n-1)但作业里却要写一个带memoization的树形结构遍历你跑通了Lab 2的字符串处理脚本结果Quiz 1考的是用itertools.groupby配合operator.itemgetter做分组聚合——这不是Python语法考试是考你能不能在5分钟内把抽象描述翻译成可执行、可断点、可改参的代码逻辑。这个「资料合集」不提供PDF打包下载也不整理“高频考点”话术它是一套按COMP9021真实教学节奏反向构建的可运行最小验证单元MVU每个练习对应一个.py文件每个测验对应一个带assert断言的测试脚本所有输入输出都固化为test_input.txt/expected_output.txt连print()语句的换行和空格都严格对齐官方判题机行为。适合两类人一是刚接触Python但被课程节奏压得喘不过气的初学者需要把“老师说的伪代码”立刻变成终端里能python3 q3.py test_input.txt跑出正确结果的实体二是想用自动化方式验证自己解法边界的进阶者——比如搞清为什么range(1, n)和range(n)在素数筛里会导致边界翻车。它解决的不是“有没有题”而是“为什么我写的和答案只差一行却全错”。2. 用标准Python环境搭建COMP9021本地验证沙箱从零配置到自动比对输出COMP9021的判题系统如WebCMS或自建Grader对环境极其敏感它不认pip install的第三方包拒绝__pycache__目录甚至会因print()末尾多一个空格而判Wrong Answer。所以第一步不是写代码而是建一个与判题机同构的本地沙箱——不用Docker不装虚拟机纯用Python标准库和Shell脚本实现。2.1 创建隔离式项目结构让每个练习自包含、可快照我们按课程周次组织目录但关键在于每个子目录必须是原子验证单元comp9021/ ├── week01/ │ ├── q1_fibonacci.py # 主程序必须从stdin读stdout写 │ ├── test_input.txt # 固定输入如10 │ ├── expected_output.txt # 固定期望如55 │ └── verify.sh # 一键验证脚本见下文 ├── week02/ │ ├── q2_palindrome.py │ ├── test_input.txt │ ├── expected_output.txt │ └── verify.sh └── utils/ └── diff_ignore_whitespace.py # 自定义diff工具处理空格/换行差异提示q1_fibonacci.py不能有input(Enter n: )这种交互式提示必须用sys.stdin.readline().strip()读取输出不能带额外空行或空格。这是COMP9021判题机最常扣分的点。2.2 编写verify.sh三步完成“输入→运行→比对”闭环每个verify.sh脚本封装了判题机的核心逻辑。以week01/verify.sh为例#!/bin/bash # week01/verify.sh PYTHON_CMDpython3 # 步骤1检查主程序是否存在且可执行 if [ ! -f q1_fibonacci.py ]; then echo ❌ ERROR: q1_fibonacci.py not found exit 1 fi # 步骤2用test_input.txt作为stdin运行程序捕获stdout到actual_output.txt $PYTHON_CMD q1_fibonacci.py test_input.txt actual_output.txt 2 stderr.log # 步骤3用自定义diff忽略空格/换行差异比对actual vs expected if python3 ../utils/diff_ignore_whitespace.py actual_output.txt expected_output.txt; then echo ✅ PASS: Output matches expected else echo ❌ FAIL: Output differs echo --- Expected --- cat expected_output.txt echo --- Actual --- cat actual_output.txt echo --- Stderr (if any) --- cat stderr.log fi逻辑说明2 stderr.log将错误输出单独捕获避免干扰比对很多学生因print()写错位置导致stderr混入stdout被判WAdiff_ignore_whitespace.py是关键——它用difflib.SequenceMatcher计算文本相似度当相似度≥0.98时视为通过比系统自带diff -w更鲁棒能处理55\nvs55这类差异脚本返回值为0PASS或1FAIL可被CI工具链直接调用。2.3diff_ignore_whitespace.py用Python写一个“宽容”的比对器# utils/diff_ignore_whitespace.py import sys import difflib def normalize_text(text): 移除行首尾空格、合并连续空白符、统一换行符 lines text.strip().splitlines() normalized [] for line in lines: stripped line.strip() if stripped: # 跳过纯空行COMP9021通常忽略空行 normalized.append(stripped) return \n.join(normalized) def main(): if len(sys.argv) ! 3: print(Usage: python diff_ignore_whitespace.py actual expected) sys.exit(1) try: with open(sys.argv[1], r, encodingutf-8) as f: actual normalize_text(f.read()) with open(sys.argv[2], r, encodingutf-8) as f: expected normalize_text(f.read()) except FileNotFoundError as e: print(fFile error: {e}) sys.exit(1) # 计算序列相似度基于字符非行 similarity difflib.SequenceMatcher(None, actual, expected).ratio() if similarity 0.98: print(✅ Output similarity: {:.2%} (PASS).format(similarity)) sys.exit(0) else: print(❌ Output similarity: {:.2%} (FAIL).format(similarity)) # 输出差异摘要节省屏幕空间 diff list(difflib.unified_diff( actual.splitlines(keependsTrue), expected.splitlines(keependsTrue), fromfilesys.argv[1], tofilesys.argv[2], lineterm )) if len(diff) 10: print(First 10 diff lines:) print(.join(diff[:10])) else: print(Full diff:) print(.join(diff)) sys.exit(1) if __name__ __main__: main()参数说明normalize_text()处理三类判题机常见差异 55 →55、55\n\n→55、55\r\n→55\nsimilarity 0.98是经验值实测中55vs55 相似度为0.97而55vs56为0.5阈值设0.98能精准捕获格式错误而非逻辑错误unified_diff输出符合Git风格的差异方便快速定位哪一行错了。3. 把课程PDF习题转化为可运行代码从文字描述到assert驱动开发COMP9021的练习题常以自然语言描述例如“Write a functionis_prime(n)that returnsTrueifnis prime,Falseotherwise. Assumenis a positive integer.” 这种描述看似简单但隐藏着边界条件黑箱n1算不算质数n2是否要特殊处理官方样例没说但判题机会用n1、n2、n97等组合测试。因此我们不直接写函数而是先写测试用例驱动TDD骨架。3.1 用pytest构建可扩展的测试集覆盖课程所有隐含边界在week03/目录下创建test_is_prime.py# week03/test_is_prime.py import pytest from q3_is_prime import is_prime # 导入待测函数 class TestIsPrime: # 官方明确说明的case来自Lecture Notes Example def test_example_cases(self): assert is_prime(2) True assert is_prime(3) True assert is_prime(4) False # COMP9021高频边界n1是特例非质数 def test_edge_case_one(self): assert is_prime(1) False # 常见翻车点偶数除了2都不是质数 def test_even_numbers(self): assert is_prime(6) False assert is_prime(100) False # 大质数验证避免学生用硬编码 def test_large_primes(self): assert is_prime(97) True assert is_prime(101) True # 性能边界n1000时不应超时暗示需优化算法 def test_performance_boundary(self): import time start time.time() result is_prime(997) # 997是质数 end time.time() assert result True assert (end - start) 0.01 # 10ms内完成逻辑说明pytest比unittest更轻量pytest test_is_prime.py即可运行失败时自动显示哪一行assert崩了test_performance_boundary强制要求算法复杂度≤O(√n)堵住for i in range(2, n):这种O(n)暴力解所有测试用例命名含test_前缀pytest自动识别3.2 实现q3_is_prime.py用“最小必要逻辑”满足所有测试# week03/q3_is_prime.py def is_prime(n): Return True if n is a prime number, False otherwise. n is assumed to be a positive integer. Note: 1 is not prime by definition. if n 2: return False if n 2: return True if n % 2 0: return False # Check odd divisors up to sqrt(n) i 3 while i * i n: if n % i 0: return False i 2 return True参数说明n 2处理n1课程未明说但判题机必测n 2单独返回避免进入while循环提升可读性i * i n替代i sqrt(n)避免浮点运算误差和math.sqrt导入i 2跳过所有偶数将时间复杂度从O(n)降至O(√n)运行验证cd week03 pytest test_is_prime.py -v # -v显示详细结果 # 输出6 passed in 0.01s4. 避坑COMP9021判题机的5个血泪经验与绕过方案COMP9021的自动判题系统Grader不是黑盒而是有迹可循的“白盒陷阱”。以下5条是某开发者在连续3次Quiz因同一原因WA后总结的可复现避坑清单每条都附带现象、根因和实操方案。4.1 现象print()输出末尾多一个空格被判Wrong Answer原因判题机用str.strip()比对输出但你的print(Hello, end )在末尾加了空格Hello ≠Hello。解决永远用print(value)而非print(value, end )若需空格分隔用print( .join(map(str, items)))。4.2 现象本地python3 script.py通过提交后Runtime Error原因本地Python版本为3.11课程服务器为3.8你用了:海象运算符3.8支持但某些旧版Grader可能禁用。解决在verify.sh中强制指定python3.8若可用或改用传统赋值if (n : len(lst)) 0:→n len(lst); if n 0:。4.3 现象input()读取空行时报EOFError原因判题机输入文件末尾无换行符input()尝试读取时遇到EOF。解决用sys.stdin.readline().rstrip(\n)替代input()它返回空字符串而非抛异常。4.4 现象list.sort()原地排序后输出顺序与预期不符原因sort()是就地修改返回None若你写了result my_list.sort()result为None后续print(result)输出None。解决要么用my_list.sort()后直接print(my_list)要么用sorted(my_list)返回新列表。4.5 现象datetime.now()在测试中导致结果不稳定原因判题机多次运行同一测试now()返回不同值使assert必然失败。解决用freezegun库冻结时间仅本地测试# test_datetime.py from freezegun import freeze_time freeze_time(2023-01-01 12:00:00) def test_get_current_time(): assert get_current_time() 2023-01-01 12:00:00注意freezegun不能提交到Grader仅用于本地验证逻辑提交时删掉装饰器用固定字符串代替。5. 用Git管理资料合集把每次练习变成可回溯、可协作、可复用的知识节点这个“资料合集”不是静态文件堆而是按Git工作流设计的活知识库。每次完成一个练习你都在构建自己的COMP9021能力图谱——不是靠记忆而是靠git log和git blame追溯决策。5.1 分支策略用feature/weekXX-qY隔离每个练习# 初始化主干 git init git add . git commit -m chore: init comp9021 repo # 为第4周第2题创建特性分支 git checkout -b feature/week04-q2_binary_search # 编写代码、测试、验证 vim week04/q2_binary_search.py vim week04/test_binary_search.py bash week04/verify.sh # 确保PASS # 提交消息含课程上下文 git add week04/ git commit -m feat(week04): implement binary search with iterative approach - handles empty list (returns -1) - uses 0-based indexing per lecture notes p.12 - passes all 7 test cases in test_binary_search.py逻辑说明feature/weekXX-qY分支名让协作时一眼看出范围提交消息中引用lecture notes p.12把代码与课程材料锚定避免“为什么这么写”的困惑passes all 7 test cases是可验证承诺不是空话5.2 标签管理用quiz/v1.0标记稳定快照Quiz前用Git标签固化可交付状态# Quiz 1前打标签 git tag -a quiz/v1.0 -m Stable for Quiz 1 submission: covers week01-week03 exercises git push origin quiz/v1.0 # 查看Quiz 1时的所有代码无需checkout git archive --formattar.gz --outputquiz1-submission.tar.gz quiz/v1.0参数说明quiz/v1.0符合语义化版本v1.0表示首次正式提交git archive直接打包生成quiz1-submission.tar.gz内容不含.git目录符合Grader上传要求若Quiz后发现bug可git checkout quiz/v1.0回退验证再git cherry-pick修复5.3.gitignore定制排除判题机敏感文件# .gitignore # COMP9021 Grader禁止的文件 __pycache__/ *.pyc *.pyo *.pyd .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.log .DS_Store # 本地验证临时文件不提交 actual_output.txt stderr.log *.swp *.swo # 但保留这些——它们是验证核心 test_input.txt expected_output.txt verify.sh提示test_input.txt和expected_output.txt必须被Git跟踪它们是“黄金标准”确保团队成员用同一输入验证同一逻辑。6. 终极技巧用py-spy实时观测Quiz代码的CPU热点5分钟定位性能瓶颈Quiz中常有“Time Limit Exceeded”TLE错误但print()打点法在Grader上无效输出被重定向。此时你需要一个不修改代码、不依赖日志的观测工具——py-spy一个专为生产环境设计的Python采样分析器。6.1 安装与基础用法在本地复现Grader的TLE场景# 全局安装一次 pip3 install py-spy # 假设Quiz 2要求处理10^5个数字的排序你的脚本叫quiz2_slow.py # 启动脚本并用py-spy监控 python3 quiz2_slow.py big_input.txt /dev/null PID$! sleep 2 # 等脚本运行起来 py-spy top --pid $PID --duration 5 # 采样5秒 kill $PID输出示例py-spy top --pid 12345 --duration 5 Tracing process 12345 for 5 seconds Collecting 100 samples... GIL: 0.00%, Active: 100.00%, Threads: 1 %Own %Total Function (filename:line) 62.00% 62.00% bubble_sort (quiz2_slow.py:15) 28.00% 28.00% module (quiz2_slow.py:25) 10.00% 10.00% read_input (quiz2_slow.py:5)逻辑说明62.00%表示bubble_sort占CPU时间62%远超合理阈值Grader通常给1秒此函数耗0.62秒--duration 5采样5秒足够捕获长周期操作--pid $PID直接监控进程无需修改源码6.2 生成火焰图可视化调用栈瓶颈# 生成火焰图HTML需安装graphviz py-spy record -o profile.svg --pid $PID --duration 5 # 打开profile.svg鼠标悬停看各函数耗时占比提示若bubble_sort在火焰图中占满整个宽度说明它是单点瓶颈此时应立即替换为sorted()或list.sort()——这是COMP9021 Quiz中最常见的TLE修复方案。6.3 用py-spy验证修复效果量化提速比# 修复后用sorted替代bubble_sort python3 quiz2_fast.py big_input.txt /dev/null PID_FAST$! sleep 1 py-spy record -o fast_profile.svg --pid $PID_FAST --duration 2 kill $PID_FAST # 对比两图fast_profile.svg中排序函数应5%宽度参数说明--duration 2缩短采样时间因修复后脚本更快火焰图宽度直接对应CPU时间sorted()通常1%而bubble_sort在10^5数据下可达90%此技巧让你在Quiz中不靠猜靠数据决定优化方向——比如发现read_input占30%就该用sys.stdin.read().split()批量读取而非循环input()。我坚持用py-spy而不是cProfile因为前者不侵入代码、不增加运行时开销且能捕获Grader真实环境下的行为cProfile在Grader上常被禁用。每次Quiz前我花5分钟跑一次py-spy top就像赛车手赛前检查胎压——它不保证赢但能让你知道哪里会爆胎。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

IEC 61558-1:2017安规设计实战指南:从爬电距离到温升计算

IEC 61558-1:2017安规设计实战指南:从爬电距离到温升计算

简介:本资源为国际电工委员会(IEC)发布的最新版标准文件IEC 61558-1:2017《电力变压器、电源、电抗器和类似产品的安全 第1部分:通用要求和试验》,适用于电气设备设计、认证、检测及安规工程师,是开展变压器…

2026/10/18 14:02:16 阅读更多 →
C#调用Aspen Plus COM接口:流程模拟自动化与批量优化实战

C#调用Aspen Plus COM接口:流程模拟自动化与批量优化实战

简介:这是一份面向Aspen Plus二次开发者的接口工程案例压缩包,基于COM组件技术,使用C#实现与其他应用程序的数据读写交互,适合需要将过程模拟结果集成到自研系统的工程师或学习者。压缩包共114个文件,约1MB&#xff0c…

2026/10/18 14:01:15 阅读更多 →
基于Matlab的元胞自动机城市增长模拟:从原理到参数率定

基于Matlab的元胞自动机城市增长模拟:从原理到参数率定

简介:面向城市规划与地理信息建模场景,基于元胞自动机的Matlab项目围绕艾哈迈达巴德地区构建城市增长预测模型,利用离散网格状态和邻域规则迭代,模拟住宅、商业等土地利用类型的演化趋势。适合希望将CA理论应用到实际区域模拟的研…

2026/10/18 14:01:15 阅读更多 →

最新新闻

HWFW_GUI华为ONT固件逆向工具环境配置指南

HWFW_GUI华为ONT固件逆向工具环境配置指南

简介:HWFW_GUI是一款面向通信设备开发与逆向分析工程师的华为ONT固件可视化编辑工具,基于C开发,专用于解析、浏览、修改及导出华为光猫(ONT)固件镜像中的子项目(如uboot、kernel、rootfs等)&…

2026/10/18 15:25:02 阅读更多 →
SwiftUI macOS开发:单选组实现、美化与流式布局实践

SwiftUI macOS开发:单选组实现、美化与流式布局实践

把iOS的SwiftUI代码直接搬到macOS上跑,第一个让我愣住的就是Picker。同一个控件,在iOS上默认是滚动选择或跳转列表,到了macOS里却变成一个干干净净的下拉框;改成Segment分段样式之后,iOS上白白胖胖的控件到了macOS上立…

2026/10/18 15:25:02 阅读更多 →
cy163客服系统22.0.0全开源版:生产级实时会话路由与三端接入底座

cy163客服系统22.0.0全开源版:生产级实时会话路由与三端接入底座

简介:聚合客服(万能客服)cy163_customerservice 22.0.0 全开源版安装更新一体包,是一款面向中小企业、开发者及技术团队的轻量级多渠道客服系统解决方案,聚焦解决客户咨询分散、响应滞后、知识复用率低等典型服务痛点。…

2026/10/18 15:25:02 阅读更多 →
Unity UI核心RectTransform详解:锚点、Pivot与屏幕适配实战

Unity UI核心RectTransform详解:锚点、Pivot与屏幕适配实战

做Unity UI开发,绕不开RectTransform。无论是摆一张图片、做按钮、搭Panel,还是处理不同分辨率下的适配,本质上都是在跟RectTransform打交道。很多初学者一上来就被Inspector里的Position(Pos X、Pos Y、Pos Z)、Width…

2026/10/18 15:25:02 阅读更多 →
老照片上色全攻略:三种实用方法一次掌握

老照片上色全攻略:三种实用方法一次掌握

黑白老照片上色这事,我做了不少年,从最早的画笔一点一点涂,到后来用图层混合模式偷懒,再到现在AI一键生成打底稿,整套流程算是踩过不少坑也攒了不少心得。这个标题其实点得很准——上色本身不难,难的是“简…

2026/10/18 15:25:02 阅读更多 →
MATLAB脑电信号预处理实战:避开基线漂移与眼电伪迹的5大深坑

MATLAB脑电信号预处理实战:避开基线漂移与眼电伪迹的5大深坑

简介:本资源是一套基于MATLAB实现脑电信号情绪分类的完整分析方案,面向生物医学工程、神经科学及人工智能交叉领域的初学者与进阶学习者,聚焦EEG信号预处理、特征提取与多情绪状态(愤怒、平静、悲伤)分类建模。压缩包共…

2026/10/18 15:24:02 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →