random.choices() 更适合抽奖权重逻辑,因其支持 weights 参数按比例分配概率、允许重复抽取,而 random.choice() 无法实现加权;需手动处理不放回抽样、去重及导出校验。

为什么 random.choices() 比 random.choice() 更适合抽奖权重逻辑
因为 random.choice() 对所有选项一视同仁,没法表达“iPhone 中奖概率是纸巾的 10 倍”这种业务需求;而 random.choices() 支持 weights 参数,直接按数值比例分配概率,且允许重复抽中(比如多人可同时中同一奖品),符合真实抽奖场景。
注意:权重不要求归一化,[10, 1, 5] 和 [100, 10, 50] 效果一致;但必须是正数,传负数或零会抛 ValueError: weights must be non-negative。
- 若需「不放回」抽(每人仅中一次、奖品不可重复),得自己维护已抽列表 + 动态更新剩余权重,不能只靠
random.choices()一步到位 -
k参数控制抽取次数,设为1是单次抽奖,设为10就是一次出 10 个中奖者(可能重复) - Python 3.6+ 才有
random.choices(),旧版本得用numpy.random.choice()或手写累积权重二分查找
导出中奖名单时,csv.writer 比 pandas.DataFrame.to_csv() 更轻量也更可控
抽奖模块通常不需要分析、透视或缺失值处理,只是把 name、prize、timestamp 三列原样存成 Excel 可打开的表格。用 pandas 会多引入依赖、启动慢,还容易因默认参数导致中文乱码或索引列多出一列。
直接用标准库 csv 模块,配合 open(..., encoding='utf-8-sig') 即可兼容 Windows Excel 的 UTF-8 BOM 识别:
import csv
with open('winners.csv', 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerow(['姓名', '奖项', '抽中时间'])
for name, prize in winners:
writer.writerow([name, prize, datetime.now().strftime('%Y-%m-%d %H:%M:%S')])- 务必加
newline='',否则 Windows 下用csv.writer会空一行(Python 文档明确要求) -
utf-8-sig编码比纯utf-8多写 BOM 头,Excel 才能正确识别中文 - 如果名单要带序号(第1名、第2名),别用
enumerate()后再写,直接在writerow里拼[i+1, name, prize, ...]
Sublime Text 里调试权重抽奖逻辑,别依赖 print(),用 subprocess.run() 调 Python 解释器更可靠
Sublime 是编辑器,不是 IDE,没内置变量监视或断点。写完 draw_prize(weights=[...]) 后想验证结果分布是否符合预期,靠肉眼看几次 print() 输出完全不可靠——小样本偏差太大。
推荐写个最小验证脚本,用 subprocess.run() 批量跑 10000 次,再统计频次:
import subprocess
result = subprocess.run(
['python', '-c', '''
import random
weights = [70, 20, 10] # 一等奖:二等奖:三等奖
samples = [random.choices(["A", "B", "C"], weights=weights)[0] for _ in range(10000)]
print({k: v/10000 for k, v in __import__("collections").Counter(samples).items()})
'''],
capture_output=True, text=True
)
print(result.stdout)- 这样避免在 Sublime 控制台里反复手动运行,也绕过插件对
sys.stdout的劫持问题 - 权重和样本数写死在命令行字符串里,改起来快,不用切文件、保存、再运行
- 如果发现实际比例严重偏离(如
70:20:10抽出来变成65:25:10),大概率是权重数组里混入了浮点误差或隐式类型转换(比如从 JSON 读进来变成70.0,但random.choices接受)
导出名单前必须校验中奖者是否重复,尤其当 k > 1 且未禁用重复时
业务上常要求“同一人不能重复中奖”,但 random.choices() 默认允许重复。如果没做去重,导出的 CSV 里可能出现张三中两次一等奖——这在审计或发奖时就是事故。
简单去重不等于用 set(),因为顺序会丢、且无法处理“张三中一等奖,李四中二等奖,张三再中三等奖”这类合法重复(不同奖)。正确做法是按业务规则定义“重复”:
- 若“每人限中一个奖”,抽完立刻用
dict记录{name: prize},后续再抽到已存在name就跳过并重抽 - 若“每人每个奖限中一次”,得用
set存(name, prize)元组 - 导出前加一行
assert len(winners) == len(set(w[0] for w in winners)),测试环境直接崩给你看,比上线后被投诉强
权重算法本身不保证唯一性,去重逻辑必须显式写清楚,藏在“抽完再 dedup”这种模糊表述里,迟早出问题。

















