Python random 模块完全指南
本文档面向零基础新手,目标是让你真正理解:
random模块是什么,能做什么- 生成随机整数、浮点数的各种方法
- 对列表进行随机抽取、打乱顺序
- 随机种子(seed)是什么,为什么要用它
- 各种概率分布(正态分布、指数分布等)
- 如何用随机数解决实际问题(抽奖、密码、模拟、游戏)
- 安全随机数 vs 普通随机数的区别
配有大量可运行示例,全部从最基础讲起。
第一部分:random 模块是什么?
1.1 为什么需要随机数?
生活中随机无处不在:
- 抽奖时随机抽取获奖者
- 游戏里随机生成地图、掉落装备
- 密码学中生成随机密钥
- 科学模拟中用随机采样来估算结果
- 机器学习中随机划分训练集和测试集
Python 内置的 random 模块就是为了满足这些需求而生的。
import random # 只需要这一行就能使用所有功能
# 快速体验
print(random.random()) # 0.0 到 1.0 之间的随机浮点数
print(random.randint(1, 100)) # 1 到 100 之间的随机整数
print(random.choice(["石头", "剪刀", "布"])) # 随机选一个
1.2 伪随机数:计算机的随机数是”真随机”吗?
重要概念: 计算机产生的随机数其实是”伪随机数(Pseudo-random numbers)“。
真随机:
来自真实物理现象(如放射性衰变、热噪声)
完全不可预测,不能重现
伪随机:
用数学算法产生的"看起来随机"的数字序列
从同一个"种子"出发,每次产生的序列完全相同
这正是我们在科学实验和调试中需要的——可重现!
import random
# 设置种子后,每次运行结果完全相同
random.seed(42)
print(random.random()) # 0.6394267984578837(永远是这个)
print(random.random()) # 0.025010755222666936(永远是这个)
print(random.random()) # 0.27502931836911926(永远是这个)
# 不同种子,产生不同序列
random.seed(100)
print(random.random()) # 完全不同的数字
Python random 模块的底层算法是 Mersenne Twister(梅森旋转算法), 周期长达 2^19937-1,对于日常使用完全够用。
第二部分:生成随机数
2.1 random()——最基础:生成 [0.0, 1.0) 的浮点数
import random
# random() 返回 0.0(含)到 1.0(不含)之间的浮点数
for i in range(5):
print(f"第{i+1}次:{random.random():.6f}")
# 输出示例:
# 第1次:0.374540
# 第2次:0.950714
# 第3次:0.731994
# 第4次:0.598658
# 第5次:0.156019
用 random() 生成任意范围的浮点数:
# 生成 [a, b) 范围内的浮点数,公式:a + (b-a) * random()
a, b = 10, 20
value = a + (b - a) * random.random()
print(f"{a} 到 {b} 之间:{value:.4f}")
# 生成 0 到 100 的百分比
percent = random.random() * 100
print(f"随机百分比:{percent:.2f}%")
2.2 uniform(a, b)——指定范围的浮点数
uniform(a, b) 是 a + (b-a) * random() 的简写版,更直观:
import random
# 生成 5.0 到 10.0 之间的浮点数
price = random.uniform(5.0, 10.0)
print(f"随机价格:¥{price:.2f}") # 如:¥7.83
# 注意:a 可以大于 b
print(random.uniform(10, 1)) # 也可以,范围是 [1, 10]
# 实际应用:模拟测量误差
true_value = 100.0
measured = true_value + random.uniform(-0.5, 0.5)
print(f"真实值:{true_value},测量值:{measured:.4f}")
2.3 randint(a, b)——随机整数(含两端)
randint(a, b) 返回 a 到 b(含 a 和 b)之间的随机整数:
import random
# 模拟掷骰子(1到6)
dice = random.randint(1, 6)
print(f"掷出了:{dice} 点")
# 验证两端都会出现
results = [random.randint(1, 6) for _ in range(10000)]
for face in range(1, 7):
count = results.count(face)
print(f" {face}点:{count}次({count/100:.1f}%)")
# 每个面出现概率约 16.7%
容易混淆的地方:
# randint(a, b):包含 b!
print(random.randint(1, 10)) # 可能输出 1 到 10,10 也会出现
# 和 range(1, 10) 不同!range(1, 10) 不包含 10
# 等价于:random.randrange(1, 11)
2.4 randrange(start, stop, step)——更灵活的随机整数
randrange 和 range() 参数一模一样,但随机返回其中一个值:
import random
# randrange(stop):0 到 stop-1
print(random.randrange(10)) # 0 到 9
# randrange(start, stop):start 到 stop-1(不含 stop)
print(random.randrange(1, 10)) # 1 到 9(不含 10!)
# randrange(start, stop, step):按步长选
# 等价于 random.choice(range(0, 20, 2))
even = random.randrange(0, 20, 2)
print(f"随机偶数(0-18):{even}") # 0,2,4,...,18 中的一个
odd = random.randrange(1, 20, 2)
print(f"随机奇数(1-19):{odd}") # 1,3,5,...,19 中的一个
# 实际应用:随机选择特定倍数
multiple_of_5 = random.randrange(5, 101, 5)
print(f"5的倍数(5-100):{multiple_of_5}")
randint vs randrange 对比:
# randint(1, 10):包含 10
# randrange(1, 10):不包含 10,即 1-9
# randrange(1, 11):等价于 randint(1, 10)
# 记忆技巧:randrange 和 range 一样,不含右端点
2.5 getrandbits(k)——生成 k 位随机二进制整数
import random
# 生成指定位数的随机整数
bit4 = random.getrandbits(4) # 0 到 15(4位二进制)
bit8 = random.getrandbits(8) # 0 到 255(8位)
bit32 = random.getrandbits(32) # 0 到 2^32-1
print(f"4位:{bit4}(二进制:{bin(bit4)})")
print(f"8位:{bit8}(二进制:{bin(bit8)})")
print(f"32位:{bit32}")
# 实际应用:生成随机的 ID
token = random.getrandbits(128)
print(f"128位随机ID:{token}")
print(f"十六进制:{hex(token)}")
第三部分:序列相关操作
3.1 choice(seq)——从序列中随机选一个
choice 从非空序列(列表、元组、字符串)中随机选一个元素:
import random
# 从列表选
fruits = ["苹果", "香蕉", "橙子", "葡萄", "西瓜"]
print(f"今天吃:{random.choice(fruits)}")
# 从元组选
directions = ("上", "下", "左", "右")
move = random.choice(directions)
print(f"向{move}走")
# 从字符串选(选一个字符)
chars = "ABCDEFG"
char = random.choice(chars)
print(f"随机字母:{char}")
# 实用:模拟石头剪刀布
def computer_move():
return random.choice(["石头", "剪刀", "布"])
for round_ in range(3):
print(f"第{round_+1}局:电脑出了{computer_move()}")
注意:序列不能为空,否则报错:
try:
random.choice([]) # IndexError: Cannot choose from an empty sequence
except IndexError as e:
print(f"错误:{e}")
3.2 choices(population, weights, k)——有权重的随机抽取(可重复)
choices 支持权重,且允许重复抽取(放回抽样):
import random
# 基础用法:从列表中抽 k 个(可重复)
colors = ["红", "绿", "蓝"]
result = random.choices(colors, k=5)
print(f"抽到:{result}") # ['红', '蓝', '红', '红', '绿']
# 设置权重(红色概率更高)
# weights 和列表一一对应,不需要相加等于1
weighted = random.choices(
population=["红", "绿", "蓝"],
weights=[5, 3, 2], # 红:绿:蓝 = 5:3:2
k=100
)
print(f"红色出现:{weighted.count('红')}次(期望约50次)")
print(f"绿色出现:{weighted.count('绿')}次(期望约30次)")
print(f"蓝色出现:{weighted.count('蓝')}次(期望约20次)")
# cum_weights:累积权重(效果相同,只是另一种写法)
# weights=[5,3,2] 等价于 cum_weights=[5,8,10]
result2 = random.choices(
["一等奖", "二等奖", "三等奖", "谢谢参与"],
cum_weights=[1, 5, 15, 100], # 累积:1%, 5%, 15%, 100%
k=1
)[0]
print(f"抽奖结果:{result2}")
模拟一个加权骰子:
import random
from collections import Counter
# 作弊骰子:6点概率是其他面的3倍
biased_dice = random.choices(
[1, 2, 3, 4, 5, 6],
weights=[1, 1, 1, 1, 1, 3], # 6的权重是3,其他是1
k=10000
)
counts = Counter(biased_dice)
print("作弊骰子统计:")
for face in range(1, 7):
pct = counts[face] / 100
bar = "█" * (counts[face] // 100)
print(f" {face}点:{counts[face]:5d}次({pct:.1f}%){bar}")
3.3 sample(population, k)——不重复抽取(无放回抽样)
sample 从序列中随机选 k 个不重复的元素:
import random
# 从 1-100 中不重复地选 6 个数(模拟彩票)
lottery = random.sample(range(1, 101), 6)
lottery.sort()
print(f"本期开奖号码:{lottery}")
# 和 choices 的区别:sample 不重复!
pool = [1, 2, 3, 4, 5]
print(f"sample(不重复):{random.sample(pool, 3)}")
print(f"choices(可重复):{random.choices(pool, k=3)}")
# 从字符串中取样(不重复字符)
word = "PYTHON"
letters = random.sample(word, 4)
print(f"从{word}随机选4个字母:{''.join(letters)}")
# k 不能超过序列长度!
try:
random.sample([1, 2, 3], 5) # ValueError
except ValueError as e:
print(f"错误:{e}")
实际应用——随机分组:
import random
students = ["张三", "李四", "王五", "赵六", "钱七", "孙八", "周九", "吴十"]
# 随机打乱顺序后分组
shuffled = random.sample(students, len(students)) # 等同于洗牌
group_size = 2
groups = [shuffled[i:i+group_size] for i in range(0, len(shuffled), group_size)]
print("随机分组结果:")
for i, group in enumerate(groups, 1):
print(f" 第{i}组:{'、'.join(group)}")
3.4 shuffle(seq)——原地打乱序列
shuffle 直接修改原列表,没有返回值:
import random
# 基础用法
cards = list(range(1, 11)) # [1, 2, 3, ..., 10]
print(f"洗牌前:{cards}")
random.shuffle(cards)
print(f"洗牌后:{cards}")
# 注意:shuffle 修改原列表,返回 None!
deck = ["A", "2", "3", "4", "5"]
result = random.shuffle(deck)
print(f"deck:{deck}") # 已被打乱
print(f"result:{result}") # None!!!
# 如果需要保留原列表,先复制
original = [1, 2, 3, 4, 5]
shuffled = original.copy()
random.shuffle(shuffled)
print(f"原列表:{original}") # 不变
print(f"打乱后:{shuffled}") # 打乱
# 实际应用:发牌
def create_deck():
suits = ["♠", "♥", "♦", "♣"]
values = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
return [f"{v}{s}" for s in suits for v in values]
deck = create_deck()
random.shuffle(deck)
players = 4
hands = 5 # 每人5张
print("发牌结果:")
for i in range(players):
hand = deck[i*hands : (i+1)*hands]
print(f" 玩家{i+1}:{' '.join(hand)}")
3.5 choice vs choices vs sample vs shuffle 对比总结
┌──────────┬──────────────────────────────┬──────────┬──────────────────────────┐
│ 方法 │ 用途 │ 重复 │ 返回值 │
├──────────┼──────────────────────────────┼──────────┼──────────────────────────┤
│ choice │ 选 1 个 │ - │ 单个元素 │
│ choices │ 选 k 个(可加权) │ 允许重复 │ 新列表 │
│ sample │ 选 k 个(不重复) │ 不重复 │ 新列表 │
│ shuffle │ 打乱整个列表 │ - │ None(原地修改!) │
└──────────┴──────────────────────────────┴──────────┴──────────────────────────┘
第四部分:随机种子(seed)
4.1 什么是种子?
随机数生成器就像一台机器:
- 种子(seed)= 机器的初始状态
- 相同的种子 → 相同的初始状态 → 产生完全相同的随机序列
- 不设种子 → Python 用当前时间戳作为种子 → 每次运行结果不同
import random
# ===== 不设种子:每次运行结果不同 =====
print("不设种子:")
print(random.randint(1, 100)) # 每次运行都不同
print(random.randint(1, 100))
print()
# ===== 设置种子:每次运行结果完全相同 =====
print("种子=42:")
random.seed(42)
print(random.randint(1, 100)) # 永远是 52
print(random.randint(1, 100)) # 永远是 93
print(random.randint(1, 100)) # 永远是 15
print()
# 再次设置相同种子,序列重置
random.seed(42)
print("重置种子=42:")
print(random.randint(1, 100)) # 还是 52(和前面一样)
print(random.randint(1, 100)) # 还是 93
4.2 种子的实际用途
import random
# 用途1:让实验结果可重现(科学研究、机器学习调试)
def train_model(seed=None):
if seed is not None:
random.seed(seed)
data = list(range(100))
random.shuffle(data)
train = data[:80]
test = data[80:]
print(f"训练集前5个:{train[:5]}")
print(f"测试集前5个:{test[:5]}")
print("第1次运行(seed=2024):")
train_model(seed=2024)
print()
print("第2次运行(seed=2024):")
train_model(seed=2024) # 完全相同!
print()
print("第3次运行(不设种子):")
train_model() # 每次不同
# 用途2:游戏地图生成——相同种子生成相同地图
def generate_map(seed, size=5):
random.seed(seed)
terrain = []
types = ["🌲", "🏔", "🌊", "🌾", "🏜"]
for _ in range(size):
row = [random.choice(types) for _ in range(size)]
terrain.append(row)
return terrain
print("n地图(种子=100):")
for row in generate_map(100):
print(" ".join(row))
print("n地图(种子=100,再次生成):")
for row in generate_map(100):
print(" ".join(row)) # 完全相同的地图!
4.3 保存和恢复随机状态
import random
random.seed(0)
# 产生几个随机数
for _ in range(3):
random.random()
# 保存当前状态
state = random.getstate()
# 继续产生随机数
r1 = random.random()
r2 = random.random()
r3 = random.random()
print(f"原始序列:{r1:.4f} {r2:.4f} {r3:.4f}")
# 恢复状态
random.setstate(state)
# 再次产生,完全相同
r1b = random.random()
r2b = random.random()
r3b = random.random()
print(f"恢复后: {r1b:.4f} {r2b:.4f} {r3b:.4f}")
print(f"完全相同:{r1==r1b and r2==r2b and r3==r3b}")
第五部分:概率分布
5.1 为什么需要不同的概率分布?
uniform(均匀分布):每个值等概率出现
→ 掷骰子、彩票、简单随机选择
normalvariate(正态分布):中间多、两端少的"钟形"分布
→ 人的身高体重、测量误差、成绩分布、股价波动
expovariate(指数分布):等待时间分布
→ 顾客到店的间隔时间、设备故障间隔
其他分布用于特定科学/工程计算场景
5.2 gauss / normalvariate——正态分布
正态分布有两个参数:
mu(μ):均值(分布的中心)sigma(σ):标准差(分布的宽度,越大越分散)
import random
# gauss(mu, sigma):正态分布(速度稍快)
# normalvariate(mu, sigma):正态分布(线程安全)
# 对普通用途,二者效果相同
# 模拟一班学生成绩(均值70,标准差15)
scores = [random.gauss(70, 15) for _ in range(1000)]
# 统计分布
buckets = {"不及格(<60)": 0, "及格(60-69)": 0,
"良好(70-79)": 0, "优秀(80-89)": 0, "满分(90+)": 0}
for s in scores:
if s < 60: buckets["不及格(<60)"] += 1
elif s < 70: buckets["及格(60-69)"] += 1
elif s < 80: buckets["良好(70-79)"] += 1
elif s < 90: buckets["优秀(80-89)"] += 1
else: buckets["满分(90+)"] += 1
print("成绩分布(1000人):")
for label, count in buckets.items():
bar = "█" * (count // 10)
print(f" {label:15s}:{count:4d}人 {bar}")
输出(示例):
成绩分布(1000人):
不及格(<60) : 154人 ███████████████
及格(60-69) : 237人 ███████████████████████
良好(70-79) : 251人 █████████████████████████
优秀(80-89) : 228人 ██████████████████████
满分(90+) : 130人 █████████████
5.3 expovariate(lambd)——指数分布
指数分布描述”独立随机事件之间的等待时间”:
lambd(λ):单位时间内平均发生次数(速率)- 平均等待时间 = 1 / λ
import random
# 模拟咖啡店:平均每分钟来3个顾客
# lambd = 3 → 平均间隔 = 1/3 分钟 ≈ 20秒
arrivals = [random.expovariate(3) for _ in range(100)]
avg_interval = sum(arrivals) / len(arrivals)
print(f"100次到店间隔:平均 {avg_interval*60:.1f} 秒(期望约20秒)")
# 模拟一小时内到店时间轴
time = 0.0
count = 0
while time < 60: # 60分钟
interval = random.expovariate(3)
time += interval
if time < 60:
count += 1
print(f"1小时内到店顾客:{count}人(期望约180人)")
5.4 triangular(low, high, mode)——三角分布
三角分布指定最小值、最大值和最可能的值(众数):
import random
# 项目完工时间估算(最乐观3天,最悲观10天,最可能5天)
estimates = [random.triangular(3, 10, 5) for _ in range(10000)]
avg = sum(estimates) / len(estimates)
over_7 = sum(1 for x in estimates if x > 7) / len(estimates)
print(f"平均完工时间:{avg:.2f} 天")
print(f"超过7天的概率:{over_7:.1%}")
5.5 betavariate(alpha, beta)——Beta 分布
Beta 分布生成 (0, 1) 之间的值,常用于模拟概率、比例:
import random
# alpha=beta=1:退化为均匀分布
# alpha>beta:偏向高值(商品好评率高)
# alpha<beta:偏向低值(商品好评率低)
# 模拟两款商品的好评率
product_a = [random.betavariate(8, 2) for _ in range(1000)] # 通常是高好评
product_b = [random.betavariate(2, 5) for _ in range(1000)] # 通常是低好评
avg_a = sum(product_a) / len(product_a)
avg_b = sum(product_b) / len(product_b)
print(f"商品A 平均好评率:{avg_a:.1%}(期望:{8/(8+2):.1%})")
print(f"商品B 平均好评率:{avg_b:.1%}(期望:{2/(2+5):.1%})")
5.6 其他分布(一览)
import random
# vonmisesvariate(mu, kappa):圆形分布,用于方向/角度
angle = random.vonmisesvariate(0, 2) # 弧度值
print(f"随机角度:{angle:.4f} 弧度(≈{angle*180/3.14:.1f}°)")
# paretovariate(alpha):帕累托分布(80/20法则)
# 用于财富分配、城市规模等幂律分布模拟
wealth = random.paretovariate(1.5)
print(f"帕累托分布:{wealth:.4f}")
# weibullvariate(alpha, beta):威布尔分布
# 用于设备寿命、可靠性工程
lifetime = random.weibullvariate(10, 1.5) # 平均寿命约10年
print(f"设备寿命估计:{lifetime:.2f} 年")
# gammavariate(alpha, beta):伽马分布
g = random.gammavariate(2, 1)
print(f"伽马分布:{g:.4f}")
# lognormvariate(mu, sigma):对数正态分布(用于股价等)
stock_return = random.lognormvariate(0.01, 0.1)
print(f"股票收益乘数:{stock_return:.4f}")
第六部分:实战案例
6.1 案例一:密码生成器
import random
import string
def generate_password(length=12, use_upper=True, use_digits=True, use_symbols=True):
"""
生成指定规则的随机密码
参数:
length - 密码长度(默认12位)
use_upper - 是否包含大写字母
use_digits - 是否包含数字
use_symbols - 是否包含特殊符号
"""
# 构建字符池
chars = string.ascii_lowercase # 小写字母 a-z
if use_upper:
chars += string.ascii_uppercase # 大写字母 A-Z
if use_digits:
chars += string.digits # 数字 0-9
if use_symbols:
chars += "!@#$%^&*" # 常用特殊符号
# 确保密码至少包含每种类型的一个字符
must_have = [random.choice(string.ascii_lowercase)]
if use_upper:
must_have.append(random.choice(string.ascii_uppercase))
if use_digits:
must_have.append(random.choice(string.digits))
if use_symbols:
must_have.append(random.choice("!@#$%^&*"))
# 用 sample 填充剩余位置(不重复,但这里 choices 更合适)
remaining = random.choices(chars, k=length - len(must_have))
# 合并并打乱顺序
password_list = must_have + remaining
random.shuffle(password_list)
return "".join(password_list)
# 生成5个密码
print("生成的密码:")
for i in range(5):
pwd = generate_password(16)
print(f" {i+1}. {pwd}")
# 不同配置
print("n纯数字PIN码(6位):")
print(" " + "".join(random.choices(string.digits, k=6)))
print("n只含字母(8位):")
print(" " + generate_password(8, use_digits=False, use_symbols=False))
6.2 案例二:模拟抽奖系统
import random
class LotterySystem:
"""可配置的抽奖系统"""
def __init__(self, prizes):
"""
prizes: 字典,格式为 {"奖品名称": 权重, ...}
权重越大,中奖概率越高
"""
self.prizes = list(prizes.keys())
self.weights = list(prizes.values())
def draw_once(self):
"""抽取一次"""
return random.choices(self.prizes, weights=self.weights, k=1)[0]
def draw_multiple(self, n):
"""抽取 n 次(可重复)"""
return random.choices(self.prizes, weights=self.weights, k=n)
def simulate(self, n_draws=10000):
"""模拟 n 次抽奖,统计概率"""
results = self.draw_multiple(n_draws)
total = self.weights
total_w = sum(total)
print(f"模拟{n_draws}次抽奖结果:")
for prize in self.prizes:
count = results.count(prize)
actual = count / n_draws
expected = self.weights[self.prizes.index(prize)] / total_w
print(f" {prize:10s}:实际{actual:.2%} 期望{expected:.2%} "
f"(差距:{abs(actual-expected)*100:.2f}%)")
# 创建抽奖系统
lottery = LotterySystem({
"一等奖(iPhone)": 1,
"二等奖(AirPods)": 5,
"三等奖(购物券)": 14,
"参与奖(贴纸)": 80,
})
print("=== 单次抽奖 ===")
print(f"恭喜您获得:{lottery.draw_once()}")
print("n=== 十连抽 ===")
results = lottery.draw_multiple(10)
for r in results:
print(f" {r}")
print("n=== 概率模拟 ===")
lottery.simulate(100000)
6.3 案例三:蒙特卡洛估算 π
蒙特卡洛方法:用大量随机数来估算数学结果。
原理:
在边长为2的正方形内(面积4),有一个半径为1的圆(面积π)
随机撒点,落在圆内的概率 = π/4
因此:π ≈ 4 × (落在圆内的点数 / 总点数)
import random
def estimate_pi(n_samples=1_000_000):
"""用蒙特卡洛方法估算 π"""
inside = 0
for _ in range(n_samples):
x = random.uniform(-1, 1)
y = random.uniform(-1, 1)
# 判断点是否在半径为1的圆内
if x**2 + y**2 <= 1:
inside += 1
return 4 * inside / n_samples
# 测试不同样本量
print("蒙特卡洛估算 π:")
for n in [100, 1_000, 10_000, 100_000, 1_000_000]:
pi_est = estimate_pi(n)
error = abs(pi_est - 3.14159265) / 3.14159265 * 100
print(f" {n:>10,} 个样本:π ≈ {pi_est:.6f} 误差:{error:.4f}%")
输出(示例):
蒙特卡洛估算 π:
100 个样本:π ≈ 3.120000 误差:0.6915%
1,000 个样本:π ≈ 3.148000 误差:0.2025%
10,000 个样本:π ≈ 3.143200 误差:0.0466%
100,000 个样本:π ≈ 3.141880 误差:0.0196%
1,000,000 个样本:π ≈ 3.141652 误差:0.0123%
6.4 案例四:随机游走(股价模拟)
import random
def simulate_stock(initial_price=100.0, days=252, mu=0.0003, sigma=0.02, seed=None):
"""
模拟股票价格的随机游走
参数:
initial_price - 初始价格
days - 交易天数(252 = 一年)
mu - 每日平均收益率
sigma - 每日收益率标准差(波动率)
seed - 随机种子
"""
if seed is not None:
random.seed(seed)
prices = [initial_price]
for _ in range(days - 1):
daily_return = random.gauss(mu, sigma)
new_price = prices[-1] * (1 + daily_return)
prices.append(max(0.01, new_price)) # 价格不能为负
final = prices[-1]
total_ret = (final - initial_price) / initial_price * 100
max_p = max(prices)
min_p = min(prices)
return prices, final, total_ret, max_p, min_p
# 模拟一只股票一年的走势
prices, final, total_ret, high, low = simulate_stock(seed=2024)
print(f"初始价格:¥100.00")
print(f"最终价格:¥{final:.2f}")
print(f"全年收益:{total_ret:+.2f}%")
print(f"最高价格:¥{high:.2f}")
print(f"最低价格:¥{low:.2f}")
# 简单绘制走势(用字符画)
print("n价格走势(每周一个数据点):")
weekly = prices[::5] # 每5天取一个点
max_w = max(weekly)
min_w = min(weekly)
for i, p in enumerate(weekly):
bar_len = int((p - min_w) / (max_w - min_w) * 30)
bar = "█" * bar_len
print(f" 第{i*5+1:3d}天:¥{p:7.2f} {bar}")
6.5 案例五:简易文字游戏——猜数字
import random
def guessing_game():
"""猜数字游戏"""
secret = random.randint(1, 100)
max_try = 7
attempt = 0
print("=" * 40)
print(" 欢迎来到猜数字游戏!")
print(f" 我想了一个 1~100 的数字")
print(f" 你有 {max_try} 次机会,加油!")
print("=" * 40)
while attempt < max_try:
attempt += 1
remaining = max_try - attempt
try:
guess = int(input(f"n第{attempt}次猜测(还剩{remaining}次):"))
except ValueError:
print(" 请输入整数!")
attempt -= 1
continue
if guess < 1 or guess > 100:
print(" 超出范围!请输入 1~100 的数字")
attempt -= 1
continue
if guess == secret:
print(f"n🎉 恭喜!你猜对了!答案就是 {secret}!")
print(f" 用了 {attempt} 次猜中!")
return
elif guess < secret:
hint = "太小了!"
if secret - guess > 20:
hint += "(差很远呢)"
print(f" ↑ {hint}")
else:
hint = "太大了!"
if guess - secret > 20:
hint += "(差很远呢)"
print(f" ↓ {hint}")
print(f"n💔 游戏结束!答案是 {secret},下次加油!")
# 运行游戏
# guessing_game() # 取消注释即可运行
6.6 案例六:随机名字/词语生成器
import random
# 中文随机姓名生成
SURNAMES = ["赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈",
"张", "刘", "杨", "黄", "林", "朱", "何", "罗", "宋", "唐"]
NAMES_MALE = ["伟", "芳", "娜", "秀英", "敏", "静", "丽", "强", "磊",
"军", "洋", "勇", "艳", "杰", "娟", "涛", "明", "超", "秀兰"]
NAMES_FEMALE = ["雪", "梅", "霞", "云", "燕", "莉", "琴", "慧", "颖", "雯",
"婷", "美", "珍", "兰", "凤", "英", "文", "春", "华", "玲"]
def random_chinese_name(gender="random"):
if gender == "random":
gender = random.choice(["male", "female"])
surname = random.choice(SURNAMES)
pool = NAMES_MALE if gender == "male" else NAMES_FEMALE
# 随机1或2个字的名
if random.random() < 0.4:
given = random.choice(pool)
else:
given = random.choice(pool) + random.choice(pool)
return surname + given
# 生成一份虚假的名单
print("随机生成10个名字:")
for i in range(10):
name = random_chinese_name()
age = random.randint(18, 60)
city = random.choice(["北京", "上海", "广州", "深圳", "杭州", "成都", "武汉"])
print(f" {i+1:2d}. {name},{age}岁,来自{city}")
第七部分:安全随机数
7.1 普通 random 的安全性问题
random 模块使用 Mersenne Twister 算法:
✅ 速度快,统计特性好
❌ 不适合密码学用途!
原因:
Mersenne Twister 不是"密码学安全"的随机数生成器
攻击者只要观察到足够多的输出值(624个32位数),
就可以完整推算出内部状态,预测未来的所有随机数!
因此,如果你用 random 生成密码、令牌、验证码等,
存在被攻击者预测的安全风险。
7.2 secrets 模块——密码学安全随机数
Python 3.6+ 内置了 secrets 模块,专门用于安全场景:
import secrets
import string
# secrets.randbelow(n):安全的 0 到 n-1 随机整数
print(secrets.randbelow(100))
# secrets.randbits(k):安全的 k 位随机整数
print(secrets.randbits(32))
# secrets.choice(seq):安全版 choice
print(secrets.choice(["A", "B", "C", "D"]))
# secrets.token_bytes(n):n 字节随机二进制数据
token = secrets.token_bytes(16)
print(f"随机字节:{token.hex()}")
# secrets.token_hex(n):n 字节的十六进制字符串(常用于 API Key)
api_key = secrets.token_hex(32)
print(f"API Key:{api_key}")
# secrets.token_urlsafe(n):URL 安全的随机字符串
session_id = secrets.token_urlsafe(32)
print(f"Session ID:{session_id}")
def secure_password(length=16):
"""使用 secrets 生成真正安全的密码"""
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
while True:
password = "".join(secrets.choice(alphabet) for _ in range(length))
# 确保包含各类字符
has_upper = any(c.isupper() for c in password)
has_lower = any(c.islower() for c in password)
has_digit = any(c.isdigit() for c in password)
has_symbol = any(c in "!@#$%^&*" for c in password)
if all([has_upper, has_lower, has_digit, has_symbol]):
return password
print("n安全密码(用 secrets 生成):")
for i in range(3):
print(f" {secure_password()}")
7.3 random vs secrets 场景对比
使用 random 的场景(速度优先,无安全要求):
✅ 游戏开发(地图生成、战斗结算)
✅ 模拟实验(蒙特卡洛、数值模拟)
✅ 机器学习(数据打乱、随机采样)
✅ 测试数据生成
✅ 抽奖、游戏概率
使用 secrets 的场景(安全性优先):
✅ 用户密码生成
✅ API 密钥生成
✅ Session Token(登录令牌)
✅ 邮件验证码、短信验证码
✅ CSRF Token
✅ 任何"如果被预测会造成损失"的场景
第八部分:常见陷阱与注意事项
8.1 陷阱1:shuffle 不返回新列表
import random
data = [1, 2, 3, 4, 5]
# ❌ 错误写法(很多新手犯的错)
shuffled = random.shuffle(data)
print(shuffled) # None!!!原因:shuffle 原地修改,返回 None
# ✅ 正确写法1:先复制,再 shuffle
data2 = data.copy()
random.shuffle(data2)
print(data2) # 打乱后的列表
# ✅ 正确写法2:用 sample 代替 shuffle,它返回新列表
data3 = random.sample(data, len(data))
print(data3) # 打乱后的新列表,原 data 不变
# ✅ 正确写法3:sorted + key=lambda: random.random()
data4 = sorted(data, key=lambda x: random.random())
print(data4)
8.2 陷阱2:randint 和 randrange 的端点问题
import random
# randint(a, b):含 b(闭区间 [a, b])
values_int = set(random.randint(1, 5) for _ in range(10000))
print(f"randint(1,5) 可能出现的值:{sorted(values_int)}") # {1, 2, 3, 4, 5}
# randrange(a, b):不含 b(半开区间 [a, b))
values_range = set(random.randrange(1, 5) for _ in range(10000))
print(f"randrange(1,5) 可能出现的值:{sorted(values_range)}") # {1, 2, 3, 4}
# 容易犯错的场景
# 想从 0-9 中取一个数:
print(random.randrange(10)) # ✅ 0 到 9
print(random.randint(0, 9)) # ✅ 0 到 9
print(random.randrange(0, 10)) # ✅ 0 到 9
# print(random.randint(0, 10)) # ⚠️ 0 到 10!含 10!
8.3 陷阱3:多线程下的随机数不安全
import random
import threading
# ❌ 问题:多个线程共享同一个随机数生成器,可能导致结果不一致
# 解决方案1:每个线程创建自己的 Random 实例
def worker(thread_id):
# ✅ 每个线程用独立的 Random 对象
rng = random.Random() # 独立实例,自动获取不同种子
for _ in range(3):
print(f"线程{thread_id}:{rng.randint(1, 100)}")
threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]
for t in threads:
t.start()
for t in threads:
t.join()
8.4 陷阱4:种子相同但调用顺序不同
import random
# 种子固定后,结果依赖于调用顺序
random.seed(42)
a = random.randint(1, 10)
b = random.random()
c = random.choice(["x", "y", "z"])
print(f"顺序1:a={a}, b={b:.4f}, c={c}")
random.seed(42)
# 改变调用顺序!
c2 = random.choice(["x", "y", "z"]) # 先调用这个
a2 = random.randint(1, 10)
b2 = random.random()
print(f"顺序2:a={a2}, b={b2:.4f}, c={c2}")
# 结果完全不同!因为消耗随机数的顺序变了
8.5 陷阱5:用 random 生成安全令牌(危险!)
import random
import secrets
# ❌ 不安全:用 random 生成验证码
def bad_verification_code():
return "".join([str(random.randint(0, 9)) for _ in range(6)])
# ✅ 安全:用 secrets 生成验证码
def good_verification_code():
return "".join([str(secrets.randbelow(10)) for _ in range(6)])
print(f"不安全的验证码:{bad_verification_code()}")
print(f"安全的验证码: {good_verification_code()}")
第九部分:Random 类——创建独立的随机生成器
9.1 为什么要用 Random 类?
默认情况下,import random 后使用的是一个"全局实例"。
如果你需要:
- 多个独立的随机流(互不影响)
- 不同模块使用不同的种子
- 不影响全局随机状态
就应该使用 random.Random 类创建独立实例。
import random
# 创建独立的随机生成器实例
rng1 = random.Random(seed=100) # 种子100
rng2 = random.Random(seed=200) # 种子200
# 两个生成器完全独立
print("生成器1:", [rng1.randint(1, 10) for _ in range(5)])
print("生成器2:", [rng2.randint(1, 10) for _ in range(5)])
# 重置生成器1,生成器2不受影响
rng1.seed(100)
print("生成器1重置后:", [rng1.randint(1, 10) for _ in range(5)])
print("生成器2继续:", [rng2.randint(1, 10) for _ in range(5)])
# 全局随机状态没有被影响
print("全局 random:", random.randint(1, 10)) # 不受 rng1/rng2 影响
9.2 实际应用:游戏中多个独立随机流
import random
class GameEngine:
"""使用独立随机流的游戏引擎"""
def __init__(self, seed=None):
base_seed = seed or random.randint(0, 2**32)
# 不同系统使用不同种子,互相不干扰
self.map_rng = random.Random(base_seed ^ 0x1234) # 地图生成
self.loot_rng = random.Random(base_seed ^ 0x5678) # 掉落计算
self.enemy_rng = random.Random(base_seed ^ 0x9ABC) # 敌人 AI
self.effect_rng = random.Random(base_seed ^ 0xDEF0) # 视觉效果
def generate_map_tile(self):
tiles = ["草地", "森林", "山地", "河流", "沙漠"]
return self.map_rng.choice(tiles)
def roll_loot(self):
if self.loot_rng.random() < 0.1: # 10% 掉落稀有物品
return self.loot_rng.choice(["传奇武器", "稀有盔甲", "神秘药水"])
return self.loot_rng.choice(["金币", "草药", "普通装备"])
def enemy_action(self):
return self.enemy_rng.choice(["攻击", "防御", "逃跑", "使用技能"])
# 使用固定种子,确保游戏可重现
game = GameEngine(seed=42)
print("游戏地图(前6格):")
for _ in range(6):
print(f" {game.generate_map_tile()}", end=" ")
print()
print("n战斗掉落(5场):")
for i in range(5):
print(f" 第{i+1}场:{game.roll_loot()}")
print("n敌人行动(5次):")
for i in range(5):
print(f" 回合{i+1}:{game.enemy_action()}")
第十部分:完整速查表
10.1 函数分类速查
📌 生成随机数
random.random() → [0.0, 1.0) 的浮点数
random.uniform(a, b) → [a, b] 的浮点数
random.randint(a, b) → [a, b] 的整数(含两端)
random.randrange(stop) → [0, stop) 的整数
random.randrange(a, b) → [a, b) 的整数(不含 b)
random.randrange(a, b, s) → range(a,b,s) 中的整数
random.getrandbits(k) → k 位随机整数
📌 序列操作
random.choice(seq) → 随机选 1 个
random.choices(seq, k=n) → 随机选 n 个(可重复,可加权)
random.sample(seq, k) → 随机选 k 个(不重复)
random.shuffle(lst) → 原地打乱(返回 None!)
📌 随机种子
random.seed(n) → 设置种子
random.getstate() → 获取当前状态
random.setstate(state) → 恢复状态
📌 概率分布
random.gauss(mu, sigma) → 正态分布
random.normalvariate(mu, sigma) → 正态分布(线程安全版)
random.uniform(a, b) → 均匀分布
random.expovariate(lambd) → 指数分布
random.triangular(low,high,mode)→ 三角分布
random.betavariate(alpha, beta) → Beta 分布
random.lognormvariate(mu,sigma) → 对数正态分布
📌 安全随机(secrets 模块)
secrets.randbelow(n) → 安全随机整数 [0, n)
secrets.randbits(k) → 安全随机 k 位整数
secrets.choice(seq) → 安全随机选一个
secrets.token_hex(n) → n 字节的十六进制字符串
secrets.token_urlsafe(n) → URL 安全的随机字符串
secrets.token_bytes(n) → n 字节随机二进制数据
10.2 选择哪个函数?决策树
你想要的是...
生成随机数?
├─ 浮点数 0~1 → random()
├─ 浮点数 a~b → uniform(a, b)
├─ 整数(含两端) → randint(a, b)
└─ 整数(类似range)→ randrange(a, b, step)
从序列中选?
├─ 只选1个 → choice(seq)
├─ 选多个可重复 → choices(seq, k=n)
│ └─ 有概率权重 → choices(seq, weights=[...], k=n)
├─ 选多个不重复 → sample(seq, k)
└─ 打乱整个序列 → shuffle(seq)(原地!)
需要可重现结果? → seed(n) 设置种子
需要安全性? → 用 secrets 模块
需要独立随机流? → Random() 创建实例
需要概率分布? → gauss / expovariate / triangular 等
总结
random 模块是 Python 中最常用的内置模块之一。学完本章,你应该掌握:
- 基础函数:
random()、uniform()、randint()、randrange()的区别和使用场景 - 序列操作:
choice(选1个)、choices(有放回)、sample(无放回)、shuffle(原地打乱) - 随机种子:为什么要用
seed(),什么时候用 - 概率分布:正态分布、指数分布、三角分布等应用场景
- 安全随机:普通
random不能用于密码学,应使用secrets模块 - 独立实例:用
random.Random()创建独立的生成器,避免干扰全局状态 - 常见陷阱:
shuffle返回None、randint含端点、多线程安全等