为何我的两款近似Python密码生成算法运行速度相差近10倍?
我是编程新手,任务是生成指定数量和长度的密码。第一段代码可以运行,但速度极慢(测试时甚至无法得到结果),修改后的第二段代码运行速度飞快,想知道背后的原因。
第一段代码(运行极慢)
from random import choices, choice def generate_password(m): ch = choices('23456789qwertyupasdfghjkzxcvbnmiLQWERTYUPASDFGHJKZXCVBNM', k=m - 3) ch.append(choice('LQWERTYUPASDFGHJKZXCVBNM')) ch.append(choice('qwertyupasdfghjkzxcvbnmi')) ch.append(choice('23456789')) return ''.join(ch) def main(n, m): p = set() t = 0 while t < n: s = generate_password(m) if s not in p: t += 1 p.add(s) return p
测试情况
使用以下测试代码(参数为生成4609个长度3的密码),始终无法得到结果:
from time import time from random import choices, choice def generate_password(m): ch = choices('23456789qwertyupasdfghjkzxcvbnmiLQWERTYUPASDFGHJKZXCVBNM', k=m - 3) ch.append(choice('LQWERTYUPASDFGHJKZXCVBNM')) ch.append(choice('qwertyupasdfghjkzxcvbnmi')) ch.append(choice('23456789')) return ''.join(ch) def main(n, m): p = set() t = 0 while t < n: s = generate_password(m) if s not in p: t += 1 p.add(s) return p t1 = time() print(*main(4609, 3)) t2 = time() print(t2 - t1)
第二段代码(运行飞快)
from random import choices, shuffle, choice def generate_password(m): global p ch = choices('LQWERTYUPASDFGHJKZXCVBNM', k=m - 3) ch.append(choice('LQWERTYUPASDFGHJKZXCVBNM')) ch.append(choice('qwertyupasdfghjkzxcvbnmi')) ch.append(choice('23456789')) while True: tt = ''.join(ch) if tt in p: shuffle(ch) continue return tt p = set() def main(n, m): global p t = 0 while t < n: p.add(generate_password(m)) t += 1 return p
测试情况
使用以下测试代码,输出耗时仅0.05657219886779785:
from time import time from random import choices, shuffle, choice def generate_password(m): global p ch = choices('LQWERTYUPASDFGHJKZXCVBNM', k=m - 3) ch.append(choice('LQWERTYUPASDFGHJKZXCVBNM')) ch.append(choice('qwertyupasdfghjkzxcvbnmi')) ch.append(choice('23456789')) while True: tt = ''.join(ch) if tt in p: shuffle(ch) continue return tt p = set() def main(n, m): global p t = 0 while t < n: p.add(generate_password(m)) t += 1 return p t1 = time() print(*main(4609, 3)) t2 = time() print(t2 - t1)
原因解析
核心原因不是shuffle()比choice()高效,而是两段代码的密码生成逻辑存在本质差异,尤其是在m=3的测试场景下:
第一段代码的致命问题
当m=3时,m-3=0,choices生成空列表,后续追加的三个字符固定为「大写字母+小写字母+数字」的顺序。这种固定顺序的组合总数为:23个大写 × 23个小写 × 8个数字 = 4232种唯一密码。但你需要生成4609个,已经超过了所有可能的唯一密码数。
这导致第一段代码的while t < n循环会陷入死循环:当所有4232种密码都被加入集合后,后续生成的全是重复值,t再也无法增长,永远达不到n=4609,所以你永远等不到结果。第二段代码的逻辑优势
同样当m=3时,初始生成的字符还是「大写+小写+数字」,但代码会通过shuffle()打乱这三个字符的顺序,直到得到不在集合中的组合。这使得密码的可能组合扩展为这三个字符的所有排列(共3! = 6种),总唯一密码数变为4232 × 6 = 25392种,远大于需求的4609个。
因此程序可以快速生成足够的唯一密码,不会陷入死循环,这才是速度快的核心原因。额外补充
第一段代码的逻辑设计本身就有缺陷:强制固定字符类型的位置,极大限制了唯一密码的总数,当需求数量超过这个总数时必然死循环。第二段代码通过打乱顺序扩展了密码空间,从根本上解决了这个问题。
内容的提问来源于stack exchange,提问作者Father Sergey

