Every test suite I write ends up needing throwaway names. Faker is great for "John Smith" and "Acme Inc", but the moment I'm seeding a game's NPC table or a fantasy-app fixture, real-world names look wrong next to a dwarf cleric. So I went down the rabbit hole of procedural name generation. You can get believable fantasy names with nothing but the standard library.
Here's the whole idea in a few lines.
The trick: stitch syllables, don't store names
A lookup table of pre-written names runs dry fast and feels repetitive. Instead you keep small pools of sound fragments per race and assemble a name from one start + optional middle + ending:
import random
ELF_START = ["Ae", "Fae", "Lael", "Cael", "Syl"]
ELF_MID = ["", "la", "ri", "va", "thy"]
ELF_END = ["riel", "wyn", "thas", "lor", "ndil"]
def elf_name(rnd=random.random):
pick = lambda pool: pool[int(rnd() * len(pool))]
return (pick(ELF_START) + pick(ELF_MID) + pick(ELF_END)).capitalize()
print(elf_name()) # -> 'Faewyn'
Five fragments per slot already gives you 5 x 5 x 5 = 125 combinations that read like elf names, because the constraint lives in the fragments, not in a giant list. Swap the pools and an orc reads like an orc:
ORC_START = ["Gr", "Mog", "Rok", "Dur", "Kra"]
# harsh consonant clusters, short endings -> 'Grukgor', 'Rokmash'
A couple of design notes that matter once you actually use this:
- Keep
generate()pure. Pass the RNG in as a callable (rnd=random.Random(7).random) so you can seed it. Reproducible names mean your test fixtures don't churn the diff every run. - Capitalize at the end, not per-fragment, or you get
FaeWyn. - An empty string in the middle pool is a cheap way to vary name length without a separate branch.
If you just want names, not a project
I packaged the full version as a pip install: seven races (human, elf, dwarf, orc, halfling, tiefling, dragonborn), masculine/feminine/any, still zero dependencies.
pip install dnd-name-generator
$ dnd-name-generator -r dwarf -g masculine -n 5
Thorin
Durgrim
Balek
Khazdin
Gimnor
Or from code:
from dnd_name_generator import generate, generate_many
generate("Tiefling", "Feminine") # -> 'Kallieth'
generate_many(3, race="Orc") # -> ['Grishnak', 'Moguk', 'Rokgor']
It's MIT and generate() takes the same rnd callable, so you can seed it for tests. Source and docs are on PyPI: https://pypi.org/project/dnd-name-generator/
Next time a fixture needs a half-orc barbarian instead of another "Test User 3", you've got options that don't pull in a single transitive dependency.
Top comments (2)
I love Korean, as a language. It inspired something similar, in which you define consonant-like and vowel-like sets of letters than can then be assembled into specific syllables you can string together. Surprisingly simple and surprisingly natural random words, including names...
Some comments may only be visible to logged-in visitors. Sign in to view all comments.