紧接着再来两道题。
第四十九题,随机生成验证码:
#编写一个程序,该程序能够根据用户指定的长度生成一个验证码(可包含大小写英文字母以及数字)
#导入模块
import random
import string
#获取用户输入信息
lens = int(input('请输入你想要验证码的长度:'))
#定义验证码中可能包含的字符集(需要导入string模块)
chars = string.digits + string.ascii_letters
verify_code = ''
for i in range(lens):
verify_code += random.choice(chars)
print(f'本次生成的验证码是:{verify_code}')
代码的思路如下:
我的整体设计:先建"奖池",再"抽奖"
这题我脑子里的模型就是一台抽奖机:先把所有可能被抽中的东西装进一个池子,然后伸手进去摸一把,摸几次就拼出几个字符。拆开来是四步:备字符池 → 问长度 → 循环抽取 → 拼接输出。
第一步:字符池怎么来?------ 让 string 模块替我干活
验证码要"大小写字母加数字",老实人写法是把 a-z、A-Z、0-9 共 62 个字符手敲成一个字符串。但我不想在这种地方费手指,于是导入了 string 模块,它里面正好有现成的常量:
chars = string.digits + string.ascii_letters
string.digits 就是 '0123456789',string.ascii_letters 就是全部大小写字母,一个加号把它们拼成一个大字符串,我的"奖池"就建成了。这也是我第一次体会到:标准库连字符表都替你备好了。
第二步:长度交给用户
int(input(...)) 问出验证码长度。这里我刻意用了 int(),因为 input 拿到的永远是字符串,不转成整数的话,下一步 range(lens) 会直接报错。
第三步:循环抽取 + 字符串拼接(核心)
这是全程序最有趣的三行:
verify_code = ''
for i in range(lens):
verify_code += random.choice(chars)
先准备一个空字符串当"篮子";
random.choice(chars) 就是往池子里摸一把------每次从 62 个字符里随机抓 1 个(这正是对 48 题学的 random 家族的再扩展:random() 管小数、randint() 管整数、choice() 管"从序列里选一个");
+= 把摸到的字符接到篮子尾巴上,循环 lens 次,篮子就攒满了。
这个"先建空变量、循环里不断累加"的套路,其实就是** accumulator(累加器)模式**,只不过上次加的是数字,这次加的是字符串。
第四步:f-string 输出
print(f'本次生成的验证码是:{verify_code}')------f 前缀让变量能直接嵌在字符串里,比用加号拼接干净多了。
和前面题目的思路联系
这题像是 43 题和 48 题的"会师":48 题教我用现成模块搞随机,这题把它用到了真实场景里;43 题教我 int(input()) 转类型,这题继续用它问长度。不过我有个私货反思:43 题可是专门教"输入验证"的,而这题的 int(input(...)) 是裸奔的------用户要是输个 abc 或者负数,程序当场崩溃。写博文时我特意提一嘴:学有余力的朋友可以把这里替换成 43 题那套 while True + try/except,程序会立刻健壮起来。
总结
我的设计一句话概括:字符池交给 string 模块,随机抽取交给 random.choice,我只负责搭那个"循环拼接"的架子。三个零件全是现成的,程序却拼出了很实用的功能------这就是 Python "模块拼装"开发风格的甜头。
顺带预告:下一题"从范围里挑 3 个不重复的数",choice 可做不到不重复(同一奖品可能被摸两次),到时候得再认识一位新成员。
第五十题,从指定范围内选择3个不重复的数字:
#编写一个程序,该程序能从用户指定的范围内获取3个随机不重复的数字
#导入模块
import random
#用户指定范围
start = int(input('请输入您所需要的起始区间:'))
end = int(input('请输入您所需要的结束区间:'))
#使用random,sample(population,k)完成需求,这个语句的意思是从population序列中随机选择k个不重复的元素
result = random.sample(range(start,end + 1),3)
print(f'为您选择的3个不重复的随机数字分别是:{result}')
代码的思路如下:
上一题留下的问题,这题解决了
49 题我做验证码时用的是 random.choice(),它每次只摸一个字符,而且是放回去再摸------同一个数字完全可能被抽中两次。可这题的要求偏偏是"不重复",用 choice 就得自己写"抽完查一下重不重要、重来"的麻烦逻辑。
好在我翻文档时找到了 random 家族的又一位成员:random.sample(序列, k)------从序列里一次抓 k 个元素,保证不重复,还不放回。上一题是一球一摸,这一题是伸手进去一把抓三球,正合需求。
我的设计就三步
第一步:问出范围。 两次 int(input(...)),拿到起始值和结束值。和 49 题一样直接转整数,因为后面要用它们构造序列。
第二步:把"范围"变成"名单"(本题最小心机的地方)。
range(start, end + 1)
random.sample 要求你给它一个可以从中挑东西的序列,可用户给的只是一个区间,所以我用 range() 把区间铺成一串整数当"候选名单"。
关键在那个 + 1:range 是左含右不含的,range(1, 10) 只到 9 为止,10 根本不在这份名单上。用户说"结束区间是 10",肯定是想把 10 也纳入抽奖池,所以我必须手动写成 end + 1,让名单正好盖到用户说的末端。大家注意:这已经是 Python 里第三次在"端点含不含"上给我上公开课了(48 题的 random()、randint(),加这回的 range())。
第三步:一句话完成抽取。 random.sample(名单, 3) 返回一个含 3 个不重复数字的列表,比如 7, 2, 9,用 f-string 直接打印收工。
我发现的隐患:这份"名单"可能不够抽
sample 有个硬脾气:如果名单里的元素不足 3 个(比如用户输了 1 到 2),它会直接抛 ValueError 罢工。再加上输入本身也是裸 int(input()),输个字母照样崩。这题的代码赌的是"用户好好输入"------赌赢了顺畅运行,赌输了当场报错。想稳的话,43 题那套 while True + try/except 验证、再加上"区间跨度至少为 3"的检查,正好能补上这个窟窿。
和前面题目的思路联系
这一小题把 random 家族的三位成员用串了:48 题的 random() 和 randint() 教你生成随机数,49 题的 choice() 教你从池子里摸一个(可重复),今天 sample() 教你从池子里摸多个(不可重复)。选哪个,就看你抽的是"一个还是多个"、"要不要放回"。
总结
我的设计一句话:用 range(start, end+1) 把区间铺成名单,再让 random.sample 一把抓走 3 个不重复的。整道题的难度几乎全压在那个小写的 + 1 上------边界问题不处理好,用户指定的"结束区间"就永远抽不到,这个细节请所有新手一定刻进 DNA。