DEV Community

Jordan Vance
Jordan Vance

Posted on

A zero-dependency way to generate fantasy character names in Python

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'
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
$ dnd-name-generator -r dwarf -g masculine -n 5
Thorin
Durgrim
Balek
Khazdin
Gimnor
Enter fullscreen mode Exit fullscreen mode

Or from code:

from dnd_name_generator import generate, generate_many

generate("Tiefling", "Feminine")   # -> 'Kallieth'
generate_many(3, race="Orc")       # -> ['Grishnak', 'Moguk', 'Rokgor']
Enter fullscreen mode Exit fullscreen mode

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 (0)