当前位置:首页>python>如何使用 Python 的 random 模块生成随机数、实现随机乱序和随机抽样?

如何使用 Python 的 random 模块生成随机数、实现随机乱序和随机抽样?

  • 2026-10-11 07:09:59
如何使用 Python 的 random 模块生成随机数、实现随机乱序和随机抽样?

Python 的 random,用不好真会把测试数据搞歪

脚本里要随机挑 20 个用户做回访,结果每次跑出来都像“假随机”。

我第一眼一般不怀疑 Python,先看代码。十有八九是把 random.choice()、random.sample()、random.shuffle() 混着用,甚至还拿 random 生成登录验证码。这个地方得先掰正:random 适合测试、抽样、打乱顺序、模拟数据,不适合密码、Token、验证码这种安全场景。

先看最常用的随机数。

import random

# 生成 1 到 100 之间的整数,包含 1 和 100
order_no_tail = random.randint(1, 100)

# 从 0、5、10、15、20 里随机拿一个
retry_gap = random.randrange(0, 21, 5)

# 生成浮点数,适合做折扣、比例、模拟耗时
mock_cost = random.uniform(0.3, 2.8)

print(order_no_tail, retry_gap, round(mock_cost, 2))

这里有个细节,randint(1, 100) 两头都包含,randrange(0, 21, 5) 更像 range(),右边不包含。这个坑不大,但线上脚本里经常见。比如你想随机 0 到 20,写成 randrange(0, 20),那 20 永远出不来。

我平时写批量脚本,更多用它做“抽几条看看”。

比如一批用户 ID,随机抽 5 个做接口回归:

import random

user_ids = [
"u_1001", "u_1002", "u_1003", "u_1004",
"u_1005", "u_1006", "u_1007", "u_1008"
]

picked = random.sample(user_ids, 5)

for uid in picked:
    print("check user:", uid)

sample() 有个好处:不重复。

这点很重要。你要抽 5 个用户,就真的是 5 个不同用户。不要用循环套 choice(),那玩意儿可能重复。

这种代码我见过:

import random

bad_picked = []

for _ in range(5):
    bad_picked.append(random.choice(user_ids))

print(bad_picked)

看着没毛病,实际可能抽出:

['u_1003', 'u_1003', 'u_1008', 'u_1001', 'u_1003']

如果只是模拟点击,那还行。如果是抽样验数据,这结果就有点糊弄自己了。

choice() 更适合“从几个策略里挑一个”。

import random

backup_nodes = ["node-a", "node-b", "node-c"]

target_node = random.choice(backup_nodes)
print("send request to:", target_node)

这类用法很干净。不要让它承担抽样的活。

再看乱序。

随机乱序我一般用在两类地方:一是批量任务别老按同一个顺序打到同一批资源上;二是测试时把数据顺序打散,看看代码有没有偷偷依赖顺序。

import random

jobs = [
    {"job_id": "j_01", "type": "sync_order"},
    {"job_id": "j_02", "type": "sync_user"},
    {"job_id": "j_03", "type": "fix_coupon"},
    {"job_id": "j_04", "type": "check_refund"},
]

random.shuffle(jobs)

for job in jobs:
    print(job["job_id"], job["type"])

注意,shuffle() 是原地打乱。

也就是说,原来的 jobs 列表被改了。如果后面代码还要用原始顺序,别直接 shuffle。

我一般会这么写:

import random

raw_jobs = ["sync_order", "sync_user", "fix_coupon", "check_refund"]

run_jobs = raw_jobs[:]
random.shuffle(run_jobs)

print("origin:", raw_jobs)
print("run:", run_jobs)

这个小切片救过不少低级 bug。尤其是脚本越写越长,前面把列表打乱了,后面还以为它是原来的顺序,查起来很烦。

还有一种场景,带权重随机。

比如灰度策略里,90% 走老逻辑,10% 走新逻辑。别自己用一堆 if random.randint() 硬凑,Python 已经给了 choices()。

import random

routes = ["old_parser", "new_parser"]

for _ in range(10):
    route = random.choices(routes, weights=[90, 10], k=1)[0]
    print("use:", route)

这里 choices() 和 choice() 不是一回事。

choices() 可以重复,可以带权重,返回的是列表。哪怕 k=1,它返回的也是列表,所以后面要 [0]。这个地方我第一次也嫌它啰嗦,但习惯了还好。

如果想让随机结果可复现,就用 seed()。

排查测试失败时很有用。随机数据跑崩了,第二次跑又不崩,这种问题最烦。加个种子,至少能把现场固定住。

import random

random.seed(202406)

prices = []

for _ in range(5):
    price = round(random.uniform(10, 99), 2)
    prices.append(price)

print(prices)

同一个种子,同一段代码,生成结果会保持一致。

但别在正式逻辑里乱加固定 seed。否则你以为自己在随机,其实每次都走同一套结果。测试脚本里可以,业务代码里要小心。

最后补一个我常用的小脚本,随机抽日志行,不用把整个文件都塞进内存。大文件这么干舒服一点。

import random

defpick_lines(log_path, limit=20):
    box = []

with open(log_path, "r", encoding="utf-8") as f:
for line_no, line in enumerate(f, 1):
            line = line.rstrip()
ifnot line:
continue

if len(box) < limit:
                box.append(line)
continue

            hit = random.randint(1, line_no)
if hit <= limit:
                box[hit - 1] = line

return box


for item in pick_lines("app.log", 10):
    print(item)

这段不是为了炫技,就是一个现场脚本的写法。

日志很大时,readlines() 一把梭容易把内存顶上去。随机抽几行看错误分布,用这种 reservoir sampling 的思路更稳。

random 这模块不复杂,真正容易出问题的是选错函数。

要唯一抽样,用 sample()。

要抽一个,用 choice()。

要打乱原列表,用 shuffle()。

要带权重,用 choices()。

要复现问题,用 seed()。

要生成安全验证码、密码、Token,别用它,换 secrets。这个边界记住,基本就不会写出太离谱的随机代码。

最新文章

随机文章