
可以通过该功能给学生随机地分配考号, 这个过程中考号不会重复出现, 而且结果是可复现的, 用户只要点击一下就能把结果一键写回到 Excel 里面去。适用的版本包括版本号大于或者等于三一点零加的软件, 还包括numpy两点x系列, 以及零点二四点三加点以上的版本。需要用到numpy这个库, 还有。所使用的平台情况是, 需要在本机上面完成Excel程序的安装工作。0. 我们先来聊一个确实已经发生过的具体场景吧。在进行教育行业工作的相关人士当中, 想必许多人都一定经历过这样的一种状况, 那就是关于考试安排的此项任务, 确实是一件非常让人感到颇为头疼的严重事项。在每一场大型考试即将到来的时候, 负责教务管理的老师手里会拿着包含有几百名考生的名单表, 任务是要给每一位考生指定唯一的考号。如果采用手动随机抽取的方式? 这种做法的速度实在太慢了, 而且很容易引发人们关于公平性的猜疑和质疑。要是打算使用 Excel 软件里的随机函数呢? 比如那个 RAND() 函数, 每次一旦刷新数据, 计算出来的结果就会发生变化, 导致最终的结果无法存档保存下来供日后复核重现, 如果真的出现了什么难以说清楚的问题, 那就真的没有办法解释得明白了。要想把这个问题给弄好, 就得满足几个条件。咱们今天就先来探讨一下, 究竟该怎么使用numpy, 并且以一种优雅的方式去把这个问题给解决掉。1. 完整实现其实它的思路是非常简单的, 只需要三步就能彻底完成。首先是要把学生的人数给数清楚, 其次是去生成一个从1到n的随机全排列结果, 最后是把这个结果竖着写入到E列当中。“随机全排列”这一操作是可以利用numpy库中的某种函数在一行代码里完成实现的, 具体调用方式是使用那个括号内的方法名, 它能够返回一个序列, 该序列是由从0一直延伸到n减1的整数所组成的, 并且这些整数被打乱了原始的顺序进行排列, 在整个最终生成的结果当中, 每一个数字都是仅会出现一次而没有重复的情况存在的。上代码import numpy as npimport xlwings as xwfrom pathlib import PathDATA_DIR Path(rD:\MTCResearch\Python-excels\data)SRC DATA_DIR / 教师资格考试汇总表.xlsx# 固定随机种子调试阶段必开保证每次跑出同样的结果方便比对# ️ 正式抽签时请删掉这一行否则每次都是同一套考号SEED 20240901rng np.random.default_rng(SEED)with xw.App(visibleFalse, add_bookFalse) as app:wb app.books.open(SRC)sheet wb.sheets[0]# 第一步数学生人数# expand(table) 从 A4 出发向下向右扩展到连续数据区边界# .shape 返回 (行数, 列数)取 [0] 就是行数## ️ 注意shape[0] 包含 A4 本身。若 A4 是表头行如姓名# 真实学生数应为 shape[0] - 1。请先确认表格结构n_students sheet.range(A4).expand(table).shape[0]print(学生人数, n_students) # 20# 第二步生成 1~n 的随机全排列# permutation(n) 返回 0~n-1 的打乱序列 → 1 变成 1~n# 数学性质每个数恰好一次天然不重复exam_numbers rng.permutation(n_students) 1print(exam_numbers)# [17 13 11 12 7 20 6 4 19 16 5 14 18 1 2 8 10 9 15 3]# 第三步竖着写进 E 列# transposeTrue 是关键numpy 数组默认按行写入横着铺# 加了它才会按列写入竖着铺sheet.range(E4).options(transposeTrue).value exam_numberswb.save()wb.close()人们为什么会提出疑问, 问为甚么不选择运用 range(E4:E23)这种写法来指定最终位置? 其实如果在执行写入一维序列的操作时, 系统行为会自动依照所处理数据的实际长度来进行范围上的扩展。因此在很多场景下, 仅仅给出起点所在的单元格地址就已经是完全足够的。使用这种方式的话, 能够有效减少代码当中需要进行人为干预以进行硬性编码的地方数量。一旦后续的行数数据发生了改变, 开发者也就无需再去额外修改相关程序代码的逻辑内容了。这个叫做等于真的东西, 实际上它所完成的具体行为, 究竟是什么。写入方式Excel 中的落位range(E4).value 横向方向上的E4对应数值1, F4对应数值2, G4对应数值3。单元格E4的值等于真这个状态, 并且它的数值设定被完成。把E4设定为一, 将E5设置为二, 然后把E6标注为三, 这样做是处于纵向排列的情况。这里顺便要大家一点, 我曾经观察过某些人编写的代码程序, 他们在其中引入了“from sympy”这一行内容, 这种行为是一种具有误导性质的导入操作, 其中的符号含义是指在括号内部存在的一个用于控制开关状态的布尔类型变量, 该内容与我之前提及的sympy库所具备的矩阵转置功能完全没有任何关联, 并且在这段代码的执行流程中, 该行导入语句也从未被实际调用过, 因此建议广大使用者不要受到互联网上流传下来的陈旧代码示例的影响而产生误解。2. 关于种子这个问题, 我们得把它拿出来说一说。在随机数这一部分的内容里, 种子这个话题是显得非常重要的, 这一点是非常值得把它单独拿出来进行具体阐述和分析的。对于numpy 2.x这个版本, 官方给出了一个建议, 说要使用那种圆括号的形式去创建属于自己独立掌控范围的随机数生成工具, 而不是继续去用那个全局存在的np..seed()函数。那么, 这两个方法之间究竟存在着什么样的区别呢。新代码请优先用 。np..seed 属于冻结的遗留 API仍可用但官方不再演进。关于可复现性和正式抽签3. 大家在用的那些随机函数, 想要快速查看一下的时候。numpy模块是用于生成随机数以及相关模拟与抽样计算的核心工具。我将我自己平时经常用到的函数整理出了一份清单, 以便大家可以随时进行查阅和参考。以下这些示例的情况, 都是基于设置了那个np..seed(42)以后的那种线性执行顺序来讨论的。这些代码的输出结果, 是在使用numpy这个库的版本号为2.4.4的环境下, 通过实际的测试运行, 然后进行了复现验证的。使用 rand() 这个函数。这个数值是在零和一之间进行均匀分布的。a np.random.rand(3) # 一维3 个随机数b np.random.rand(2, 3) # 2 行 3 列a [0.37454012 0.95071431 0.73199394]b [[0.59865848 0.15601864 0.15599452][0.05808361 0.86617615 0.60111501]]randn()函数——标准正态分布, 其中均值μ等于0, 标准差σ等于1。arr np.random.randn(2, 4)[[-0.58087813 -0.52516981 -0.57138017 -0.92408284][-2.61254901 0.95036968 0.81644508 -1.523876 ]]这是一个用于生成整数随机值的区间操作。low, high)r1 np.random.randint(0, 10, size5) # [0,9] 取 5 个r2 np.random.randint(1, 100, size(2, 3)) # [1,99] 取 2×3r1 [9 2 6 3 8]r2 [[ 3 51 7][21 73 39]]右开区间是一个经常被弄错的知识点, 例如区间(1, 100)是绝对不会包含数字100的。如果需要闭区间来包含端点的话, 可以使用新的API函数, 即调用参数设置为(1, 100, True)。() —— 自定义区间的均匀分布u np.random.uniform(low1, high5, size4)[1.05305984 4.76880702 3.25315287 2.54166601]这是一个自定义的平均数与标准差的正态分布。n np.random.normal(loc50, scale10, size6) # loc均值, scale标准差[57.79192635 38.98902244 61.30228194 53.73118915 46.13527049 38.41229758]() —— 随机抽样data [A, B, C, D]res1 np.random.choice(data, size3) # 有放回默认res2 np.random.choice(data, size2, replaceFalse) # 无放回res3 np.random.choice([0, 1], size5, p[0.7, 0.3]) # 按概率抽样res1 [B D D]res2 [A C]res3 [0 0 0 1 0]p 数组的长度必须与被抽样的序列保持一致, 并且其中的各个元素的总和必须等于 1 , 当然这里的判断是在允许一定的浮点数误差的范围内进行的, 只要不满足上述这些条件, 程序就应当报错。() —— 原地打乱arr np.array([1, 2, 3, 4, 5])np.random.shuffle(arr) # 直接改 arr无返回值print(arr) # [3 1 4 5 2]它返回的是一个全新的数组, 这个新数组里面的元素是打乱顺序之后的结果。而原始的数据内容是不会发生任何改变或者被移动的。arr np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])new_arr np.random.permutation(arr)print(new_arr) # [ 3 2 9 10 5 6 1 4 7 8]print(arr) # [ 1 2 3 4 5 6 7 8 9 10] ← 原数组完好经常会有人把它们弄混, 所以这里给大家列个表来做个对比一下:对比()()是否修改原数组修改不进行原地修改, 会生成并返回一个新的数组对象。返回值None打乱后的新数组传整数 n 时不支持这意思就是等于, 用那个字母 n 括号里装着另一个 n。典型用途就地洗牌生成随机序列或考号, 这是本文所涉及的具体应用场景。对于种子能够被复制这个事, 现在去进行一下验证。np.random.seed(1)print(np.random.randint(0, 5, size3)) # [3 4 0]np.random.seed(1)print(np.random.randint(0, 5, size3)) # [3 4 0] ← 重置种子后完全一致结果要是一样的, 前提就是种子得一样这一点既是实现可重复性的根底所在, 也在实际正式操作的时候变成了需要特别留神的小细节。4. 那个东西的对象模型, 分层做到四层就已经足够使用了。使用Excel进行操作, 其核心要点在于掌握四层对象, 把这四层对象弄清楚了, 便不会出现混乱的情况发生。AppExcel 进程└── books 工作簿集合└── Book wb一个 .xlsx 文件└── sheets 工作表集合└── Sheet一张工作表└── Range单元格 / 单元格区域xw.App() 这个东西, 指的是一个独立的 Excel 应用程序实例, 这个独立的应用程序实例呢, 其实就对应着在任务管理器里面能够看到的那个 EXCEL.EXE 进程。在构造参数的领域里, 有两类参数是被最为广泛地使用到的:这些参数。说明这个判断是正确的。把Excel的窗口设为可见状态, 这样就可以方便进行调试工作了。如果设置为False的话程序就会在后台静默运行, 这种情况是在需要大批量处理数据时必须要使用的。它的取值结果为假。当启动的时候并不会自动去创建一个空白的Excel文件, 如果省略这里的话, 默认是True的情况, 会自动带有一个叫做Book1的文件。有几个属性是常常要被使用到的, 也有几个方法是要常常去调用的:app.books # 当前实例下所有打开的工作簿集合app.books.open(rD:\test.xlsx) # 打开已有文件app.books.add() # 新建空白工作簿app.screen_updating False # 关闭屏幕刷新批量操作提速明显app.display_alerts False # 关闭是否保存/是否覆盖弹窗避免脚本卡死app.calculation manual # 关自动重算manual / automaticapp.quit() # 正常退出 Excel 进程app.kill() # 强制杀死进程quit() 失败时的兜底当你在处理上百个文件这类批量工作的场景中, 将参数设置为False与空字符串组合使用, 也就是所谓的False加上这种方式, 通常能够让执行速度提升好几倍。不过需要特别提醒的是, 在完成所有操作之后, 一定要及时把它们重新改回默认的True和引号包起来的空字符串状态。我一直以来都是采用这一套用于进行批量处理的标准的骨架结构的。import xlwings as xwfrom pathlib import PathDATA_DIR Path(rD:\MTCResearch\Python-excels\data)with xw.App(visibleFalse, add_bookFalse) as app: # 上下文管理器异常也会 quitfor f in sorted(DATA_DIR.glob(统计*.xlsx)):wb app.books.open(f)try:sht wb.sheets[0]print(sht.range(A1).value) # 真正的业务逻辑finally:wb.close() # 无论成败都关工作簿几个要点用 with 而非裸 app.quit()异常时也能保证进程回收 ≥ 0.24.3业务逻辑单独放便于替换wb.close() 放防止单个文件出错拖垮整批。总结所谓随机分配考号这件事, 其核心要点简单来说就是包括几方面的内容。生成不重复的随机序列, 这个功能比自己手动写循环并且加上去重逻辑来操作要可靠得多。种子参数可以决定结果是否可复现, 在调试阶段应当固定种子, 而在正式使用时务必将其去掉。将数据写回 Excel 文件时采用 True 参数进行纵向写入, 同时利用 with 上下文管理器确保系统资源被正确释放。类似的思路其实还可以被应用在很多不同的场景里面, 比方说进行随机分班, 比方说进行抽奖活动, 又或者是进行抽样调查, 再或者是洗牌操作等等, 这些操作的核心逻辑其实都是要生成一个不重复的随机排列顺序, 只要掌握了这个基本的处理模式, 那么这类需求基本上都可以被快速解决掉。