NumPy数组数据清洗:numpy.delete函数从入门到实战指南
1. 项目概述为什么我们需要精准地删除数组元素在数据处理和分析的日常工作中我们经常面对一个看似简单却至关重要的任务从数据集中剔除不需要的部分。想象一下你手头有一份销售数据表里面可能混杂着测试记录、无效的零值行或者某些不相关的指标列。手动在Excel里删除固然可以但当你需要处理成千上万行数据或者这个操作需要嵌入到一个自动化的分析流程中时一个高效、精准的程序化方法就变得不可或缺。这正是numpy.delete函数大显身手的地方。作为Python科学计算库NumPy的核心功能之一delete方法允许我们基于行索引或列索引从多维数组ndarray中精确地移除指定的行或列并返回一个新的数组。它处理的不是像Excel那样的“单元格”而是整个数据“轴”axis上的切片。对于数据分析师、机器学习工程师或者任何需要批量处理数值型数据的人来说掌握这个方法就如同掌握了一把精准的手术刀能让你在数据清洗和预处理的环节中游刃有余避免因数据冗余或错误导致的模型偏差和分析失误。2.numpy.delete函数深度解析numpy.delete函数是NumPy中用于从输入数组中删除指定子数组行、列或任意轴上的子数组并返回新数组的工具。它的核心设计哲学是“无副作用”和“索引驱动”——它不会修改原始数组而是创建一个删除了指定元素后的副本。理解其参数和工作原理是正确使用它的第一步。2.1 函数签名与核心参数函数的完整签名是numpy.delete(arr, obj, axisNone)。这三个参数共同决定了删除操作的行为。arr: 这是输入的源数组也就是你想要进行操作的那个NumPy ndarray对象。它可以是任意维度的。obj: 这是最关键的一个参数它指定了要删除的部分。它可以接受多种形式整数 删除单个索引对应的子数组。例如obj2表示删除索引为2的行或列取决于axis。整数切片slice 删除一个连续范围内的子数组。例如objslice(1, 4)表示删除索引1到3左闭右开的子数组。整数列表或数组 删除多个不连续索引对应的子数组。这是最强大的功能之一。例如obj[0, 2, 5]表示同时删除索引为0、2和5的子数组。需要注意当提供列表时列表中的索引值应该是唯一的并且通常建议是排序后的否则可能导致意想不到的结果或难以理解的新数组顺序。axis: 这个参数定义了删除操作沿着哪个轴进行。它决定了obj参数中的索引指向的是行、列还是更高维度。axis0 这是默认值。操作沿着第一个轴进行对于二维数组来说就是删除行。你可以将其理解为“垂直方向”的操作。axis1 操作沿着第二个轴进行对于二维数组来说就是删除列。你可以将其理解为“水平方向”的操作。axisNone 如果设置为此值则arr会先被展平flatten成一维数组然后obj指定的索引将应用于这个一维数组。这在需要基于全局位置删除元素时有用但会丢失原有的多维结构。注意axis参数的理解是很多初学者的绊脚石。一个简单的记忆方法是axis的值就是你想要“压缩”或“移除”的那个维度的索引。想删行就压缩行维度axis0想删列就压缩列维度axis1。2.2 底层逻辑与返回值特性理解numpy.delete的底层逻辑能帮你更好地预测其结果。函数内部可以看作执行了以下步骤索引解析根据axis参数确定在哪个维度上应用obj索引。子数组选择根据obj索引选出所有不被删除的子数组行或列。重新堆叠将这些保留下来的子数组沿着指定的axis重新组合concatenate起来形成一个新的数组。返回副本将新组合的数组作为结果返回。原始数组arr始终保持不变。这个“返回副本”的特性至关重要。这意味着无论你对结果数组做什么都不会影响到最初的arr。这保证了数据源的安全性但也意味着对于非常大的数组频繁使用delete可能会产生一定的内存开销。如果确定要修改原数组通常需要将结果赋值回原变量如arr np.delete(arr, obj, axis)。3. 从入门到精通多种删除场景实战理论说再多不如动手试一遍。我们通过一个具体的二维数组来演示各种删除操作。假设我们有一个5行4列的数组代表5个样本的4个特征import numpy as np # 创建一个示例二维数组 (5行4列) data np.array([ [ 1, 2, 3, 4], # 样本0 [ 5, 6, 7, 8], # 样本1 [ 9, 10, 11, 12], # 样本2 [13, 14, 15, 16], # 样本3 [17, 18, 19, 20] # 样本4 ]) print(原始数组 data:) print(data) print(形状:, data.shape) # 输出: (5, 4)3.1 删除单行与单列这是最基本也是最常用的操作。删除单行axis0 假设我们发现第2行索引为2即第三行[9,10,11,12]是无效数据需要删除。# 删除索引为2的行 data_del_row np.delete(data, 2, axis0) print(\n删除第2行索引2后:) print(data_del_row) print(新形状:, data_del_row.shape) # 输出: (4, 4)结果将是一个4行4列的数组原第2行数据消失后面的行自动上移。删除单列axis1 假设第1列索引为1即第二列[2,6,10,14,18]^T是无关特征需要剔除。# 删除索引为1的列 data_del_col np.delete(data, 1, axis1) print(\n删除第1列索引1后:) print(data_del_col) print(新形状:, data_del_col.shape) # 输出: (5, 3)结果将是一个5行3列的数组原第1列被移除后面的列自动左移。3.2 删除多行与多列不连续索引实际数据清洗中我们往往需要一次性删除多个分散的无效条目。obj参数接受列表的特性在这里派上用场。删除多行 假设经过检查第0行和第4行首尾两行是表头或备注不属于有效样本需要同时删除。# 删除索引为0和4的行 rows_to_delete [0, 4] data_del_multi_rows np.delete(data, rows_to_delete, axis0) print(\n删除第0行和第4行后:) print(data_del_multi_rows) print(新形状:, data_del_multi_rows.shape) # 输出: (3, 4)现在数组只剩下中间的3行数据。删除多列 假设第0列是ID号第3列是总和可由其他列推导对我们当前的分析没有意义需要一并删除。# 删除索引为0和3的列 cols_to_delete [0, 3] data_del_multi_cols np.delete(data, cols_to_delete, axis1) print(\n删除第0列和第3列后:) print(data_del_multi_cols) print(新形状:, data_del_multi_cols.shape) # 输出: (5, 2)数组变为5行2列只保留了原来的第1列和第2列。实操心得在准备要删除的索引列表时一个常见的坑是索引越界。务必确保你提供的所有索引值都在当前数组对应轴的有效范围内0 到shape[axis]-1。另一个技巧是你可以利用NumPy的布尔索引或条件判断来动态生成这个索引列表例如rows_to_delete np.where(data[:, 0] 0)[0]来删除第一列值为负的所有行。3.3 使用切片进行批量删除当需要删除一个连续范围时使用切片slice语法比列出所有索引更简洁。删除连续行范围 删除第1行到第3行索引1, 2, 3。注意切片1:4是左闭右开区间。# 使用切片删除第1到第3行 data_del_slice_rows np.delete(data, slice(1, 4), axis0) # 等价于 np.delete(data, [1,2,3], axis0) print(\n删除第1到第3行切片1:4后:) print(data_del_slice_rows) print(新形状:, data_del_slice_rows.shape) # 输出: (2, 4)只剩下第0行和第4行。删除连续列范围 删除第1列和第2列索引1, 2。# 使用切片删除第1到第2列 data_del_slice_cols np.delete(data, slice(1, 3), axis1) # 等价于 np.delete(data, [1,2], axis1) print(\n删除第1到第2列切片1:3后:) print(data_del_slice_cols) print(新形状:, data_del_slice_cols.shape) # 输出: (5, 2)3.4 高阶技巧组合索引与负索引obj参数的能力远不止于此它支持更灵活的索引方式。使用负数索引 负数索引表示从末尾开始计数。-1表示最后一行/列-2表示倒数第二行/列以此类推。# 删除最后一行和最后一列 data_del_neg np.delete(data, [-1, -2], axis0) # 删除倒数第一和倒数第二行等等这里有个坑 print(\n尝试删除索引为[-1, -2]的行:) print(data_del_neg)直接运行上述代码可能会得到意想不到的结果。因为np.delete在内部处理列表时是按顺序应用索引的。当你指定[-1, -2]时它首先删除索引为-1最后一行此时数组形状变了原来的倒数第二行变成了新的最后一行索引-2可能就不再指向你最初想要的那一行了。更安全的做法是先使用正索引n_rows data.shape[0] rows_to_delete [n_rows-1, n_rows-2] # 明确计算为正索引 [4, 3] data_del_safe np.delete(data, rows_to_delete, axis0) print(\n安全地删除最后两行先计算正索引:) print(data_del_safe)组合不同形式的objobj甚至可以是一个包含切片和整数的可迭代对象虽然不常见。更常见的做法是利用np.r_这个索引技巧来生成复杂的索引序列。# 删除第0行、以及第2到第4行索引2,3 # 使用 np.r_ 来连接索引和切片 import numpy as np indices np.r_[0, 2:5] # 生成数组 [0, 2, 3, 4] data_del_complex np.delete(data, indices, axis0) print(\n删除第0行和第2,3,4行使用np.r_后:) print(data_del_complex) # 应该只剩下原第1行4. 避坑指南与性能优化在实际项目中使用numpy.delete时除了掌握基本用法了解其局限性和优化策略同样重要。4.1 常见错误与排查IndexError索引越界这是最常见的错误。总是先检查obj中的索引值是否小于数组在对应轴上的长度arr.shape[axis]。误用axis参数牢记“想删行设axis0想删列设axis1”。混淆两者会导致完全错误的删除结果甚至因为维度不匹配而报错。对原数组的误解忘记np.delete返回的是副本。如果你后续的操作基于“原数组已被修改”的假设就会出错。如果需要就地修改必须赋值arr np.delete(arr, obj, axis)。负索引的陷阱如上节所述在列表中使用负索引可能导致非预期行为因为删除操作会改变数组大小和后续索引的意义。尽量使用正索引或确保列表索引是独立的例如只删除[-1]是安全的但[-1, -2]可能不安全。4.2 性能考量与替代方案numpy.delete在底层需要分配新内存并复制数据因此对于大规模数组或循环中频繁删除的操作其性能可能成为瓶颈。时间复杂度大致是O(n)其中n是剩余元素的数量。场景对比与优化建议少量、一次性删除np.delete是最清晰、最直接的选择可读性高。大量、分批删除如果需要在循环中多次删除性能会很差。更好的策略是收集所有要删除的索引在循环中将要删除的索引添加到一个列表中而不是每次调用np.delete。一次性删除循环结束后使用这个索引列表调用一次np.delete完成所有删除操作。基于条件的删除更常见通常我们不是根据固定索引而是根据条件如“删除所有年龄为负的行”来删除数据。这时np.delete结合布尔索引或np.where是标准做法但仍有优化空间。示例高效的条件删除假设要删除data数组中第一列值大于10的所有行。# 方法1使用 np.where 找到索引再用 np.delete condition data[:, 0] 10 rows_to_remove np.where(condition)[0] result_method1 np.delete(data, rows_to_remove, axis0) # 方法2直接使用布尔索引更高效、更Pythonic # 保留条件为False的行即第一列不大于10的行 result_method2 data[~condition] # “~”是逻辑非运算符 print(方法1np.delete结果形状:, result_method1.shape) print(方法2布尔索引结果形状:, result_method2.shape) # 两种方法结果相同在这个例子中方法2布尔索引通常是更优选择。它语法更简洁并且NumPy对布尔索引有高度优化直接通过掩码选择数据避免了构建中间索引列表和调用通用删除函数的开销在处理大数据集时速度优势明显。4.3 在多维数组中的应用numpy.delete不仅限于二维数组。对于三维或更高维数组axis参数可以指定任何维度。# 创建一个3维数组 (2个3x4的矩阵) arr_3d np.arange(24).reshape(2, 3, 4) print(三维数组形状:, arr_3d.shape) # (2, 3, 4) # 删除第一个维度axis0上的第0个元素即第一个矩阵 arr_del_3d_axis0 np.delete(arr_3d, 0, axis0) print(删除axis0, obj0后形状:, arr_del_3d_axis0.shape) # (1, 3, 4) # 删除第二个维度axis1上的第1行在每个矩阵内部 arr_del_3d_axis1 np.delete(arr_3d, 1, axis1) print(删除axis1, obj1后形状:, arr_del_3d_axis1.shape) # (2, 2, 4)理解高维数组的axis是关键。你可以把axis想象成你想要“捏扁”或“移除一层”的那个维度。5. 真实工作流中的综合应用案例让我们通过一个模拟真实数据分析的场景将np.delete与其他NumPy/Pandas操作结合起来完成一个完整的数据清洗任务。场景我们有一份来自某传感器的数据集sensor_data.csv读入后是一个NumPy数组。数据包含时间戳、传感器ID、以及A、B、C三个通道的读数。我们发现数据中存在一些问题前5行是测试数据需要删除。传感器ID为999的记录是无效的需要删除对应行。C通道数据全部为0是故障通道需要删除整列。所有读数中的负值可能是传输错误需要被识别但本例我们选择删除这些异常行。import numpy as np # 模拟生成数据 np.random.seed(42) n_samples 100 # 列 [时间戳(模拟), 传感器ID, 通道A, 通道B, 通道C] data_raw np.column_stack([ np.arange(n_samples), # 时间戳 np.random.choice([101, 102, 103, 999], sizen_samples), # ID混入无效值999 np.random.randn(n_samples) * 10 50, # 通道A正常数据 np.random.randn(n_samples) * 5 30, # 通道B正常数据 np.zeros(n_samples) # 通道C故障全0 ]) # 故意插入一些负值异常到通道A neg_indices np.random.choice(n_samples, size5, replaceFalse) data_raw[neg_indices, 2] -np.random.rand(5) * 20 print(原始数据形状:, data_raw.shape) print(前几行数据含问题:) print(data_raw[:8])步骤一删除前5行测试数据data_step1 np.delete(data_raw, slice(0, 5), axis0) print(步骤1后形状:, data_step1.shape)步骤二删除传感器ID为999的无效记录我们需要先找到这些行的索引。注意传感器ID在第二列索引1。# 找到传感器ID列等于999的行索引 invalid_id_mask data_step1[:, 1] 999 rows_invalid_id np.where(invalid_id_mask)[0] print(f找到{len(rows_invalid_id)}条传感器ID无效的记录。) data_step2 np.delete(data_step1, rows_invalid_id, axis0) print(步骤2后形状:, data_step2.shape)步骤三删除全零的故障C通道第5列索引4data_step3 np.delete(data_step2, 4, axis1) # 删除索引为4的列 print(步骤3后形状:, data_step3.shape)步骤四删除通道A读数为负值的异常行现在通道A是第三列原始第四列删除一列后索引变为2。# 找到通道A读数小于0的行索引 negative_readings_mask data_step3[:, 2] 0 rows_negative np.where(negative_readings_mask)[0] print(f找到{len(rows_negative)}条通道A读数为负的记录。) data_clean np.delete(data_step3, rows_negative, axis0) print(\n最终清洗后数据形状:, data_clean.shape) print(清洗后数据前5行:) print(data_clean[:5])案例总结 在这个工作流中我们链式地使用了多次np.delete。每一次删除都基于明确的条件和索引。值得注意的是在步骤二和步骤四我们使用了条件索引np.where来动态确定需要删除的行这比手动指定固定索引要强大和自动得多。同时步骤三展示了如何删除无用的特征列。最终我们得到了一个干净、可用于后续分析如计算统计量、可视化、建模的数据集。个人体会在实际项目中数据清洗很少是单一操作。np.delete是我工具箱中的一把精确螺丝刀但通常需要配合布尔索引、条件判断、np.where、np.column_stack等其他工具一起使用。我的习惯是在删除操作前先用print或len()确认一下要删除的索引数量和内容避免误删核心数据。对于非常大的数据集我会更倾向于使用布尔索引进行筛选data data[~condition]因为其语法更直观且性能往往更好。np.delete在需要基于复杂、非布尔逻辑的特定位置索引进行删除时其价值无可替代。

相关新闻

故障抢修占用周末?AWS DevOps Agent 几分钟自动定位根因

故障抢修占用周末?AWS DevOps Agent 几分钟自动定位根因

云架构规模化、多云混合部署已成企业数字化常态,但随之而来的线上故障、多链路排查、权限壁垒等难题,持续消耗 SRE 与架构团队大量精力。突发告警打断工作、周末紧急线上抢修、跨平台调取日志耗费数十分钟、无生产权限难以定位根因,成为运维团…

2026/10/5 22:25:58 阅读更多 →
零基础AI漫剧制作:从脚本到视频发布的完整流程指南

零基础AI漫剧制作:从脚本到视频发布的完整流程指南

这次我们来看一个面向零基础用户的“学渣级教程漫剧教程”,它把从脚本创作到视频发布的完整流程拆解成了可执行的步骤。如果你一直想尝试制作自己的漫剧、动画解说或图文视频,但被复杂的工具链和专业技能门槛劝退,这个教程或许能帮你快速上手…

2026/10/11 19:02:50 阅读更多 →
Grok图像模型拓扑理解力解析:从场景图构建到关系推理实战

Grok图像模型拓扑理解力解析:从场景图构建到关系推理实战

在计算机视觉和人工智能领域,理解图像中物体的空间关系一直是一个核心挑战。最近,一项关于“Grok 图像模型拓扑理解力”的研究引起了广泛关注,其结果表明,在某些衡量空间关系的任务上,该模型的表现超越了现有的一些主流…

2026/10/13 9:31:04 阅读更多 →

最新新闻

Excel数据导入MySQL的3种实操方案:从图形化到命令行再到Python

Excel数据导入MySQL的3种实操方案:从图形化到命令行再到Python

如果你日常和数据打交道,大概率经历过这个场景:Excel 里的表格排得整整齐齐,领导一句“把这张表导入 MySQL,我们需要跑数”,结果打开客户端对着空表发呆。数据量小的时候复制粘贴还能勉强糊弄,一旦上千行、…

2026/10/14 17:40:03 阅读更多 →
LabVIEW通过TSPL指令控制TSC打印机实现标签批量打印

LabVIEW通过TSPL指令控制TSC打印机实现标签批量打印

简介:面向需要在 LabVIEW 环境下驱动 TSC TTP-244 条形码与标签打印机完成打印控制的开发者,这是一套可直接运行的串口打印方案,适合物流、零售、制造等场景的标签输出以及自定义标签批量打印需求。压缩包共 24 个文件、约 156KB,…

2026/10/14 17:40:03 阅读更多 →
2026智慧充电桩管理系统-充电桩小程序-云快充2.1 版本通讯协议

2026智慧充电桩管理系统-充电桩小程序-云快充2.1 版本通讯协议

博主介绍: 所有项目都配有从入门到精通的安装教程,可二开,提供核心代码讲解,项目指导。 项目配有对应开发文档、解析等 项目都录了发布和功能操作演示视频;项目的界面和功能都可以定制,包安装运行&#xff…

2026/10/14 17:40:03 阅读更多 →
SCORM课件演示与测试:从本地能播到平台能过的完整指南

SCORM课件演示与测试:从本地能播到平台能过的完整指南

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的兼容性、跟踪与复用能力。压缩包共28个文件,约12.07MB,以swf…

2026/10/14 17:40:03 阅读更多 →
MySQL大表清空:TRUNCATE底层原理、权限与避坑实战

MySQL大表清空:TRUNCATE底层原理、权限与避坑实战

最近在帮一个老项目排查数据库慢查询时,发现一张千万级的日志表被一条清理任务拖得半死不活。对方一脸无奈地跟我说:“我明明用的DELETE,怎么数据越清越慢?”我让他把SQL贴出来看了一眼,果然又是那个场景:D…

2026/10/14 17:40:03 阅读更多 →
STM32F103ZET6库函数工程从零搭建实战指南

STM32F103ZET6库函数工程从零搭建实战指南

1. 为什么今天还要从零搭建一个“老”芯片的库函数工程?在2024年,当RISC-V开发板满天飞、Cortex-M33芯片已成中端主力、AIoT项目动辄集成Wi-FiBLERTOS的时候,我依然在Keil MDK里,为一块STM32F103ZET6——这颗2007年发布、主频72MH…

2026/10/14 17:39:02 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →