ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python随机生成书名:从random到命令行工具

用Python随机生成书名:从random到命令行工具 1. 从给东西起名这件烦人事说起我写代码这么多年被问到最多的问题从来不是这个bug怎么修而是你这个开源项目叫什么名字你公众号叫什么你给这本书起个什么标题。你可能以为只有写小说、做自媒体的人才会有这种烦恼其实程序员也有只不过我们把逃避起名的过程包装成了另一种形式写一个随机生成名字的小工具。某天晚上我又被一个书名卡住了脑子里翻来覆去都是深夜月亮孤独这种词实在憋不出来干脆打开编辑器写了一个generate_book_title函数。结构很简单用import random引入随机库用def定义一个函数函数里从几个词库里随机取样再拼起来。跑了一下出来一个沉默的河流。那一下我意识到随机生成不是一个偷懒的替代方案而是一种能打破思维僵局的有效手段。你盯着白纸想不出名字是因为大脑在已有的语义网络里反复走同一条路径而随机组合直接跨过那条路径把两个本来没有关联的词撞在一起比如午夜灯塔会呼吸的地图。哪怕十次里只有一两次靠谱那一两次也足够你继续往下写了。这篇文章就从那个小小的generate_book_title函数出发把import random、def、随机取样、词库设计、命令行封装、踩坑排查这些事情一次讲透。适合刚开始学 Python 的初学者也适合那些能写代码但想把手头脚本做得更工整的进阶读者。2. 核心代码逐行拆解import random、def、return 在干什么2.1 import random 到底导入了什么很多刚接触 Python 的人第一次看到import random都是一脸懵——这一行到底干了什么简单说import语句做的事情是在当前命名空间里挂载一个模块对象这个模块对象里封装了一堆可用的函数和常量。random模块就是 Python 标准库自带的伪随机数工具包包含random.random()、random.randint()、random.choice()、random.sample()等常用方法。这里要澄清一个容易混淆的概念random模块生成的是伪随机数不是真正意义上的随机数。它内部使用的是一个确定性算法默认是梅森旋转算法只需要一个初始种子值之后每次调用都会按照固定的递推公式产生一个看起来没有规律、实际上完全可以复现的序列。这个特性在实际开发里特别重要。调试的时候你希望每次运行结果一样方便定位问题上线或者给别人演示的时候你又希望每次都不一样。靠什么控制靠random.seed()。比如random.seed(42)之后再跑代码不管执行多少次生成的序列都一样。我后面会专门讲这个这里先记住一个结论用random不是碰运气它是一套可复现的、可控制熵的机制。2.2 def generate_book_title()为什么要把随机逻辑包进函数初学者容易犯的一个毛病是把所有代码平铺在模块顶层从上到下写一遍出结果了事。但一旦你有了多个词库、多种生成模式、甚至要批量生成几百个标题平铺写法的劣势就非常明显变量散落到处都是、没法重复调用、改一处要牵连好几处。def generate_book_title():这一行的核心价值是封装。你把生成一个书名这件事做成一个动作调用方只需要关心两件事传什么参数进去、拿到什么返回值。其他的过程细节全部藏在函数体内部。函数定义可以拆成几个要素函数名generate_book_title见名知意比写abc()强一百倍。参数列表括号里可以空着也可以接收词库、数量、种子等外部输入。函数体缩进的代码块是真正干活的地方。返回值return把结果交还给调用方。如果没有return函数会隐式返回None很多初学者的 bug 就是从这里来的——明明调用了函数打印出来却是None。另外Python 里函数的return是立即结束函数的标志。函数体里return后面的代码不会执行。所以如果你的函数里有多个return要注意它们的分支关系是否真的覆盖了所有可能路径否则就会出现走到某个分支没有任何返回值的情况。2.3 函数体的核心random.choice 与模板拼接我们看一个基本实现import random ADJECTIVES [寂静, 燃烧, 沉默, 潮湿, 透明, 野性] NOUNS [河流, 灯塔, 花园, 午后, 迷宫, 时钟] VERBS [坠落, 归来, 生长, 埋葬, 喧哗, 静止] def generate_book_title(): pattern random.choice([adj_noun, verb_noun, noun_and_noun]) if pattern adj_noun: return random.choice(ADJECTIVES) random.choice(NOUNS) elif pattern verb_noun: return random.choice(VERBS) random.choice(NOUNS) else: return random.choice(NOUNS) 与 random.choice(NOUNS) print(generate_book_title())这里面用到了两个随机方法random.choice(seq)从一个非空序列中等概率地随机选取一个元素是这里最核心的方法。random.sample(seq, k)则是无放回地取 k 个元素比如random.sample(NOUNS, 2)会一次性取出两个不重复的名词适合生成河流与迷宫这种结构时避免撞词。模板拼接的逻辑也很重要。我设计了三种模式形容词加名词、动词加名词、名词与名词。这个设计参考了图书标题里常见的形式比如说《寂静的春天》就是形容词加名词《追风筝的人》可以理解为动词加名词《百年孤独》里孤独也可以当作抽象名词。你不需要真的去归纳所有文学套路但至少要保证词性匹配否则会出现坠落花园这种动宾结构语义混乱的情况。值得注意的是random.choice([adj_noun, verb_noun, noun_and_noun])这一层。如果直接写死一种模板生成出来的书名变化太少跑十次可能十次都是同一个句式看着很呆。加一个模式选择层可以让输出形态更多样。真实的随机生成工具往往都是先随机选结构再随机填词这个思路。3. 让随机组合出来的书名像话词库与模板设计3.1 词库分类原料质量决定了成品下限很多第一次写随机生成器的人没跑几次就会产生一个很沮丧的疑问为什么我生成出来的名字一股子人工智障味问题通常出在词库上而不是随机函数上。词库是随机生成系统的原材料。如果你往形容词列表里塞了好吃红色很大这种口语化、描述性太泛的词拼出来的书名必然是灾难级别的。反过来如果你塞进去的是荒诞克制凛冽澄明这类有氛围感、有情绪张力的词哪怕组合不那么合逻辑也大概率能读出一点味道来。我一般会把词库分成这样几类形容词库描述氛围、情绪、质感。潮湿凛冽寂静灼热幽暗。具象名词库有画面感的具体事物。灯塔钟楼渡口麦田火车。抽象名词库情绪、概念、时间。孤独晚风回声昨日边界。动词库有动作感、有叙事的词。坠落逃亡抵达埋葬生长。对仗连接词与或之上之下不曾。词库质量的核心指标不是词的数量而是词的可组合性。所谓可组合性就是这个词和其他词拼在一起时能不能触发新的想象空间。举个例子灯塔是一个高可组合性的词因为它自带时间、海上、方向、孤独、希望这些意象。公司就是一个低可组合性的词它太具体、太干瘪。搭建词库的时候穷尽一个领域显然不现实但你至少可以把常用词过一遍把那些联想到太多具体画面但没有余韵的词删掉。3.2 模板不只是形容词加名词这一种上文的adj_noun、verb_noun、noun_and_noun只是一层最基本的模板。真正常见的书名结构其实非常丰富我整理了几个值得做的方向模板结构示例风格代码拼法形容词 名词《寂静的春天》adj 的 noun动词 名词《追风筝的人》verb 的 noun名词 连接词 名词《月亮与六便士》noun 与 noun名词 介词短语《挪威的森林》noun 的 place_noun动词 地点《回到未来》verb 到 place数字 时间名词《1984》《百年孤独》num time_word你以为模板越多越好不是。模板数量超过六个以后你会发现自己陷入一种选择瘫痪每次生成的书名结构变化太大反而失去了统一的语言风格。对于书名生成器来说保持两三种主力模板、两种辅助模板就足够了。风格一致性比花样多少更重要读者拿到一批生成结果应该有这像是同一类型的书的感觉而不是像十本书的名录。我在后续迭代中给函数增加了词库参数允许调用方按需传入不同的形容词库、名词库这样同一个生成函数可以在不同风格的场景下复用。比如给儿童书生成标题用一套明亮词库给悬疑小说生成标题用另一套阴郁词库。3.3 嵌入权重、排除逻辑和种子参数让工具真正可用仅仅依靠random.choice等概率随机会让某些组合长期占据输出结果。比如一个词库里孤独特别多跑二十次有六次都是孤独的XX——这种局面就需要权重机制。为了不把代码写复杂我建议用一个非常朴素但有效的方法在词库中重复收录权重较高的词。比如希望黄昏出现概率是普通词的三倍就往列表里放三个黄昏再用random.choice去取。这个方法对工具级别的应用完全够用不用引入numpy或自定义加权函数简单粗暴且好理解。排除逻辑也很重要。有些组合在美学上是可以的在现实里却很尴尬。比如燃烧的下午尚可接受燃烧的钟表就有点诡异如果词库里还有牛仔裤那就可能出现燃烧的牛仔裤。针对这种情况我建议配置文件里维护一个blocked_pairs列表生成后做一次配对检查命中就重新生成。注意要设置一个最大重试次数比如 10 次避免极端情况下死循环。种子参数属于那种不加的时候没什么感觉加了以后回不去了的设计。函数签名里带上seedNone调用时如果传入了整数内部执行一次random.seed(seed)这样每次运行结果一致测试用例就好写了演示的时候也不用担心上次生成的好名字这次跑不出来了。4. 从函数到工具批量生成、命令行调用、结果导出4.1 用 argparse 给脚本加命令行入口写到这里generate_book_title还只是一个函数只能在代码里调用。但你想真正把它用起来最直接的方式就是让它在命令行里执行python generate_title.py --count 20一口气给你 20 个书名。argparse是 Python 标准库里的命令行参数解析工具不需要额外安装。用起来也很简单import argparse import random def generate_book_title(word_pools, seedNone): if seed is not None: random.seed(seed) pattern random.choice([adj_noun, verb_noun, noun_and_noun]) adjectives, nouns, verbs word_pools if pattern adj_noun: return random.choice(adjectives) 的 random.choice(nouns) elif pattern verb_noun: return random.choice(verbs) 的 random.choice(nouns) else: return random.choice(nouns) 与 random.choice(nouns) def main(): parser argparse.ArgumentParser(description随机生成书名的小工具) parser.add_argument(--count, typeint, default10, help生成数量) parser.add_argument(--seed, typeint, defaultNone, help随机种子) args parser.parse_args() word_pools ( [寂静, 燃烧, 沉默, 潮湿, 透明, 野性, 凛冽, 荒诞], [河流, 灯塔, 花园, 午后, 迷宫, 时钟, 渡口, 麦田], [坠落, 归来, 生长, 埋葬, 喧哗, 静止, 抵达, 虚构], ) for _ in range(args.count): print(generate_book_title(word_pools, seedargs.seed)) if __name__ __main__: main()这里有个 Python 新手经常忽略的知识点if __name__ __main__:这行。它的作用是判断当前文件是被直接执行的还是被其他模块导入的。如果直接执行__name__变量的值会是__main__此时运行main()如果是被导入__name__会是模块名此时不执行main()只暴露函数定义。这个写法可以让同一个文件既作为脚本执行又作为可导入模块复用非常关键。4.2 批量产出一批书名并写入文件命令行打印到终端读起来还行但你要给朋友看、或者要做一个书名灵感池最好把结果输出到文件里。最省事的方案是写入纯文本文件一条一行with open(titles.txt, w, encodingutf-8) as f: for _ in range(args.count): f.write(generate_book_title(word_pools, seedargs.seed) \n)如果希望以后在 Excel 里编辑、筛选、打标签也可以考虑写.csv格式。需要注意csv模块在处理中文时涉及编码问题写文件时要指定encodingutf-8-sig这样用 Excel 打开时才不会出现中文乱码。utf-8-sig和utf-8的区别就在于它会自动写入一个 BOM 头这是 Windows 环境兼容性的老坑你只要记住这个小知识点就够用了。批量生成的时候还有一个设计决策要不要每生成一个标题就把结果追加写入文件我的建议是不要边生成边写文件而是先存内存列表全部生成完再一次写入。这样万一在生成过程中报错比如词库里有空列表导致random.choice抛异常也不会在输出文件里留下半截数据事后排查起来也方便。4.3 更进一步表格导出和简单统计到命令行加文件导出这个阶段工具已经能用了。但我个人习惯再向前走一小步统计一下生成的这批标题中各个词库的使用频率。这听起来好像很过度设计实际非常有用。因为随机生成器有一个隐蔽的问题某个词可能因为索引位置、随机种子、模板偏好的关系长期不被抽中导致词库覆盖面不均匀。你可以简单用collections.Counter统计每个词出现的次数和理论概率对比一下偏差太大的说明你的生成逻辑有 bug比如某个模板分支永远走不到。另外如果你真的想接 Excel 工作流还可以用 pandas 把结果做成一张带序号、标题、模板类型的表格保存成.xlsx。pandas 在热词里频繁出现说明很多人都在用它处理日常数据。对于书名生成器这个场景pandas 有点杀鸡用牛刀但如果你本来就有处理 Excel 的既有脚本把标题追加到同一张表里也不算麻烦。5. 回看一次真实运行随机生成过程里踩过的三个小坑5.1 随机种子复现调试时最容易被忽略的细节有一次我帮朋友改一个类似的生成工具他抱怨生成出来的结果越来越怪以前好看的组合好像再也出不来了。我上去一看他的代码在循环里每次都调用random.seed(10086)等于每个书名都是同一个随机序列的第一个值看起来当然又重复又怪。排查这类问题有一个标准手法先把seed固定住跑一遍抓到一个异常结果然后把seed参数暴露在命令行里重新运行确认同样的问题可以复现。能复现的问题就好修不能复现的问题才是真正难缠的。我在自己的工具里对 seed 的处理原则是函数内部不写死任何random.seed()只有调用方显式传入 seed 时才设置。这样既能满足日常随机需求也能满足调试时的确定性需求。如果你正在写的是一个长期维护的小工具一定要把随机性可控当作基本要求而不是一个花活。5.2 词库冷启动一开始攒的几百个词根本不够用第一次搭词库时我信心满满写了大概一百多个词觉得总量不少了。真跑起来后才发现一百多个词撑死也就几千种组合而且人的审美疲劳来得非常快。跑一百条输出之后你会觉得大部分组合都是换汤不换药。词库是需要持续喂养的。我平时看书、看文章、刷到好的标题会顺手往手机备忘录里记一个词定期汇总进词库文件。大概攒了半年后我那个主词库突破了八百个词这个时候随机生成的质量才真正开始有可用性。整个过程没有任何捷径就是靠积累。另外一个让我很意外的规律是具象名词的质量和效果往往比形容词更影响整个生成结果的质感。一个不太抽象、画面感强的名词比如灯塔渡口废墟比五个形容词堆在一起更能撑起一个书名。所以如果你时间有限优先扩展名词库得到的回报通常最大。5.3 组装陷阱随机组合不等于随机语感最后一个坑比较微妙但非常值得单独说。随机生成器最容易出现的问题不是某个词不好而是词与词之间的语感失衡。比如你从形容词库里抽到澄明从名词库里抽到下水道生成的澄明的下水道——单独看每个词都在及格线上拼起来却像是在玩冷幽默。原因在于不同词带有不同的语域、格调和隐含语境。有些词适合抒情叙事有些词天然带有日常烟火气强行拼在一起就会产生巨大的语体撕裂感。解决这个问题的方法有三个第一给词库打标签。按氛围分组比如自然意象城市意象时间意象情绪意象让函数只在同一个大组里组合而不是全库乱抽。第二增加一个简单的语感校验词表。把常见不搭的组合提前记录在blocked_pairs中。这个表可以在使用过程中持续积累和词库一样靠迭代。第三也是最重要的永远不要指望随机生成直接产出完美结果。它的定位应该是灵感放大器而不是成品生成器。我实际的工作流是每次批量跑 50 个标题从中挑出两三个有潜力的再手动调整用词最终加工成真正的候选书名。在线运行稳定之后我又给这个小工具加了一个非常轻量的功能把生成结果按可用待补废弃三个标签分组保存。每天跑一次顺手把能用的标题囤进可用清单。到现在那个清单里攒了上百个上佳的候选书名朋友圈里已经有好几个人靠我这个小工具解决了项目命名和文章起题的问题。说到底generate_book_title这个函数能带给你的方法论价值比它生成的具体书名大得多随机性是探索工具词库是审美积累封装度决定工具寿命。按这个思路写下去你以后遇到任何需要起名字的场景——起项目代号、起代码变量名、给视频起标题——都可以套用这套逻辑。
返回列表