API reference

The 2.0 API

Parsing

nameparser.parse(text: str) ParsedName[source]

Parse a name with the default configuration and return a ParsedName. Equivalent to Parser().parse(text); build your own Parser (or use parser_for()) for custom vocabulary or behavior. Never raises on string content.

class nameparser.Parser(lexicon: Lexicon = None, policy: Policy = None, *, segmenter: Callable[[str], Segmentation | None] | None = None)[source]

A configured name parser: a Lexicon (vocabulary) plus a Policy (behavior), both defaulted when omitted. Build one when you need non-default configuration, build it once, and call parse() many times – it is immutable and thread-safe.

An optional keyword-only segmenter (a Segmenter) plugs in outside knowledge of where an unspaced CJK token divides – Japanese kanji names, which no bundled list can settle. It is consulted only for a token the segmentation stage gates in and the vocabulary DECLINES, so a locale pack’s surnames always win where they match; returning None declines in turn and the token stays whole. Two promises narrow when one is supplied (the first is rules.md#A1’s Accepted clause): parse-totality gains its one exception – an exception raised by the segmenter propagates, because a user-supplied callable’s own error is a user-code error, not a content error – and this Parser pickles only if its segmenter does (a module-level function pickles; a lambda or closure does not). With no segmenter, both promises hold unconditionally: all validity checking happens at construction, so a Parser that constructs successfully cannot fail at parse time on any str content.

(The None field defaults resolve in __post_init__; after construction lexicon and policy are always non-None – the annotations state the steady-state truth, hence the assignment ignores on the defaults.)

segmenter: Callable[[str], Segmentation | None] | None

An optional hook supplying outside knowledge of where an unspaced token divides – see the class docstring; None leaves such tokens whole. Keyword-only, so the reserved growth stays additive: positional construction keeps its two-argument shape.

parse(text: str) ParsedName[source]

Parse one name string into a ParsedName. Never raises on string content (unparseable input yields empty fields plus ambiguities); non-str raises TypeError eagerly, with a decode hint for bytes (bytes support ended with 1.x). The one exception to that totality is a configured segmenter, whose own exceptions propagate (see the class docstring).

revise(name: ParsedName, **fields: str) ParsedName[source]

ParsedName.replace() with this parser’s vocabulary: each value is tokenized and classified by a full sub-parse, so the stable tags survive and the tag-driven views (family_particles, initials(), the suffix join) behave as if the text had been parsed. The value is classified ON ITS OWN, though – a word whose reading depends on surrounding context may classify differently than it would in place (a standalone “B. S.” reads as initials, not a suffix run). The sub-parse’s role choices and ambiguities are discarded – every harvested token takes the named field’s role – and its structural behavior applies: delimiter characters do not become tokens, and a maiden marker is consumed as in parsing – mid-value always, and leading a DELIMITED value under a policy routing that pair to maiden, where “(née Jones)” revises to “Jones” while the bare “née Jones” keeps its marker, a leading marker in an undelimited value being no marker at all (#329). Tokens are synthetic (span=None); original is unchanged; a value with no name content (empty, whitespace, or punctuation only) clears the field; ambiguities referencing replaced tokens are dropped.

matches(a: str | ParsedName, b: str | ParsedName) bool[source]

Component-wise case-insensitive comparison of two names, parsing str arguments with THIS parser. ParsedName.matches() parses its str argument with the DEFAULT parser instead – for names parsed with a custom Parser, use this method.

capitalized(name: ParsedName, *, force: bool = False) ParsedName[source]

ParsedName.capitalized() under THIS parser’s lexicon. The no-argument form of that method uses the DEFAULT lexicon – for names parsed with a custom Parser, use this method.

nameparser.parser_for(*locales: Locale, base: Parser | None = None, segmenter: Callable[[str], Segmentation | None] | None | _Unset = _Unset.UNSET) Parser[source]

Lexicon fragments unioned left-to-right onto base’s; policy patches applied left-to-right (later wins; set-valued fields union per the patch metadata). Validation errors raised while applying a pack are wrapped with that pack’s identity (rule D2) – PolicyPatch validates lazily, so with stacked packs the raw error would otherwise point at nothing. Two packs setting the same SCALAR field is a declared conflict: UserWarning, later wins.

A segmenter is passed straight through to the built Parser – parser_for(locales.JA, segmenter=locales.ja_segmenter()) is how a pack and a segmenter combine, since packs are pure data and cannot supply one. The argument has THREE states, the same UNSET spelling a PolicyPatch field uses, because None is a meaningful value here and not an absence: omitted (UNSET) carries base’s segmenter through unchanged; a callable OVERRIDES base’s (later wins, the rule scalar policy fields follow); and an explicit None CLEARS base’s, which is how you derive an unsegmented parser from a segmented one without rebuilding its lexicon and policy by hand.

class nameparser.Segmentation(splits: tuple[int, ...], confidence: float | None = None)[source]

A segmenter’s answer for one unspaced token: the interior offsets to split at (each offset begins a new piece, so Segmentation((2,)) cuts a three-character token into token[:2] and token[2:]; strictly ascending, each >= 1 – an index protocol, so a segmenter physically cannot invent, drop, or rewrite characters) and an optional confidence in [0, 1]. Segmentation(()) means “confidently one token” – distinct from returning None, which DECLINES (“I don’t know”). The upper bound (< len(token)) is the half this class cannot check, never having seen the text; the consuming stage checks it and RAISES ValueError on a violation, the same call it makes on an answer of the wrong type – both are protocol bugs in the segmenter, not facts about the name.

splits: tuple[int, ...]

Interior character offsets to split at, ascending.

confidence: float | None

How sure the segmenter is, or None for “no opinion”.

nameparser.Segmenter = Callable[[str], Segmentation | None]

The type of the optional Parser(segmenter=...) hook: a callable given one token’s text, returning a Segmentation that divides it, or None to decline and leave it whole. An alias, not a class — any callable of that shape qualifies, and nothing needs to be subclassed or registered. It is consulted only for tokens whose script is listed in Policy.segment_scripts, and only where the surname vocabulary declined first; see Segmenters for what a segmenter owes its caller. ja_segmenter() is the shipped implementation.

Results

class nameparser.ParsedName(original: str, tokens: tuple[Token, ...], ambiguities: tuple[Ambiguity, ...] = ())[source]

The immutable result of parsing one name string. Read the seven fields as strings (.given, .family, …); inspect structure through tokens / tokens_for(); correct a parse with replace() (returns a new value; Parser.revise is the tag-preserving form); produce output with render(), initials(), capitalized(), or str().

Constructor-enforced invariants: spans ascending, non-overlapping, in bounds of original; every Ambiguity’s tokens are a value-equal subset of tokens (see Ambiguity.tokens). Provenance semantics (text == original[span] for parser-produced names) are documented, not enforced – transforms like replace() legitimately break them.

original: str

The input string exactly as passed to parse().

tokens: tuple[Token, ...]

Every classified token, in document order.

ambiguities: tuple[Ambiguity, ...]

Judgment calls that could have gone the other way; empty for most names (see Ambiguity).

tokens_for(role: Role | str) tuple[Token, ...][source]

The tokens of one field, in document order. Takes a Role member or its string value; anything else raises ValueError naming the valid roles.

replace(**fields: str) ParsedName[source]

Return a new ParsedName with the named fields re-tokenized as synthetic tokens (span=None). Whitespace-splits each value; an empty value clears the field. original is unchanged (provenance). Ambiguities referencing replaced tokens are dropped.

Replacement tokens carry NO tags, so tag-driven views degrade: family_particles empties, particles regain their initials, and a multi-word suffix is comma-joined. Parser.revise() is the tag-preserving alternative.

comparison_key() tuple[str, ...][source]

One casefolded component per Role, in canonical order, for dedup, dict keys, and sorting. The semantic layer; __eq__ stays strict.

matches(other: str | ParsedName, *, parser: Parser | None = None) bool[source]

Component-wise case-insensitive comparison (the semantic layer; __eq__ stays strict). A str argument is parsed with parser, or with the DEFAULT parser when None – if this name came from a custom Parser, pass that parser (or use Parser.matches); otherwise the comparison silently runs under the wrong configuration.

render(spec: str = '{title} {given} "{nickname}" {middle} {family} ({maiden}) {suffix}') str[source]

Fill the str.format spec from the seven role fields and the derived views; empty fields collapse (#254), including the default spec’s decorations (empty ‘””’ and ‘()’ wrappers). The default shows every non-empty field: the nickname quoted after the given name, the maiden name parenthesized after the family name. Unknown keys raise KeyError naming the valid fields.

initials(spec: str = '{given} {middle} {family}', delimiter: str = '.', separator: str = ' ') str[source]

Initials per group; v1’s initials_format/_delimiter/_separator become call-site arguments instead of Config-wide settings. Valid spec keys: given, middle, family.

capitalized(lexicon: Lexicon | None = None, *, force: bool = False) ParsedName[source]

Case-fixing transform -> new ParsedName, same spans, new token texts. Needs a lexicon for capitalization_exceptions and particle rules; None uses the DEFAULT lexicon – if this name came from a custom Parser, pass its lexicon or use Parser.capitalized. force=False preserves mixed-case input (v1 parity). Idempotent.

class nameparser.Token(text: str, span: Span | None, role: Role, tags: frozenset[str] = frozenset({}))[source]

One classified word of a parsed name: its text, where it came from, which field it belongs to, and how it was classified. Read tokens off ParsedName.tokens or ParsedName.tokens_for(); you only construct one directly when hand-building a ParsedName.

text: str

The word exactly as written in the input (never empty).

span: Span | None

Position in ParsedName.original; None marks a synthetic token (e.g. introduced by replace()) with no source position.

role: Role

The field this token belongs to.

tags: frozenset[str]

Classification labels. Exactly the four members of STABLE_TAGS (“particle”, “conjunction”, “initial”, “joined”) are API; namespaced tags like “vocab:…” are unstable debugging provenance – never match against them.

nameparser.STABLE_TAGS = frozenset({"particle", "conjunction", "initial", "joined"})

The four Token.tags values that are stable API: particle (a word from the particle vocabulary, “de”/”van”, wherever it lands — combine with Role.FAMILY for actual family particles), conjunction (a joining word, “and”/”y”), initial (an initial-shaped word in a script that HAS initials — “J.” or “А.”, never “씨.”), and joined (a continuation of the previous token within one merged piece, so the suffix view renders “Ph. D.” as one credential). Every other tag is namespaced (vocab:...) and unstable — never match against those.

class nameparser.Span(start: int, end: int)[source]

Where a Token came from: a character range into ParsedName.original such that original[start:end] is the token’s source text (end exclusive). A plain two-int NamedTuple; None in Token.span marks a synthetic token with no source position.

start: int

First character index (0-based).

end: int

One past the last character index.

class nameparser.Role(*values)[source]

The seven fields of a parsed name, one per Token. Declaration order is the canonical field order everywhere (as_dict(), comparison_key(), rendering). A StrEnum, like AmbiguityKind: members ARE their string values, so token.role == "given" compares directly and str(Role.GIVEN) == "given". Members order as strings, so sorted() yields alphabetical order – iterate Role itself for the canonical order.

TITLE = 'title'

Pre-nominal titles and honorifics (“Dr.”, “Sir”, “Capt.”).

GIVEN = 'given'

The given (first) name, or its initial.

MIDDLE = 'middle'

Names between given and family – middle names or initials.

FAMILY = 'family'

The family (last) name, including any particles (“de la Vega”).

SUFFIX = 'suffix'

Post-nominal pieces (“III”, “Jr.”, “PhD”).

NICKNAME = 'nickname'

Delimited nickname content (“Jonathan ‘Jack’ Kennedy” -> “Jack”).

MAIDEN = 'maiden'

A birth surname, from a marker word (“Jane Smith née Jones” -> “Jones”) or a delimiter pair routed via Policy.maiden_delimiters.

class nameparser.Ambiguity(kind: AmbiguityKind, detail: str, tokens: tuple[Token, ...])[source]

A call the parser made that could legitimately have gone the other way, surfaced on ParsedName.ambiguities instead of silently guessed away. The parse still commits to one reading – an Ambiguity is a flag for review, not an error.

kind: AmbiguityKind

Which known ambiguity shape this is (stable API values).

detail: str

Human-readable specifics of this occurrence (wording unstable).

tokens: tuple[Token, ...]

The tokens involved – always a value-equal subset of the owning ParsedName’s tokens (checked with ==, not identity: two distinct Token instances with identical text/span/role/tags satisfy this); may be empty (e.g. unbalanced-delimiter).

class nameparser.AmbiguityKind(*values)[source]

The stable vocabulary of Ambiguity kinds. A StrEnum: members ARE their string values, so kind == "particle-or-given" compares directly. New kinds may be added in minor releases; existing values never change meaning.

A kind names a FORK THE PARSE HAD TO CALL, not a word that could be read two ways: the same token elsewhere in a name may present no choice at all and is then reported by nothing. Reporting is also partial – a kind listed here is not necessarily emitted everywhere its fork occurs (the comma paths stay quiet by design), and coverage grows over releases. A non-empty tuple is a signal to act on; an empty one is not a guarantee of certainty.

ORDER = 'order'

Reserved: the name’s field order itself is uncertain (e.g. a two-word name under a non-default name_order). Not yet emitted; planned for 2.x.

SUFFIX_OR_NICKNAME = 'suffix-or-nickname'

Delimited content is an ambiguous suffix acronym, so it reads plausibly as either a post-nominal or a nickname – “JEFFREY (JD) BRICKEN” keeps the nickname reading, where the unambiguous “(MBA)” escapes to suffix on vocabulary alone.

SUFFIX_OR_NAME = 'suffix-or-name'

A trailing word reads plausibly as either a post-nominal or an ordinary name part. Covers an ambiguous acronym written without periods (“John Smith MA” takes MA as a credential because a family name remains; “Jack MA” keeps it as the name because none would) and a trailing roman numeral, which is a suffix where any other single letter would be a name (“John Smith V” vs “John Smith B”). Which name part was declined depends on position and name_order, so detail names it rather than the kind.

PARTICLE_OR_GIVEN = 'particle-or-given'

An ambiguous particle at the head of a name is either a particle or a name in its own right – “Van Johnson” is the actor’s given name, a bare “Van Buren” the presidential surname, and the two-word shape cannot distinguish them. Two shapes report this kind, decided in different stages, and detail is what tells them apart. A particle left standing alone chained nothing and was assigned a role, which detail names (“read as a given name”) – that role is whatever assignment gave it, so it follows name_order and any script_orders entry, which is why the kind cannot name it. A particle that something ahead of it shifted off the front of the name was instead claimed by the prefix chain, and detail says that and names no field at all: grouping runs before roles exist, so that text is the same under every order. Since #367 a plain title is not such a thing – “Dr. Van Johnson” reads as the untitled “Van Johnson” does and takes the first shape – and what remains is a leading word that is both a title and a particle, so it stays a name piece and the particle behind it is genuinely not leading (“Freiherr von Richthofen”).

UNBALANCED_DELIMITER = 'unbalanced-delimiter'

A nickname/maiden delimiter opened without closing (or closed without opening); the text was kept as literal name content, so the tokens are the one the stray character ended up inside. Two cases leave that tuple empty: a character that lands in no token at all (inside a masked region), and an input with no alphanumeric content anywhere, which parses to an empty name – the report survives because “was this malformed?” is the only question left, but there is no token for it to point at. parse("(") is the second case, not an exotic one.

COMMA_STRUCTURE = 'comma-structure'

More comma-separated segments than any recognized name shape; the parse is best-effort over the extra segments.

SEGMENTATION = 'segmentation'

A division of an unspaced CJK token that the parse had to choose, from either of the two things that can divide one. A VOCABULARY fork: more than one surname-supported split existed (“夏侯惇” was taken as 夏侯 + 惇, while 夏 + 侯惇 also matched), longest-match picked, and detail names both readings (#271). Or a SEGMENTER answer scoring under the stage’s confidence floor: only one reading was offered, but the score says it was a statistical guess rather than a stated certainty, and detail names the pieces and the score (#272). Either way it points at ALL the tokens the division produced – two for a vocabulary split, n+1 for a segmenter answer cutting n times.

Configuration

class nameparser.Lexicon(titles: frozenset[str] = frozenset({}), given_name_titles: frozenset[str] = frozenset({}), suffix_acronyms: frozenset[str] = frozenset({}), suffix_words: frozenset[str] = frozenset({}), suffix_acronyms_ambiguous: frozenset[str] = frozenset({}), particles: frozenset[str] = frozenset({}), particles_ambiguous: frozenset[str] = frozenset({}), conjunctions: frozenset[str] = frozenset({}), bound_given_names: frozenset[str] = frozenset({}), maiden_markers: frozenset[str] = frozenset({}), surnames: frozenset[str] = frozenset({}), honorific_tails: frozenset[str] = frozenset({}), capitalization_exceptions: tuple[tuple[str, str], ...] = ())[source]

The vocabulary a parser matches against: which words are titles, particles, suffixes, and so on. Immutable and hashable. Start from default() (the shipped vocabulary) or empty(), derive variants with add() / remove() / | (union), and pass the result to Parser(lexicon=...). Entries are normalized at construction – lowercased, edge periods stripped – so matching is case-insensitive. Vocabulary entries are single words – a multi-word entry warns at construction and can never match (given_name_titles, matched as a space-joined run, is the one exception). Field docs below show examples, not full contents; inspect any field’s shipped vocabulary directly, e.g. Lexicon.default().conjunctions.

titles: frozenset[str]

Pre-nominal titles (“dr”, “sir”, “capt”, …). Full default list: TITLES.

given_name_titles: frozenset[str]

Titles whose single following name reads as a GIVEN name (“sheikh”, “sister”, …) rather than a family name. Full default list: GIVEN_NAME_TITLES.

suffix_acronyms: frozenset[str]

Post-nominal acronym suffixes, matched with or without periods (“phd” matches “PhD” and “Ph.D.”). Full default list: SUFFIX_ACRONYMS.

suffix_words: frozenset[str]

Post-nominal word suffixes (“jr”, “esquire”, “iii”, …). Full default list: SUFFIX_WORDS.

suffix_acronyms_ambiguous: frozenset[str]

Subset of suffix_acronyms counted as suffixes only when written WITH periods – their bare forms are common surnames (“ma”, “do”: “Jack Ma” keeps his family name). Full default list: SUFFIX_ACRONYMS_AMBIGUOUS.

particles: frozenset[str]

Family-name particles that chain onto the following piece (“van”, “de”, “bin”, …). Full default list: PARTICLES.

particles_ambiguous: frozenset[str]

Subset of particles that can also BE a given name (“Van Johnson”, but also “Van Buren”). Membership decides nothing about chaining: the prefix chain skips the name’s first piece and never consults this set, so it leaves a leading particle a piece of its own whether listed or not – “de Mesnil” groups into two pieces exactly as “van Gogh” does, and since #367 the NAME in “Dr. de Mesnil” and “Dr. Van Johnson” groups into those same two pieces behind the title piece, a title not being part of the name it precedes. What membership decides is what becomes of that piece afterwards. Under ANY name_order a member records a particle-or-given ambiguity and a non-member records none, and a non-member is additionally folded back into the family name once roles exist, so the whole name is the surname (“de Mesnil” – a bare “de”, with nothing to fold into, is left alone). That fold is order-independent too (#359): a word that can never be a given name leaves name_order nothing to decide. Which field a MEMBER’s piece lands in is name_order’s question, not this set’s. No constant of its own – the default derives as particles minus NON_GIVEN_NAME_PARTICLES (which marks the opposite, never-given subset).

conjunctions: frozenset[str]

Words or characters that join surrounding pieces into one (“and”, “&”, “y”, “и”, …). Full default list: CONJUNCTIONS.

bound_given_names: frozenset[str]

Given-name prefixes that bind to the following word to form one given name (“abdul” -> “Abdul Salam”); never standalone names. Full default list: BOUND_GIVEN_NAMES.

maiden_markers: frozenset[str]

Marker words introducing a birth surname, routed to the maiden field (“née”, “geb.”, “roz.”, …). Full default list: MAIDEN_MARKERS.

surnames: frozenset[str]

Family names for the unspaced-name segmentation stage (#271), matched longest-first against the start of the FIRST token written wholly in a script Policy.segment_scripts activates. The default carries the Korean census list (KOREAN_SURNAMES); Chinese surnames ship in locales.ZH because Han segmentation is opt-in.

honorific_tails: frozenset[str]

Honorifics that may be peeled off the END of a name token (#308), matched longest-first: 田中さん splits into 田中 and さん before the tokens are classified. Every entry must also be a suffix_words entry – the peeled tail is claimed by suffix classification like any other post-nominal. Deliberately NOT gated on Policy.segment_scripts (unlike surnames above): 田中さん peels under the default policy, where HAN is in no activation set, because a tail entry carries its own license to fire. Entries are matched against the RAW token text, and only within a name containing a non-ASCII character, so an ASCII or mixed-case entry is at best conditionally active – a "Jr" entry is stored "jr" and matches only lowercase text. The field is effectively CJK-scoped in 2.1, which is what the shipped vocabulary is. Full default list: GLUED_HONORIFICS.

class nameparser.Policy(name_order: tuple[Role, Role, Role] = (Role.GIVEN, Role.MIDDLE, Role.FAMILY), script_orders: tuple[tuple[Script, tuple[Role, Role, Role]], ...] = ((Script.HAN, (Role.FAMILY, Role.GIVEN, Role.MIDDLE)), (Script.HANGUL, (Role.FAMILY, Role.GIVEN, Role.MIDDLE)), (Script.HIRAGANA, (Role.FAMILY, Role.GIVEN, Role.MIDDLE))), segment_scripts: frozenset[Script] = frozenset({Script.HANGUL}), patronymic_rules: frozenset[PatronymicRule] = frozenset({}), middle_as_family: bool = False, nickname_delimiters: frozenset[tuple[str, str]] = frozenset({('"', '"'), ("'", "'"), ('(', ')'), ('«', '»'), ('»', '«'), ('“', '”'), ('”', '”'), ('„', '“'), ('「', '」'), ('『', '』'), ('(', ')')}), maiden_delimiters: frozenset[tuple[str, str]] = frozenset({}), extra_suffix_delimiters: frozenset[str] = frozenset({}), lenient_comma_suffixes: bool = True, strip_emoji: bool = True, strip_bidi: bool = True)[source]

The behavior switches a parser runs with: name order, patronymic rules, delimiter routing, input scrubbing. Immutable and hashable; every field has a safe default, so construct with only what you change – Policy(maiden_delimiters=frozenset({("(", ")")})) – and pass the result to Parser(policy=...).

name_order: tuple[Role, Role, Role]

How positional (no-comma) input maps onto given/middle/family. Valid values are exactly the three exported name-order constants – GIVEN_FIRST (the default), FAMILY_FIRST, and FAMILY_FIRST_GIVEN_LAST; any other tuple of Roles raises ValueError. Ignored when a comma separates family from given: “Thomas, John” puts the family name first no matter which words could otherwise be either (“Thomas” and “John” both work as given or family names). A comma that only sets off suffixes (“John Smith, Jr.”) leaves name_order governing the name part.

script_orders: tuple[tuple[Script, tuple[Role, Role, Role]], ...]

Per-script overrides of name_order (#271), consulted when every name piece is written wholly in one script, or in the Han/Hiragana/Katakana repertoire the #272 kana license shares across pieces: {Script: order} (constructor accepts a mapping; stored as sorted pairs). The default reads wholly-Han/Hangul names, and kana-licensed Japanese names, family-first – see DEFAULT_SCRIPT_ORDERS. Opt out with script_orders=(). Latin-script and mixed-script input is never affected. Like name_order, ignored where a comma already decides the family name.

segment_scripts: frozenset[Script]

Scripts for which the unspaced-name segmentation stage is active (#271): the first token written wholly in an activated script is split by longest surname match against Lexicon.surnames, and, where that vocabulary declines, by a Segmenter if one was given to the parser (#272). Default: {Script.HANGUL} – hangul is unambiguously Korean and Korean surnames are a closed default-shipped set. Han is NOT default: a zh surname list corrupts Japanese names (高橋一郎 must not split as 高+橋一郎), so it’s opt-in via locales.ZH for Chinese and locales.JA – which activates Script.HIRAGANA alongside it, the kana license’s carrier key – for Japanese. Opt out with segment_scripts=frozenset(); note a PolicyPatch unions rather than replaces, so a pack can only add scripts, never disable one.

patronymic_rules: frozenset[PatronymicRule]

Opt-in detectors that reorder patronymic-shaped names (EAST_SLAVIC, TURKIC); usually set via a locale pack.

middle_as_family: bool

Folds middle into family instead of splitting them (v1’s middle_name_as_last) – for data where unrecognized interior words are surname parts, not middle names: multi-part surnames like Spanish/Portuguese dual surnames (“Gabriel García Márquez” -> family “García Márquez” instead of middle “García”).

nickname_delimiters: frozenset[tuple[str, str]]

(open, close) pairs whose enclosed content becomes the nickname field. Defaults to DEFAULT_NICKNAME_DELIMITERS (#273).

maiden_delimiters: frozenset[tuple[str, str]]

(open, close) pairs whose enclosed content becomes the maiden field instead; a pair listed here is dropped from the effective nickname set (maiden wins, see __post_init__), so maiden_delimiters=frozenset({(“(”, “)”)}) is the whole recipe (#274). A maiden_markers word opening the enclosed content is dropped from the value, but only where that content holds more than one token: a lone “(Nee)” is a maiden NAME, not a marker (#329).

extra_suffix_delimiters: frozenset[str]

Additional separators that split suffix groups (e.g. “ - “ for “Jane Smith, RN - CRNA”). Additive only: the comma always splits suffix groups and cannot be replaced – comma handling is structural (the same comma reading that parses “Family, Given” input), not a configurable delimiter.

lenient_comma_suffixes: bool

Governs “Family, Suffix”-shaped input where the suffix word is also initial-shaped (a single letter, bare or period-written – of the default vocabulary that means the roman numerals “I” and “V”): “John Smith, V” reads as John Smith the fifth when True (the default, v1 behavior); False reads “V” as a given-name initial instead (family “John Smith”, given “V”). Multi-letter suffixes (“III”, “MD”) parse the same either way.

strip_emoji: bool

Excludes emoji from tokenization: they appear in no token, field, or rendered view. The original string keeps them (input is never modified – spans stay true).

strip_bidi: bool

Excludes bidirectional control characters from tokenization: they appear in no token, field, or rendered view; the original string keeps them.

patched(patch: PolicyPatch) Policy[source]

Fold a PolicyPatch onto this Policy and return the combined Policy. Set-valued fields union with the patch’s; scalar fields are overridden by the patch; UNSET fields are left alone. Patch VALUES are validated here (Policy’s constructor re-runs on the result), not at patch construction – see PolicyPatch. The maiden-wins canonicalization applies to the combined result exactly as if it had been constructed directly.

class nameparser.PolicyPatch(name_order: tuple[Role, Role, Role] | _Unset = _Unset.UNSET, script_orders: tuple[tuple[Script, tuple[Role, Role, Role]], ...] | _Unset = _Unset.UNSET, segment_scripts: frozenset[Script] | _Unset = _Unset.UNSET, patronymic_rules: frozenset[PatronymicRule] | _Unset = _Unset.UNSET, middle_as_family: bool | _Unset = _Unset.UNSET, nickname_delimiters: frozenset[tuple[str, str]] | _Unset = _Unset.UNSET, maiden_delimiters: frozenset[tuple[str, str]] | _Unset = _Unset.UNSET, extra_suffix_delimiters: frozenset[str] | _Unset = _Unset.UNSET, lenient_comma_suffixes: bool | _Unset = _Unset.UNSET, strip_emoji: bool | _Unset = _Unset.UNSET, strip_bidi: bool | _Unset = _Unset.UNSET)[source]

A partial Policy: one field per Policy field, all defaulting to UNSET. Composition per field is DECLARED via metadata – set-valued fields union, scalars override (later wins). Kept in lockstep with Policy by the parity test in tests/v2/test_policy.py.

Values are validated when the patch is applied (Policy’s constructor re-runs), not at patch construction.

script_orders: tuple[tuple[Script, tuple[Role, Role, Role]], ...] | _Unset

Composes as a SCALAR (override, not merge) – deliberate: nothing shipped patches it today, so the simpler rule is the one to defend; revisit if a pack ever needs to add one script’s entry without restating the rest.

nameparser.UNSET

Sentinel meaning “this patch does not set this field” — the default of every PolicyPatch field, distinguishable from every real value including None and False. You rarely need it: omit a field instead of passing it. Import it to test whether a patch sets a field (patch.name_order is UNSET) or to leave a field conditionally unset when building patches programmatically.

class nameparser.PatronymicRule(*values)[source]

Stable rule names (API); implementations live in the pipeline. Enable via Policy(patronymic_rules={...}) or, more commonly, a locale pack (nameparser.locales).

EAST_SLAVIC = 'east-slavic'

East Slavic formal order: “Sidorov Ivan Petrovich” (family, given, patronymic) is detected by the patronymic ending and reordered. Enabled by locales.RU.

TURKIC = 'turkic'

Turkic patronymic markers: a standalone “oglu”/”qizi”/”kyzy” (etc.) binds to the preceding name as a patronymic. Enabled by locales.TR_AZ.

class nameparser.Script(*values)[source]

Writing systems the parser can key SCRIPT-CONDITIONAL behavior on: per-script name order (Policy.script_orders) and unspaced-name segmentation (Policy.segment_scripts). The rule that admits these (amendment 2026-07-27): script-conditional behavior only where the script itself determines the convention – Latin-script input is never affected. The codepoint table backing these members is internal.

HAN = 'han'

Chinese Hanzi – and Japanese Kanji: a pure-Han string cannot say which language it is, which is fine for ORDER (both write family-first natively) and exactly why Han SEGMENTATION is opt-in, per language: locales.ZH brings the Chinese surname list, locales.JA activates the same stage for a pluggable segmenter to divide kanji names with.

HANGUL = 'hangul'

Korean Hangul (precomposed syllables). Unambiguously Korean.

HIRAGANA = 'hiragana'

Japanese hiragana. Never transcribes foreign names, so a mixed kanji+kana token (高橋みなみ) is Japanese and resolves HERE – this member is the carrier key in script_orders/segment_scripts.

KATAKANA = 'katakana'

Japanese katakana. A PURE-katakana token is predominantly a transcribed foreign name in its original order (マイケル), so no default behavior keys on this member; it exists so the classifier can name what it deliberately declines.

Name-order constants

The three valid values for Policy(name_order=...). name_order is deliberately restricted to these exported constants — an arbitrary tuple of Role members raises ValueError, because only these three orders have defined assignment semantics.

nameparser.GIVEN_FIRST = (Role.GIVEN, Role.MIDDLE, Role.FAMILY)

Western order (the default): the first word of positional input is the given name, the last is the family name, everything between is middle.

nameparser.FAMILY_FIRST = (Role.FAMILY, Role.GIVEN, Role.MIDDLE)

Family name first, given name second, remaining words middle — e.g. Hungarian, or East Asian order.

nameparser.FAMILY_FIRST_GIVEN_LAST = (Role.FAMILY, Role.MIDDLE, Role.GIVEN)

Family name first, given name last, the words between middle — e.g. Vietnamese full-name order.

nameparser.DEFAULT_SCRIPT_ORDERS = ((Script.HAN, FAMILY_FIRST), (Script.HANGUL, FAMILY_FIRST), (Script.HIRAGANA, FAMILY_FIRST))

The default script_orders table: a name written wholly in Han or Hangul reads family-first, and so does a Japanese name mixing kanji with kana, which resolves to the HIRAGANA entry whichever of the two syllabaries it actually uses — that member is the license’s carrier key, not a claim about the characters present. A name written wholly in katakana has no entry on purpose and stays positional. A matching entry in this table takes precedence over name_order, including a name_order you set explicitly — name_order governs only the names no entry matches. The values are drawn from the same three constants above, and the same restriction applies. Build on it for additive customization — script_orders=(*DEFAULT_SCRIPT_ORDERS, (Script.HAN, GIVEN_FIRST)), where a later entry for a script REPLACES an earlier one, so appending is how you override — and pass script_orders=() to opt out entirely and get the purely positional read back. Latin-script and mixed-script names are never affected either way.

Delimiter defaults

nameparser.DEFAULT_NICKNAME_DELIMITERS = frozenset({("'", "'"), ('"', '"'), ("(", ")"), ("“", "”"), ("„", "“"), ("”", "”"), ("«", "»"), ("»", "«"), ("「", "」"), ("『", "』"), ("(", ")")})

The default nickname_delimiters set: straight quotes and parentheses plus the typographic conventions — smart quotes, German/Polish low-high quotes, Swedish right-right quotes, guillemets in both directions, CJK corner brackets, and fullwidth parentheses (#273). Curly single quotes are deliberately absent: U+2019 is the typographic apostrophe (“O’Connor”). Build on the constant for additive customizations, e.g. nickname_delimiters=DEFAULT_NICKNAME_DELIMITERS | {("⦅", "⦆")}; to reroute a pair to maiden, just list it in maiden_delimiters (it is dropped from the effective nickname set automatically).

Locales

class nameparser.Locale(code: str, lexicon: Lexicon, policy: PolicyPatch = PolicyPatch())[source]

A named, shareable bundle of vocabulary and behavior for a naming tradition: a lexicon fragment plus a policy patch, applied together by nameparser.parser_for(). The packs shipped with nameparser live in nameparser.locales; building your own needs no registration – construct one and pass it to parser_for. See the locale packs guide for using, creating, and contributing packs.

code: str

Identifier, lowercase [a-z0-9_]+ (e.g. “ru”, “tr_az”).

lexicon: Lexicon

Vocabulary ADDED to the base parser’s lexicon (unioned; a pack never removes base vocabulary).

policy: PolicyPatch

Behavior changes folded onto the base policy (set-valued fields union; scalars override, later pack wins).

Locale packs: named (lexicon fragment, PolicyPatch) deltas folded in by parser_for (mechanisms.md#LOCALE-PACKS-PURE-DATA). Packs are pure data with no privileged capabilities; they dissolve at parser construction.

Loaded lazily (PEP 562): importing nameparser.locales never imports a pack module; locales.RU triggers nameparser.locales.ru on first access and caches the Locale here.

Layering: this package imports pack modules, which import _locale/ _lexicon/_policy only (enforced by tests/v2/test_layering.py).

nameparser.locales.available() tuple[str, ...][source]

The registered locale codes, lowercase, sorted.

nameparser.locales.get(code: str) Locale[source]

Dynamic lookup by code (‘ru’); raises KeyError listing the available codes.

nameparser.locales.ja_segmenter(*, gbdt: bool = False) Segmenter[source]

The Japanese segmenter factory (#272), needing the optional pip install nameparser[ja]; see nameparser.locales.ja for what it wraps, what it declines, and its licensing.

A real function rather than a _REGISTRY entry, because the registry resolves LOCALES: __getattr__ is annotated to return one, so routing a callable through it would type every pack access as “Locale or Segmenter” for no gain. Laziness is preserved here instead – the pack module (and with it the optional dependency) loads on the call, not on importing this package.

1.x compatibility layer

Note

HumanName and nameparser.config are the 1.x API, kept working through 2.x and removed in 3.0. New code should use the 2.0 API above; see Migrating from HumanName.

HumanName.parser

class nameparser.parser.HumanName[source]
class nameparser.parser.HumanName(full_name: str = '', constants: Constants | None = <Constants : [     prefixes: 70     suffix_acronyms: 613     suffix_not_acronyms: 40     titles: 711     first_name_titles: 35     conjunctions: 14     bound_first_names: 12     non_first_name_prefixes: 33     suffix_acronyms_ambiguous: 4 ]>, string_format: str | None = None, initials_format: str | None = None, initials_delimiter: str | None = None, initials_separator: str | None = None, suffix_delimiter: str | None = None, first: str | list[str] | None = None, middle: str | list[str] | None = None, last: str | list[str] | None = None, title: str | list[str] | None = None, suffix: str | list[str] | None = None, nickname: str | list[str] | None = None, maiden: str | list[str] | None = None)[source]

v1 HumanName facade: a mutable wrapper over a frozen ParsedName, delegating all parsing to the core Parser resolved from the bound Constants shim (dirty-tracked via its _generation). Keeps every v1 spelling (first/last over the core given/family); the v1 parsing hooks are never called (#280). Deleted with the facade layer in 3.0.

__init__(full_name: str = '', constants: Constants | None = <Constants : [     prefixes: 70     suffix_acronyms: 613     suffix_not_acronyms: 40     titles: 711     first_name_titles: 35     conjunctions: 14     bound_first_names: 12     non_first_name_prefixes: 33     suffix_acronyms_ambiguous: 4 ]>, string_format: str | None = None, initials_format: str | None = None, initials_delimiter: str | None = None, initials_separator: str | None = None, suffix_delimiter: str | None = None, first: str | list[str] | None = None, middle: str | list[str] | None = None, last: str | list[str] | None = None, title: str | list[str] | None = None, suffix: str | list[str] | None = None, nickname: str | list[str] | None = None, maiden: str | list[str] | None = None) None[source]
parse_full_name() None[source]

Re-parse the stored full_name (v1’s documented re-parse trigger, docs/customize.rst): mutate name.C then call this to force a re-parse without reassigning full_name. The v1 parsing INTERNALS this name evokes live in the core Parser, not here; a subclass overriding this method still triggers the #280 hook-override warning, and full_name assignment never consults it.

capitalize(force: bool | None = None) None[source]

Re-capitalize the current parse against the bound lexicon. force=None reads the bound Constants’ render default (force_mixed_case_capitalization); the core’s capitalized() implements the single-case gate (v1 parity) – not re-implemented here.

property has_own_config: bool

True when this instance is not using the shared module-level CONSTANTS.

matches(other: str | HumanName) bool[source]

Component-wise case-insensitive comparison (v1 parity); a str argument is parsed with this instance’s resolved parser.

comparison_key() tuple[str, ...][source]

One casefolded component per field in canonical order – the v1 replacement for ==/hash (#223); see ParsedName.comparison_key.

as_dict(include_empty: bool = True) dict[str, str][source]

The seven v1-named components as a dict; include_empty=False drops empty fields.

HumanName.config

v1 import-path preservation (mechanisms.md#CONFIG-SHIM-SNAPSHOT): the Constants shim lives in nameparser._config_shim.

Two unrelated things share this package. The names re-exported below – Constants, CONSTANTS, SetManager, TupleManager, RegexTupleManager – are v1 compatibility surface, and go with the rest of the facade in 3.0. The vocabulary data modules beside them (titles, particles, …) are not compatibility surface at all: they are the word lists the 2.0 Lexicon is built from, they are named for its fields since 2.2 (#293), and its documentation cross-references them.

RegexTupleManager is re-exported unchanged from the shim purely for pickle compatibility: a v1.4 Constants blob’s regexes field was pickled as nameparser.config.RegexTupleManager(...), and unpickling resolves and reconstructs that nested object before Constants.__setstate__ ever runs. Without this name, loading such a blob raises AttributeError looking up the class, not a clean compatibility failure.

class nameparser.config.Constants(*, prefixes: Iterable[str] | object = <object object>, suffix_acronyms: Iterable[str] | object = <object object>, suffix_not_acronyms: Iterable[str] | object = <object object>, suffix_acronyms_ambiguous: Iterable[str] | object = <object object>, titles: Iterable[str] | object = <object object>, first_name_titles: Iterable[str] | object = <object object>, conjunctions: Iterable[str] | object = <object object>, bound_first_names: Iterable[str] | object = <object object>, non_first_name_prefixes: Iterable[str] | object = <object object>, capitalization_exceptions: Mapping[str, str] | ~collections.abc.Iterable[tuple[str, str]] | object=<object object>, regexes: object = <object object>, patronymic_name_order: bool = False, middle_name_as_last: bool = False)[source]

v1 Constants shim: a mutable container whose state resolves to a frozen (Lexicon, Policy, _RenderDefaults) snapshot via _snapshot(). _generation increments on every mutation; facades compare it against a cached value to decide whether their snapshot is stale (dirty-tracking – the facade side lives in nameparser/_facade.py).

The module-level CONSTANTS singleton (below) has _shared flipped to True: any mutation reached through it emits DeprecationWarning pointing at Lexicon/Policy and HumanName(constants=...). A private Constants() never warns – only the shared instance is on the 3.0 removal path.

copy() Constants[source]

Independent copy (#260), subclass-preserving. Divergence from v1 (which deepcopied __dict__): attributes OUTSIDE the known field surface – e.g. ad-hoc names stashed on the instance – are not carried by copy() or pickling; only the enumerated fields survive.

class nameparser.config.RegexTupleManager(*args: object, _on_change: Callable[[], None] | None = None, **kwargs: object)[source]

Pickle-compat alias only: v1.4’s Constants.regexes field was a nameparser.config.RegexTupleManager instance (a TupleManager subclass whose __getattr__ fell back to EMPTY_REGEX for an unknown key). Unpickling a v1.4 blob resolves and constructs this class – via TupleManager.__reduce__’s (type(self), (), state) – before Constants.__setstate__ runs, so the name must exist here even though the shim’s Constants._snapshot() never reads it: regexes is a read-only _RegexesProxy in 2.0 (see above), and Constants.__setstate__ below deliberately ignores an incoming regexes key rather than restoring it from this reconstructed (and otherwise unused) instance.

class nameparser.config.SetManager(elements: Iterable[str] = (), _on_change: Callable[[], None] | None = None, _field: str | None = None)[source]

v1 SetManager surface over a plain set of lc()-normalized strings. Mutations call _on_change (the owning Constants’ generation bump, wired by the facade). __call__ and the missing-member-tolerant remove() are gone per the #243 schedule (warned 1.3.0, removed 2.0): remove() of a missing member raises KeyError, matching set.remove.

add(*strings: str) SetManager[source]

Add the normalized string arguments to the set. Returns self for chaining.

remove(*strings: str) SetManager[source]

Remove the normalized string arguments from the set. Raises KeyError if any argument is not a member. Returns self for chaining.

discard(*strings: str) SetManager[source]

Remove the normalized string arguments from the set if present; missing members are ignored, like set.discard. Returns self for chaining.

clear() SetManager[source]

Remove all entries from the set. Returns self for chaining.

class nameparser.config.TupleManager(*args: object, _on_change: Callable[[], None] | None = None, **kwargs: object)[source]

v1 TupleManager: a dict with dot-notation access. Backs capitalization_exceptions. Unknown-key attribute access raises AttributeError naming the key (#256, warned 1.4, enforced 2.0 – the v1 DeprecationWarning is gone, this shim only speaks 2.0). Mutations call _on_change (the owning Constants’ generation bump, wired by the facade).

pop(k[, d]) v, remove specified key and return the corresponding value.[source]

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem() tuple[str, object][source]

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

clear() None.  Remove all items from D.[source]
update([E, ]**F) None.  Update D from mapping/iterable E and F.[source]

If E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

setdefault(key: str, default: object = None) object[source]

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

HumanName.config Defaults

nameparser.config.titles.GIVEN_NAME_TITLES = frozenset({'aunt', 'auntie', 'brother', 'cheikh', 'dame', 'father', 'king', 'maid', 'master', 'mother', 'pope', 'queen', 'shaik', 'shaikh', 'shayk', 'shaykh', 'sheik', 'sheikh', 'shekh', 'sir', 'sister', 'uncle', 'أستاذ', 'أستاذة', 'الأستاذ', 'الأستاذة', 'الحاج', 'الحاجة', 'الدكتور', 'الدكتورة', 'الشيخ', 'الشيخة', 'دكتور', 'دكتورة', 'مهندس'})

When these titles appear with a single other name, that name is a given name, e.g. “Sir John”, “Sister Mary”, “Queen Elizabeth”.

nameparser.config.titles.TITLES = frozenset({'10th', '1lt', '1sgt', '1st', '1stlt', '1stsgt', '2lt', '2nd', '2ndlt', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', 'a1c', 'ab', 'abbess', 'abbot', 'abolitionist', 'academic', 'acolyte', 'activist', 'actor', 'actress', 'adept', 'adjutant', 'adm', 'admiral', 'advertising', 'adviser', 'advocate', 'air', 'akhoond', 'alderman', 'almoner', 'ambassador', 'amn', 'analytics', 'anarchist', 'animator', 'anthropologist', 'appellate', 'apprentice', 'arbitrator', 'archbishop', 'archdeacon', 'archdruid', 'archduchess', 'archduke', 'archeologist', 'architect', 'arhat', 'army', 'arranger', 'assistant', 'assoc', 'associate', 'asst', 'astronomer', 'attache', 'attaché', 'attorney', 'aunt', 'auntie', 'author', 'award-winning', 'ayatollah', 'baba', 'bailiff', 'ballet', 'bandleader', 'banker', 'banner', 'bard', 'baron', 'baroness', 'barrister', 'baseball', 'bearer', 'behavioral', 'bench', 'bg', 'bgen', 'biblical', 'bibliographer', 'biochemist', 'biographer', 'biologist', 'bishop', 'blessed', 'blogger', 'blues', 'bodhisattva', 'bookseller', 'botanist', 'bp', 'brigadier', 'briggen', 'british', 'broadcaster', 'brother', 'buddha', 'burgess', 'burlesque', 'business', 'businessman', 'businesswoman', 'bwana', 'canon', 'capt', 'captain', 'cardinal', 'cartographer', 'cartoonist', 'catholicos', 'ccmsgt', 'cdr', 'celebrity', 'ceo', 'cfo', 'chair', 'chairs', 'chancellor', 'chaplain', 'charge', 'chargé', 'chef', 'cheikh', 'chemist', 'chief', 'chieftain', 'choreographer', 'civil', 'classical', 'clergyman', 'clerk', 'cmsaf', 'cmsgt', 'co-chair', 'co-chairs', 'co-founder', 'coach', 'col', 'collector', 'colonel', 'comedian', 'comedienne', 'comic', 'commander', 'commander-in-chief', 'commodore', 'composer', 'compositeur', 'comptroller', 'computer', 'comtesse', 'conductor', 'consultant', 'controller', 'corporal', 'corporate', 'correspondent', 'councillor', 'counselor', 'count', 'countess', 'courtier', 'cpl', 'cpo', 'cpt', 'credit', 'criminal', 'criminologist', 'critic', 'csm', 'curator', 'customs', 'cwo-2', 'cwo-3', 'cwo-4', 'cwo-5', 'cwo2', 'cwo3', 'cwo4', 'cwo5', 'cyclist', "d'affaires", 'dame', 'dancer', 'dcn', 'deacon', 'delegate', 'deputy', 'designated', 'designer', 'detective', 'developer', 'dhr', 'dipl.-ing', 'diplomat', 'dir', 'director', 'discovery', 'dissident', 'district', 'division', 'do', 'docent', 'docket', 'doctor', 'doyen', 'dpty', 'dr', 'dra', 'dramatist', 'druid', 'drummer', 'duchesse', 'dutchess', 'ecologist', 'economist', 'editor', 'edler', 'edmi', 'edohen', 'educator', 'effendi', 'ekegbian', 'elerunwon', 'eminence', 'emperor', 'empress', 'engineer', 'english', 'ens', 'entertainer', 'entrepreneur', 'envoy', 'erzbischof', 'essayist', 'evangelist', 'excellency', 'excellent', 'exec', 'executive', 'expert', 'fadm', 'family', 'father', 'federal', 'fh-prof', 'field', 'film', 'financial', 'first', 'flag', 'flying', 'foreign', 'forester', 'founder', 'fr', 'frau', 'freifrau', 'freiherr', 'friar', 'frk', 'fru', 'fräulein', 'frøken', 'fürst', 'fürsterzbischof', 'gaf', 'gen', 'general', 'generalissimo', 'gentiluomo', 'giani', 'goodman', 'goodwife', 'governor', 'graf', 'grand', 'group', 'großfürst', 'gräfin', 'guitarist', 'guru', 'gyani', 'gysgt', 'hajji', 'headman', 'heir', 'heiress', 'her', 'hereditary', 'heren', 'herr', 'herren', 'herrn', 'herzog', 'high', 'highness', 'his', 'historian', 'historicus', 'historien', 'holiness', 'hon', 'honorable', 'honourable', 'host', 'hr', 'illustrator', 'imam', 'industrialist', 'information', 'instructor', 'intelligence', 'intendant', 'inventor', 'investigator', 'investor', 'journalist', 'journeyman', 'jr', 'judge', 'judicial', 'junior', 'jurist', 'keyboardist', 'king', "king's", 'kingdom', 'knowledge', 'lady', 'lama', 'lamido', 'law', 'lawyer', 'lcdr', 'lcpl', 'leader', 'lecturer', 'legal', 'librarian', 'lieutenant', 'linguist', 'literary', 'lord', 'lt', 'ltc', 'ltcol', 'ltg', 'ltgen', 'ltjg', 'lyricist', 'madam', 'madame', 'mademoiselle', 'mag', 'mag-judge', 'mag/judge', 'magistrate', 'magistrate-judge', 'magnate', 'maharajah', 'maharani', 'mahdi', 'maid', 'maj', 'majesty', 'majgen', 'manager', 'marcher', 'marchess', 'marchioness', 'marketing', 'marquess', 'marquis', 'marquise', 'master', 'mathematician', 'mathematics', 'matriarch', 'mayor', 'mcpo', 'mcpoc', 'mcpon', 'md', 'me', 'member', 'memoirist', 'merchant', 'met', 'metropolitan', 'mevr', 'mevrouw', 'mevrouwe', 'mg', 'mgr', 'mgysgt', 'military', 'minister', 'miss', 'misses', 'missionary', 'mister', 'mlle', 'mme', 'mobster', 'model', 'monk', 'monseigneur', 'monsieur', 'monsignor', 'most', 'mother', 'mountaineer', 'mpco-cg', 'mr', 'mrs', 'ms', 'msg', 'msgt', 'mufti', 'mullah', 'municipal', 'murshid', 'musician', 'musicologist', 'mx', 'mystery', 'nanny', 'narrator', 'national', 'naturalist', 'navy', 'neuroscientist', 'novelist', 'nurse', 'obstetritian', 'officer', 'opera', 'operating', 'ornithologist', 'painter', 'paleontologist', 'pastor', 'patriarch', 'pd', 'pediatrician', 'personality', 'petty', 'pfc', 'pharaoh', 'phd', 'philantropist', 'philosopher', 'photographer', 'physician', 'physicist', 'pianist', 'pilot', 'pioneer', 'pir', 'player', 'playwright', 'po1', 'po2', 'po3', 'poet', 'police', 'political', 'politician', 'pope', 'prefect', 'prelate', 'premier', 'pres', 'presbyter', 'president', 'presiding', 'priest', 'priestess', 'primate', 'prime', 'prin', 'prince', 'princess', 'principal', 'printer', 'printmaker', 'prinz', 'prior', 'priv.-doz', 'private', 'pro', 'producer', 'prof', 'professor', 'provost', 'pslc', 'psychiatrist', 'psychologist', 'publisher', 'pursuivant', 'pv2', 'pvt', 'queen', "queen's", 'ra', 'rabbi', 'radio', 'radm', 'rangatira', 'ranger', 'rdml', 'rear', 'rebbe', 'registrar', 'reichsgraf', 'rep', 'representative', 'researcher', 'resident', 'rev', 'revenue', 'reverend', 'right', 'risk', 'ritter', 'rock', 'royal', 'rt', 'sa', 'sailor', 'saint', 'sainte', 'saoshyant', 'satirist', 'scholar', 'schoolmaster', 'scientist', 'scpo', 'screenwriter', 'se', 'secretary', 'security', 'seigneur', 'senator', 'senhor', 'senhora', 'senhorita', 'senior', 'senior-judge', 'sergeant', 'servant', 'señor', 'señora', 'señores', 'señorita', 'señoritas', 'sfc', 'sgm', 'sgt', 'sgtmaj', 'sgtmajmc', 'shaik', 'shaikh', 'shayk', 'shaykh', 'shehu', 'sheik', 'sheikh', 'shekh', 'sheriff', 'siddha', 'signor', 'signora', 'signore', 'signorina', 'singer', 'singer-songwriter', 'sir', 'sister', 'sma', 'smsgt', 'sn', 'soccer', 'social', 'sociologist', 'software', 'soldier', 'solicitor', 'soprano', 'spc', 'speaker', 'special', 'sr', 'sra', 'sres', 'srta', 'srtas', 'ssg', 'ssgt', 'st', 'staff', 'state', 'states', 'strategy', 'subaltern', 'subedar', 'suffragist', 'sultan', 'sultana', 'superior', 'supreme', 'surgeon', 'swami', 'swordbearer', 'sysselmann', 'tax', 'teacher', 'technical', 'technologist', 'television', 'tenor', 'theater', 'theatre', 'theologian', 'theorist', 'timi', 'tirthankar', 'translator', 'travel', 'treasurer', 'tsar', 'tsarina', 'tsgt', 'uk', 'uncle', 'united', 'univ.prof', 'us', 'vadm', 'vardapet', 'vc', 'venerable', 'verderer', 'vicar', 'vice', 'viscount', 'vizier', 'vocalist', 'voice', 'vrouwe', 'warden', 'warrant', 'wing', 'wm', 'wo-1', 'wo1', 'wo2', 'wo3', 'wo4', 'wo5', 'woodman', 'wp', 'writer', 'zoologist', 'δρ', 'κα', 'καθ', 'κος', 'акад', 'г-жа', 'г-н', 'д-р', 'пан', 'пані', 'проф', "גב'", 'גברת', 'גב׳', 'ד"ר', 'ד״ר', 'הרב', 'מר', 'עו"ד', 'עו״ד', "פרופ'", 'פרופסור', 'פרופ׳', 'أستاذ', 'أستاذة', 'الأستاذ', 'الأستاذة', 'الحاج', 'الحاجة', 'الدكتور', 'الدكتورة', 'الشيخ', 'الشيخة', 'دكتور', 'دكتورة', 'مهندس', 'डॉ', 'श्री', 'श्रीमती'})

Cannot include things that could also be given names, e.g. “dean”. Many of these from wikipedia: https://en.wikipedia.org/wiki/Title. The parser recognizes chains of these including conjunctions allowing recognition titles like “Deputy Secretary of State”.

nameparser.config.suffixes.SUFFIX_WORDS = frozenset({'2', 'dr', 'esq', 'esquire', 'i', 'ii', 'iii', 'iv', 'jnr', 'jr', 'junior', 'ret', 'snr', 'sr', 'v', 'vet', 'ז"ל', 'ז״ל', 'שליט"א', 'שליט״א', 'くん', 'さま', 'さん', 'ちゃん', '先生', '博士', '女士', '小姐', '教授', '様', '殿', '氏', '교수님', '군', '님', '박사', '박사님', '선생님', '씨', '양'})

Post-nominal suffixes matched as WORDS: the lookup uses the normalized token, so only EDGE periods come off and interior ones survive – “Junior.” matches here, “J.u.n.o.r.” does not and stays name text (“John J.u.n.o.r.” parses a family name, on both APIs). The example is deliberately not “J.u.n.i.o.r.”, which fails this lookup too and is a suffix anyway: an interior-period token that no whole-token set claims goes to period_joined_vocab, which splits it on its periods and, no chunk being a title, calls the whole thing a suffix if ANY chunk is suffix vocabulary – and the chunk “i” is the Roman numeral listed above. So membership here is not the last word on a dotted token; the sentence is about this set’s lookup alone. SUFFIX_ACRONYMS is the set matched with every period removed, so it alone covers the multi-dot spelling “E.S.Q.” – and, having no interior period to lose, “Esq” as well. ‘esq’ is listed here too (v1 data): inert against the shipped acronym set, since dropping it changes no parse, but what keeps “Esq” matching for a caller who removes it from SUFFIX_ACRONYMS. That is why the two sets are deliberately not asserted disjoint – see the guard block at the bottom.

nameparser.config.suffixes.GLUED_HONORIFICS = frozenset({'くん', 'さま', 'さん', 'ちゃん', '先生', '女士', '小姐', '教授', '様', '교수님', '님', '박사', '박사님', '선생님', '씨'})

The subset of SUFFIX_WORDS a name token may end WITH, peeled off as its own token before segmentation (#308). Deliberately harsher than the spaced set, because a glued tail has no writer-drawn token boundary to lean on – these entries are recognized in the SPACED position only:

  • 양, 군 – 김지양 and 김지군 are given names ending in these syllables, and 양 is a top-tier surname besides.

  • 氏 – 王氏 is a historical name form (“the Wang woman”).

  • 博士 – glued 田中博士 IS Tanaka Hiroshi, an attested given name.

  • 殿 – some ninety Japanese surnames END in it, 鵜殿 (Udono) and 真殿 (Madono) with four-figure populations, so peeling it would cut a real family name in two. Spaced 殿 is safe for the reason 양/군 are: a 殿-surnamed person’s name LEADS, and the suffix gate is trailing-only.

Three more are in NEITHER set, so neither spelling is recognized. 君: 王君 is a complete Chinese name (君 is a common given-name final), so the honorific reading never gets the benefit of the doubt – while its kana spelling くん ships glued, above. Bare 선생 and 교수: they read as common nouns as readily as address terms, and only their -님 forms ship.

nameparser.config.suffixes.SUFFIX_ACRONYMS_AMBIGUOUS = frozenset({'do', 'ed', 'jd', 'ma'})

Acronym suffixes from SUFFIX_ACRONYMS that also plausibly collide with a common given-name nickname. Not a partition of SUFFIX_ACRONYMS – a small, standalone exception list, read by the delimited-content escape in _pipeline/_extract.py and by _pipeline/_vocab.py’s period gate.

nameparser.config.suffixes.SUFFIX_ACRONYMS = frozenset({'8-vsb', 'aas', 'aba', 'abc', 'abd', 'abpp', 'abr', 'aca', 'acas', 'ace', 'acha', 'acp', 'ae', 'aem', 'afasma', 'afc', 'afm', 'agsf', 'aia', 'aicp', 'ala', 'alc', 'alp', 'am', 'amd', 'ame', 'amieee', 'ams', 'aphr', 'apn', 'apr', 'aprn', 'apss', 'aqp', 'arm', 'arrc', 'asa', 'asc', 'asid', 'asla', 'asp', 'atc', 'awb', 'ba', 'bca', 'bcl', 'bcss', 'bds', 'bem', 'bls-i', 'bn', 'bpe', 'bpi', 'bpt', 'bsc', 'bt', 'btcs', 'bts', 'cacts', 'cae', 'caha', 'caia', 'cams', 'cap', 'capa', 'capm', 'capp', 'caps', 'caro', 'cas', 'casp', 'cb', 'cbe', 'cbm', 'cbne', 'cbnt', 'cbp', 'cbrte', 'cbs', 'cbsp', 'cbt', 'cbte', 'cbv', 'cca', 'ccc', 'ccca', 'cccm', 'cce', 'cchp', 'ccie', 'ccim', 'cciso', 'ccm', 'ccmt', 'ccna', 'ccnp', 'ccp', 'ccp-c', 'ccpr', 'ccs', 'ccufc', 'cd', 'cdal', 'cdfm', 'cdmp', 'cds', 'cdt', 'cea', 'ceas', 'cebs', 'ceds', 'ceh', 'cela', 'cem', 'cep', 'cera', 'cet', 'cfa', 'cfc', 'cfcc', 'cfce', 'cfcm', 'cfe', 'cfeds', 'cfi', 'cfm', 'cfp', 'cfps', 'cfr', 'cfre', 'cga', 'cgap', 'cgb', 'cgc', 'cgfm', 'cgfo', 'cgm', 'cgma', 'cgp', 'cgr', 'cgsp', 'ch', 'cha', 'chba', 'chdm', 'che', 'ches', 'chfc', 'chi', 'chmc', 'chmm', 'chp', 'chpa', 'chpe', 'chpln', 'chpse', 'chrm', 'chsc', 'chse', 'chse-a', 'chsos', 'chss', 'cht', 'cia', 'cic', 'cie', 'cig', 'cip', 'cipm', 'cips', 'ciro', 'cisa', 'cism', 'cissp', 'cla', 'clsd', 'cltd', 'clu', 'cm', 'cma', 'cmas', 'cmc', 'cmfo', 'cmg', 'cmp', 'cms', 'cmsp', 'cmt', 'cna', 'cnm', 'cnp', 'cp', 'cp-c', 'cpa', 'cpacc', 'cpbe', 'cpcm', 'cpcu', 'cpe', 'cpfa', 'cpfo', 'cpg', 'cph', 'cpht', 'cpim', 'cpl', 'cplp', 'cpm', 'cpo', 'cpp', 'cppm', 'cprc', 'cpre', 'cprp', 'cpsc', 'cpsi', 'cpss', 'cpt', 'cpwa', 'crde', 'crisc', 'crma', 'crme', 'crna', 'cro', 'crp', 'crt', 'crtt', 'csa', 'csbe', 'csc', 'cscp', 'cscu', 'csep', 'csi', 'csm', 'csp', 'cspo', 'csre', 'csrte', 'csslp', 'cssm', 'cst', 'cste', 'ctbs', 'ctfa', 'cto', 'ctp', 'cts', 'cua', 'cusp', 'cva', 'cva[22]', 'cvo', 'cvp', 'cvrs', 'cwap', 'cwb', 'cwdp', 'cwep', 'cwna', 'cwne', 'cwp', 'cwsp', 'cxa', 'cyds', 'cysa', 'dabfm', 'dabvlm', 'dacvim', 'dbe', 'dc', 'dcb', 'dcm', 'dcmg', 'dcvo', 'dd', 'dds', 'ded', 'dep', 'dfc', 'dfm', 'diplac', 'diplom', 'djur', 'dma', 'dmd', 'dmin', 'dnp', 'do', 'dpm', 'dpt', 'drb', 'drmp', 'drph', 'dsc', 'dsm', 'dso', 'dss', 'dtr', 'dvep', 'dvm', 'ea', 'ed', 'edd', 'ei', 'eit', 'els', 'emd', 'emt-b', 'emt-i/85', 'emt-i/99', 'emt-p', 'enp', 'erd', 'esq', 'evp', 'faafp', 'faan', 'faap', 'fac-c', 'facc', 'facd', 'facem', 'facep', 'facha', 'facofp', 'facog', 'facp', 'facph', 'facs', 'faia', 'faicp', 'fala', 'fashp', 'fasid', 'fasla', 'fasma', 'faspen', 'fca', 'fcas', 'fcela', 'fd', 'fec', 'fhames', 'fic', 'ficf', 'fieee', 'fmp', 'fmva', 'fnss', 'fp&a', 'fp-c', 'fpc', 'frm', 'fsa', 'fsdp', 'fws', 'gaee[14]', 'gba', 'gbe', 'gc', 'gcb', 'gchs', 'gcie', 'gcmg', 'gcsi', 'gcvo', 'gisp', 'git', 'gm', 'gmb', 'gmr', 'gphr', 'gri', 'grp', 'gsmieee', 'hccp', 'hrs', 'iaccp', 'iaee', 'iccm-d', 'iccm-f', 'idsm', 'ifgict', 'iom', 'ipep', 'ipm', 'iso', 'issp-csp', 'issp-sa', 'itil', 'jd', 'jp', 'kbe', 'kcb', 'kchs/dchs', 'kcie', 'kcmg', 'kcsi', 'kcvo', 'kg', 'khs/dhs', 'kp', 'kt', 'lac', 'lcmt', 'lcpc', 'lcsw', 'lg', 'litk', 'litl', 'litp', 'llm', 'lm', 'lmsw', 'lmt', 'lp', 'lpa', 'lpc', 'lpn', 'lpss', 'lsi', 'lsit', 'lt', 'lvn', 'lvo', 'lvt', 'ma', 'maaa', 'mai', 'mba', 'mbe', 'mbs', 'mc', 'mcct', 'mcdba', 'mches', 'mcm', 'mcp', 'mcpd', 'mcsa', 'mcsd', 'mcse', 'mct', 'md', 'mda', 'mdb', 'mdbb', 'mdep', 'mdhb', 'mdiv', 'mdl', 'mem', 'meng', 'mfa', 'micp', 'mieee', 'mirm', 'mle', 'mls', 'mlse', 'mlt', 'mm', 'mmad', 'mmas', 'mnaa', 'mnae', 'mp', 'mpa', 'mph', 'mpse', 'mra', 'ms', 'msa', 'msc', 'mscmsm', 'msm', 'mt', 'mts', 'mvo', 'nbc-his', 'nbcch', 'nbcch-ps', 'nbcdch', 'nbcdch-ps', 'nbcfch', 'nbcfch-ps', 'nbct', 'ncarb', 'nccp', 'ncidq', 'ncps', 'ncso', 'ncto', 'nd', 'ndtr', 'nmd', 'np', 'np[18]', 'nraemt', 'nremr', 'nremt', 'nrp', 'obe', 'obi', 'oca', 'ocm', 'ocp', 'od', 'om', 'oscp', 'ot', 'pa-c', 'pcc', 'pci', 'pe', 'pfmp', 'pg', 'pgmp', 'ph', 'pharmd', 'phc', 'phd', 'phr', 'phrca', 'pla', 'pls', 'pmc', 'pmi-acp', 'pmp', 'pp', 'pps', 'prm', 'psm', 'psp', 'psyd', 'pt', 'pta', 'qam', 'qc', 'qcsw', 'qfsm', 'qgm', 'qpm', 'qsd', 'qsp', 'ra', 'rai', 'rba', 'rci', 'rcp', 'rd', 'rdcs', 'rdh', 'rdms', 'rdn', 'res', 'rfp', 'rhca', 'rid', 'rls', 'rmsks', 'rn', 'rp', 'rpa', 'rph', 'rpl', 'rrc', 'rrt', 'rrt-accs', 'rrt-nps', 'rrt-sds', 'rtrp', 'rvm', 'rvt', 'sa', 'same', 'sasm', 'sccp', 'scmp', 'se', 'secb', 'sfp', 'sgm', 'shrm-cp', 'shrm-scp', 'si', 'siie', 'smieee', 'sphr', 'sra', 'sscp', 'stb', 'stmieee', 'tbr-ct', 'td', 'thd', 'thm', 'ud', 'usa', 'usaf', 'usar', 'uscg', 'usmc', 'usn', 'usnr', 'uxc', 'uxmc', 'vc', 'vcp', 'vd', 'vrd'})

Post-nominal acronyms. Titles, degrees and other things people stick after their name that may or may not have periods between the letters. The parser removes periods when matching against these pieces.

nameparser.config.particles.NON_GIVEN_NAME_PARTICLES = frozenset({"'t", 'af', 'auf', 'av', 'bint', 'das', 'de', "de'", 'degli', 'dei', 'delle', 'delli', 'dello', 'dem', 'der', 'dos', 'het', 'ibn', 'las', 'los', 'mc', 'op', 'ste', 'ter', 'vd', 'vom', 'zu', 'בן', 'בת', 'آل', 'ابن', 'بن', 'بنت'})

The sub-set of PARTICLES that are never a standalone given name. Where one of these stands ALONE as the piece opening a name, that name has no given name – the whole thing is a surname (e.g. “de Mesnil” -> family name “de Mesnil”) – and that reading holds under EVERY name_order (#359). It is not scoped to the default order the way the rest of the positional read is: name_order says which side of the name the family sits on, and a word that can never be a given name leaves it nothing to decide, so Policy(name_order=FAMILY_FIRST) reads “de Mesnil” as the family name too. What is asked about is the opening piece, not the first word of the string: a particle that has already chained onto the word behind it is part of that piece rather than standing alone. Opening the name is only the commonest shape. The rule enforcing it (rules.md#P1; the pre-2.2 docstrings called it rule 1b) reaches a member standing alone as a piece in the given position too, folding it into the family beside it, so that neither shape leaves a given name behind – as long as there is another name token to fold into. A bare “de” stays as it is. Where a chain is reported as the given name anyway it is because the member is no longer standing alone: under Policy(name_order=FAMILY_FIRST) the given position of “Juan de la Vega” holds the whole three-token chain, so the rule declines and given “de la Vega” stands. A title in front is NOT such a case – since #367 a title is transparent to the leading-particle exception, so “Sir de Mesnil” leaves “de” a lone piece and reads family “de Mesnil”, exactly as the untitled form does. Membership also decides the ambiguity report – see PARTICLES below. Curated to exclude anything that can be a given name in some culture (al, van, von, della, di, del, da, vander, …) and anything that is also a bound given-name particle (abu). When unsure, leave a word out: a missing member just means that name is not auto-fixed, whereas a wrong member misparses a real person. Must stay a subset of PARTICLES and disjoint from BOUND_GIVEN_NAMES.

nameparser.config.bound_given_names.BOUND_GIVEN_NAMES: frozenset[str] = frozenset({'abd', 'abdal', 'abdel', 'abdul', 'abou', 'abu', 'umm', 'أبو', 'أم', 'ابو', 'ام', 'عبد'})

Bound Arabic given-name prefixes that attach to the following word to form one given name (e.g. “abdul salam smith” → given name “abdul salam”). They are never standalone names. The join is a group-stage rule on the FIRST non-title piece, so it is not about roles – it fires whatever name_order later assigns. It reserves a piece for what follows: three pieces that are neither title nor suffix in a main segment – counting the bound word’s OWN piece even where that word is also suffix vocabulary, since it is the piece the rule has claimed rather than one left to spare – which is why two-word “abdul salam” stays given “abdul” plus family “salam”; only two after a family comma, where the family name is already fixed (“salam, abdul rahman” → given “abdul rahman”). Mirrors PARTICLES, which chains onto the piece that follows it.

nameparser.config.conjunctions.CONJUNCTIONS = frozenset({'&', 'and', 'e', 'et', 'of', 'the', 'und', 'y', 'και', 'и', 'й', 'та', 'і', 'و'})

Pieces that should join to their neighboring pieces, e.g. “and”, “y” and “&”. “of” and “the” are also include to facilitate joining multiple titles, e.g. “President of the United States”.

nameparser.config.maiden_markers.MAIDEN_MARKERS = frozenset({'född', 'fødd', 'født', 'geb', 'geboren', 'geborene', 'nee', 'né', 'née', 'roz', 'rozená', 'урожд', 'урожденная', 'урожденный', 'урождённая', 'урождённый', '旧姓'})

Marker words that introduce a birth surname, e.g. “Jane Smith née Jones” (#274). French née/né/nee, German geb./geborene, Dutch geboren, Czech/Slovak roz./rozená, Danish/Norwegian født (Nynorsk fødd), Swedish född, Russian урожд./урождённая/урождённый (both ё and е spellings — case normalization does not fold them, and running text routinely writes е). Both grammatical genders are listed where #274 or review attested them (née/né, урождённая/урождённый); Czech masculine rozený awaits the same vetting. Entries are stored normalized: lowercase, no periods.

Japanese 旧姓 is here rather than in locales.JA, on the rule that admitted the Cyrillic entries: a native-script marker cannot collide with a Latin-script name, and matching is whole-token, so it is safe as a default. Neither character appears in any shipped surname, title, suffix, conjunction, particle or bound-given vocabulary. locales.JA is for what needs the my-data-is-Japanese declaration – segmentation, where a pure-Han string cannot say which language wrote it – and this needs none, since it can only ever match Han text.

Matching being whole-token, the marker has to BE a token – which for Japanese means something has to divide it from the name it marks. A space does, and so does a configured delimiter: extract masks the whole bracketed region, delimiter characters included, before tokenize runs, so a bracket bounds a token exactly as a space does and “山田(旧姓 佐藤)” needs no space in front of 旧姓 at all. The bare “山田花子 旧姓 佐藤” and – since #329 – the bracketed “山田 花子(旧姓 佐藤)” under Policy(maiden_delimiters=…) alike give maiden 佐藤. What divides nothing is the fullwidth colon that the form Japanese more often writes puts after the marker: “山田(旧姓:佐藤)” still yields maiden “旧姓:佐藤” with the marker and its colon attached. Not because delimited content escapes classification – classify tags a marker wherever it is a token – but because : is no separator tokenize knows, so marker and name arrive as ONE token and there is nothing to drop. The wholly unspaced “山田花子(旧姓佐藤)” reads as one token for the same reason. Peeling a marker off the head of a token is #317’s job.

Consumed by the 2.0 parser’s default lexicon. The 1.x parser does not read this module.

Deliberately absent: Polish “z domu” (a two-token marker; pending the 2.0 pipeline’s multi-token matching decision) and the Scandinavian abbreviation “f.” (collides with the initial “F.” — only the full participles are safe).

nameparser.config.surnames.KOREAN_SURNAMES = frozenset({'가', '강', '경', '계', '고', '공', '곽', '구', '국', '권', '금', '기', '길', '김', '나', '남', '남궁', '노', '도', '독고', '동방', '두', '류', '마', '망절', '맹', '명', '모', '목', '문', '민', '박', '반', '방', '배', '백', '변', '복', '봉', '부', '사', '사공', '서', '서문', '석', '선', '선우', '설', '성', '소', '손', '송', '신', '심', '안', '양', '엄', '여', '연', '염', '예', '오', '옥', '왕', '용', '우', '원', '위', '유', '육', '윤', '은', '이', '인', '임', '장', '전', '정', '제', '제갈', '조', '주', '지', '진', '차', '채', '천', '최', '추', '탁', '태', '편', '표', '피', '하', '한', '함', '허', '현', '형', '홍', '황', '황보'})

Korean surnames (#271), used by the 2.0 API’s unspaced-name segmentation (Lexicon.default().surnames): a hangul token like “김민준” splits into surname + given name by longest match. This ships as DEFAULT vocabulary because it is self-selecting – a hangul entry can only ever match hangul text – and hangul text is unambiguously Korean. Chinese surnames deliberately live in nameparser.locales.zh instead (Han segmentation is opt-in; a zh list corrupts Japanese kanji names).

Source: the 2015 South Korean census surname tables – the most common single-syllable surnames (Kim/Lee/Park alone cover ~45% of the population) plus the two-syllable surnames in current use. A coverage floor, not the complete census roster: extend with Lexicon.default().add(surnames={...}).

Consumed by the 2.0 parser’s default lexicon. The 1.x parser does not read this module.

nameparser.config.capitalization.CAPITALIZATION_EXCEPTIONS = {'ii': 'II', 'iii': 'III', 'iv': 'IV', 'md': 'M.D.', 'phd': 'Ph.D.'}

Any pieces that are not capitalized by capitalizing the first letter.

nameparser.config.regexes.REGEXES = {'bidi': re.compile('[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]+'), 'commas': re.compile('[,،,]'), 'double_quotes': re.compile('\\"(.*?)\\"'), 'east_slavic_patronymic': re.compile('(ovich|ovna|evich|evna|ichna|ilyich|kuzmich|lukich|fomich|fokich)$', re.IGNORECASE), 'east_slavic_patronymic_cyrillic': re.compile('(ович|овна|евич|евна|ична|ильич|кузьмич|лукич|фомич|фокич)$', re.IGNORECASE), 'emoji': re.compile('[🌀-🙏🚀-\U0001f6ff☀-⛿✀-➿]+'), 'initial': re.compile('^(\\w\\.|[A-Z])?$'), 'mac': re.compile('^(ma?c)(\\w{2,})', re.IGNORECASE), 'parenthesis': re.compile('\\((.*?)\\)'), 'period_abbreviation': re.compile('^[^\\W\\d_]{2,}\\.$'), 'period_not_at_end': re.compile('.*\\..+$', re.IGNORECASE), 'phd': re.compile('\\s(ph\\.?\\s+d\\.?)', re.IGNORECASE), 'quoted_word': re.compile("(?<!\\w)\\'([^\\s]*?)\\'(?!\\w)"), 'roman_numeral': re.compile('^(X|IX|IV|V?I{0,3})$', re.IGNORECASE), 'space_before_comma': re.compile('\\s+,'), 'spaces': re.compile('\\s+'), 'turkic_patronymic_marker': re.compile("^(oglu|oğlu|ogly|ogli|o['’ʻ]g['’ʻ]li|qizi|qızı|kizi|kyzy|gyzy|uly|uulu)$", re.IGNORECASE), 'turkic_patronymic_marker_cyrillic': re.compile('^(оглу|оглы|оғлу|ўғли|угли|кызы|гызы|қызы|қизи|улы|ұлы|уулу)$', re.IGNORECASE), 'word': re.compile('(\\w|\\.)+')}

All regular expressions used by the parser are precompiled and stored in the config.