如何使用 Python 的 random 模块生成随机数、实现随机乱序和随机抽样?
抽检脚本跑了三次,三次结果一模一样。
第一眼我就不太信“随机数坏了”。Python 的 random 没那么容易坏,更多时候是代码写得太随手:把 seed 固定了、抽样后又改原列表、或者把“乱序”和“抽样”混成一件事。
random 这个模块,平时写脚本够用了。比如生成测试订单号、随机挑几条数据回放、把任务列表打散、从一批用户里抽样做灰度。别拿它做密码、验证码、密钥,这个后面单独说。
先看最常见的随机数。
import random# 0 到 1 之间的小数,不包含 1
score = random.random()
# 生成 1000 到 9999 的整数,左右都包含
ticket_no = random.randint(1000, 9999)
# 从 0, 5, 10, 15 ... 95 里挑一个
bucket = random.randrange(0, 100, 5)
# 生成一个浮点金额,模拟压测数据
amount = round(random.uniform(8.8, 299.9), 2)
print(score, ticket_no, bucket, amount)
这里有个小坑,randint(1, 10) 是包含 10 的,randrange(1, 10) 不包含 10。
这个差异不大,但写抽奖、分桶、分页随机偏移的时候,边界错一次就挺恶心。尤其是 randrange(len(items)) 这种写法,刚好适合拿来取列表下标。
users = ["u_101", "u_102", "u_103", "u_104"]idx = random.randrange(len(users))
picked_user = users[idx]
print(picked_user)
如果只是从列表里拿一个,别绕这么远,直接用 choice。
levels = ["debug", "info", "warn", "error"]log_level = random.choice(levels)
print(log_level)
我平时写一些造数据脚本,比较喜欢把随机逻辑收在一个小函数里。后面要调权重、加字段,不至于满文件都是 random.xxx。
import random
from datetime import datetimedefbuild_mock_event(user_id: str) -> dict:
actions = ["login", "search", "pay", "logout"]
areas = ["beijing", "shanghai", "hangzhou", "chengdu"]
return {
"user_id": user_id,
"action": random.choice(actions),
"area": random.choice(areas),
"cost_ms": random.randint(20, 1800),
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
for uid in ["u_1001", "u_1002", "u_1003"]:
print(build_mock_event(uid))
这段不复杂,但够用。真到线上排障,很多时候缺的不是一套漂亮框架,而是能马上造出几条像样数据的脚本。
再看随机乱序。
shuffle 会直接改原列表,这点要记住。它不是返回一个新列表。
import randomtask_ids = ["job_01", "job_02", "job_03", "job_04", "job_05"]
random.shuffle(task_ids)
print(task_ids)
如果原列表后面还要用,就别直接 shuffle。这个坑我见过,前面为了“随机执行任务”打乱了一下,后面生成对账文件也用了同一个列表,结果顺序全乱了,还以为是导出逻辑有问题。
稳一点这么写:
origin_tasks = ["job_01", "job_02", "job_03", "job_04", "job_05"]run_tasks = origin_tasks[:]
random.shuffle(run_tasks)
print("原始顺序:", origin_tasks)
print("执行顺序:", run_tasks)
还有一个更干净的写法,用 sample 抽取全部元素,它会返回新列表,也能达到乱序效果。
origin_tasks = ["job_01", "job_02", "job_03", "job_04", "job_05"]run_tasks = random.sample(origin_tasks, k=len(origin_tasks))
print(run_tasks)
shuffle 适合你明确就是要改原列表。sample 适合你想要一个新的随机结果。
随机抽样这块,最容易写错的是“是否允许重复”。
sample 是不放回抽样,抽过的不会再出现。
import randomorder_ids = [
"od_10001", "od_10002", "od_10003",
"od_10004", "od_10005", "od_10006"
]
check_orders = random.sample(order_ids, k=3)
print(check_orders)
这个场景很常见,比如从当天订单里抽 100 条做人工核对。抽出来的订单不应该重复,那就用 sample。
如果允许重复,用 choices。
colors = ["red", "blue", "green"]balls = random.choices(colors, k=10)
print(balls)
choices 还有个挺实用的参数:权重。
比如做接口压测,不同接口流量比例不一样。别每个接口平均随机,那样测出来的数据会骗人。
import randomapis = ["/api/login", "/api/search", "/api/order/create", "/api/pay"]
weights = [30, 50, 15, 5]
for _ in range(10):
api = random.choices(apis, weights=weights, k=1)[0]
print(api)
这段意思是搜索接口流量更高,支付接口流量最低。虽然只是脚本,但比瞎随机靠谱一点。
还有一个现场经常用到的点:固定随机结果。
比如你排查一个批处理 bug,随机抽样抽到了某几条脏数据。你第二次想复现,就需要 seed。
import randomrandom.seed(20240618)
user_ids = [f"u_{i}"for i in range(1000, 1020)]
picked = random.sample(user_ids, k=5)
print(picked)
只要 seed 一样,随机序列就一样。
这东西适合测试复现,不适合放在正式随机逻辑里。尤其别在循环里反复 random.seed(),那样随机性会被你自己干掉。
我见过这种写法:
for item in items:
random.seed(100)
print(random.randint(1, 10))
看着每次都在随机,其实结果基本被锁死了。seed 一般放在脚本入口,真需要复现时才打开。
最后补一刀安全问题。
random 是伪随机,适合模拟、抽样、乱序,不适合生成密码、短信验证码、重置 token。安全相关的东西,用 secrets。
import secretscode = "".join(str(secrets.randbelow(10)) for _ in range(6))
token = secrets.token_urlsafe(24)
print(code)
print(token)
所以平时写 Python 脚本,我大概这么分:
生成普通随机数,用 random()、randint()、randrange()。
从列表里挑一个,用 choice()。
打乱列表,用 shuffle(),但注意它会改原列表。
不重复抽样,用 sample()。
允许重复、还要按权重抽,用 choices()。
要复现问题,用 seed()。
涉及安全,别碰 random,直接换 secrets。
这几个函数掌握住,日常写脚本基本够了。别小看这些小工具,很多排障脚本、数据清洗脚本、压测造数脚本,最后拼的就是这些不起眼的细节。