API reference

Classes and functions

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/A1’s Accepted bullet “The one exception to totality”): 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.

property languages: frozenset[str]

The languages this parser’s vocabulary is chosen under, Lexicon.languages read through: set by parser_for(), empty where none was chosen. Added in 3.0.

property strict: bool

Whether the choice is strict, Lexicon.strict read through. Added in 3.0.

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, and since #407 capitalized()) 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, and a glued CJK honorific the whole name kept on an initial may peel in the value. 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). A suffix value’s ENTRY structure is derived from the value’s own commas after the role is forced, by the rule a whole name uses: a comma parts two credentials and a space joins them, so revise(n, suffix="MD PhD") is one entry and a name’s rendered suffix revises back to itself wherever the value’s words read as the whole name read them – the honorific peel above was the one corpus exception of 368 suffix-bearing names, 2026-09-06 (#511). A second came with #601/#602: a suffix holding a maiden marker the whole name left a word, whose value read on its own takes the clause and drops the marker (‘Jane Doe PhD nee Smith’ renders ‘PhD nee Smith’, which revises to ‘PhD Smith’), three corpus names on 2026-10-04. A delimiter the policy names through extra_suffix_delimiters parts a value only where the value’s own words, read as a name, give it a tail segment for the core to be dropped on; a run of post-nominals has none, with or without a comma of its own ("MD PhD - FACS" and "MD, PhD - FACS" both keep the dash as a word), so write a comma at the boundary you want rather than the delimiter. 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.

matching(accept: Iterable[Outcome | str] | None = None, *, other_readings: bool = False) → Matching[source]

A Matching bound to THIS parser, so its check and keys parse str arguments with this parser’s lexicon and policy. A ParsedName argument is compared as given, whatever parser produced it. accept is the iterable of outcomes counted as consistent; None takes the default set, and any other value replaces it, so extend the default with parser.matching(accept=parser.matching().accept | {"family-initial"}). other_readings=True also searches each side’s other readings (Matching.other_readings). A value is validated by Matching, which raises for a bad one.

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, strict: bool | _Unset = _Unset.UNSET) → Parser[source]

The packs’ languages and base’s recorded choice unioned into one choice, recorded on the built parser’s lexicon (Parser.languages), so the order of the packs, and whether they were chained through base, does not change it: parser_for(EN, base=parser_for(PT)) reads as parser_for(PT, EN). The packs’ lexicon fragments are joined left-to-right and base’s lexicon after them, so a caller’s own readings outrank every pack’s and a later pack’s an earlier one’s; the choice filters the shipped readings and every lexicon’s add and remove apply after it, so a choice never drops a caller’s word. A caller’s reading that cannot stand beside one the choice turns on raises ValueError here, naming the call (decisions/vocabulary-model, 2026-11-02 #754, items 3 and 4). Policy patches apply left-to-right (later wins; set-valued fields union per the patch metadata). A pack that chooses no language leaves the base vocabulary exactly as it is.

strict=True makes the choice strict: only the chosen languages’ vocabulary files are compiled, each tag selecting its own and its ancestors’ as a plain choice does, plus mul.toml, the words every language writes alike (Dr, Prof, PhD, MSc, the numerals), and a native-script word’s readings (one with a letter and no Latin letter), name readings included, as a plain choice of the same languages keeps them; every other file’s readings leave, und.toml’s among them, and with them the markers they derive. It is for single-origin data (parser_for(Locale(code="ko", lexicon=Lexicon.empty(), languages=frozenset({"ko"})), strict=True) reads Kim Do Hyun with no Portuguese particle do). A caller’s own readings and the packs’ fragments apply after it, as after a plain choice, and the policy patches unchanged. strict has three states, as segmenter does: omitted (UNSET) keeps base’s recorded strictness, False on a strict base raises ValueError, a strict choice not being undone by a later call, and True on a plain one makes the whole choice strict. A strict choice accepts a language whose file holds no reading yet (hu), which a plain one refuses as changing nothing, and raises ValueError when the packs choose no language, since it would then keep no language’s own words, only mul.toml’s and native-script words’ readings (decisions/vocabulary-model, 2026-10-30 #753, file-strict, and 2026-10-31 #753, phase 3j).

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 to different values is a declared conflict: UserWarning, later wins; two setting it to the same value agree, and nothing is warned.

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, ...] = (), order: tuple[Role, Role, Role] | None = None)[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); read the views derived from the fields (given_names, family_base, generation, credentials, honorifics, patronymic, surname1, surname2, …); 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).

order: tuple[Role, Role, Role] | None

The order the name was read in: GIVEN_FIRST, FAMILY_FIRST or FAMILY_FIRST_GIVEN_LAST, the declared name_order or the script_orders entry that overrode it, and GIVEN_FIRST for a name with a family comma. str() and render()’s default lay the name out in it. None (a name built by hand, or a 2.x pickle) renders as 2.x did: given-first, its words joined by spaces. Left out of == and comparison_key(), so two equal names can render differently. Added in 3.0.

property title: str

The words read as titles (“Dr.”, “Sir”), wherever in the name they stand, space-joined in written order, save words written with nothing between them, which stay so; '' for a name with none.

property given: str

The given name’s words, space-joined in written order, save words written with nothing between them, which stay so; '' for a name with none (“de la Vega”).

property middle: str

The middle names’ words, space-joined in written order, save words written with nothing between them, which stay so; '' for a name with none.

property family: str

The family name’s words, particles included (“de la Vega”), space-joined in written order, save words written with nothing between them, which stay so (“王李” in “王李小明” under locales.ZH), and the middle names middle_as_family folds in, which come first; '' for a name with none (“Williams” is a given name alone).

property suffix: str

The words after the name (“Jr.”, “PhD”, “さん”): each entry the writer set apart joined to the next with ", ", the words of one entry with a space (“MD PhD, Jr.”); '' for a name with none. generation, credentials and honorifics divide it by kind.

property nickname: str

The words written inside nickname delimiters (quotes, parentheses; Policy.nickname_delimiters), space-joined in written order, the delimiters dropped; '' for a name with none.

property maiden: str

The maiden name’s words, those a birth marker (“née”, “geb.”) introduces or a pair in Policy.maiden_delimiters encloses, space-joined in written order, the marker dropped; '' for a name with none.

property family_particles: str

The particles of family (“de la” in “Juan de la Vega”), space-joined as family writes them; '' for a family with none. A family that replace() or Parser.revise wrote carries no particle tags, so its words are all in family_base.

property family_base: str

family without its particles (“Vega” in “Juan de la Vega”), space-joined as family writes them, so the two views together hold the family’s words; '' for a name with no family.

property surnames: str

The middle names and the family name together (“Q. Xavier de la Vega” in “Juan Q. Xavier de la Vega”), space-joined in written order, save words written with nothing between them, which stay so; '' for a name with neither (“Williams”).

property generation: str

The suffix words read as a generation or an agnomen (“Jr.”, “III”, “Filho”), joined as suffix joins them; '' for a name with none. Added in 3.0.

property credentials: str

The rest of the suffix: credentials and the post-nominal words that are neither a generation nor an honorific (“PhD”, “Esq.”, “Rinpoche”), joined as suffix joins them; '' for a name with none. A suffix word with no Token.reading (one replace() or Parser.revise wrote) is counted here. Added in 3.0.

property honorifics: str

The suffix words read as a form of address written after the name (“さん”, “씨”, “先生”), joined as suffix joins them; '' for a name with none. Added in 3.0.

property given_names: str

The given name and the middle names together (“Juan Q. Xavier” in “Juan Q. Xavier de la Vega”), space-joined in written order, save words written with nothing between them, which stay so; '' for a name with neither (“de la Vega”).

property patronymic: str

The words read as a patronymic or matronymic (“bin Mohamad”, and “Ivanovich” where a policy turns on the East Slavic rule), in whichever field the parse put them, which keeps them; '' for a name with none. Space-joined in written order, the family’s words in the order family writes them. Added in 3.0.

property surname2: str

The family’s second name unit where the parse read the family as two (Márquez in Gabriel García Márquez under two_family_names, 李 in 王李小明 under locales.ZH), which family keeps; '' for a family read as one, a family comma’s (“Lloyd Webber, Andrew”) and a hyphenated one included, for a name unpickled from 2.x, and once replace() or Parser.revise rewrites family, whose words then carry no reading; rewriting another field keeps it. Added in 3.0.

property surname1: str

The family’s first name unit where the parse read the family as two (García in Gabriel García Márquez under two_family_names, de la Vega in Juan de la Vega del Valle under it too), surname2 being the other; elsewhere the whole family. A middle name middle_as_family folds into the family is in neither (María García in Ana María García López under middle_as_family, whose surname1 is López). Added in 3.0.

property surname1_particles: str

surname1’s particles, as family_particles reads the whole family’s (de la in Juan de la Vega del Valle under two_family_names), so the two units’ particles are the family’s. Added in 3.0.

property surname1_base: str

surname1 without its particles, as family_base reads the whole family (Pérez in Juan Pérez de la Vega under two_family_names, where family_base is Pérez Vega). Added in 3.0.

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.

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

The seven fields as a new dict keyed by each Role’s value ("title", "given", …), in Role order, each value the string its property gives, '' included; include_empty=False leaves the empty fields out. The views derived from the fields (family_base, generation, …) are not in it.

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 STABLE tag, 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. They do carry UNCLASSIFIED_TAG, which says the text was never read rather than that it was read and found plain: a view holding a vocabulary falls back to it for these and to the tags for everything else.

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 | None = None) → str[source]

Fill the str.format spec from the seven role fields and the derived views; empty fields collapse (#254), including the spec’s decorations (empty ‘””’ and ‘()’ wrappers). A spec renders exactly as written, its order and its joiners. Unknown keys raise KeyError naming the valid fields.

None, the default and what str() passes, lays the name out in order: given-first '{title} {given} "{nickname}" {middle} {family} ({maiden}) {suffix}', and for a family-first order the family (with its maiden name) after the title and the given and middle names in the order’s sequence, the suffix last. Two words that stand one directly after the other both in the name as written and in that layout keep the separator written between them, normalized: nothing stays nothing, any whitespace becomes one space, an interpunct, nakaguro or Ethiopic wordspace the parse divided at stands alone, and one comma is written ‘, ‘ (ASCII), ‘، ‘ (Arabic), ‘፣ ‘ (Ethiopic), ‘၊ ‘ (Burmese) or ‘,’ (full-width). Every other pair takes a space, or the layout’s quotes and parentheses where those stand between the two fields, and suffixes not written side by side are joined with ‘, ‘ as the suffix field joins them.

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

The initials of the given, middle and family names, laid out by spec, a format string whose keys are the groups: each initial is followed by delimiter, and the initials of one group are joined by separator (so initials(spec="{given}{middle}{family}", separator="") runs them together). Each group is ordered the way its own field is ordered, so a name whose middle words fold into the family initials them before the family’s own words, as family renders them. 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 a name whose words outside the suffix are written in more than one case, and in a name it does repair keeps a suffix written in more than one case as written (‘EdD’), repairing only one written in a single case (rules/R5). Idempotent.

class nameparser.Token(text: str, span: Span | None, role: Role, tags: frozenset[str] = frozenset({}), reading: Reading | None = None)[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.

reading: Reading | None

The reading the parse took for this word: what it was read as, the field it landed in (always this token’s role), and why – a listed word, a declared name, a shape, or position alone – and the shipped files whose reading of it is on in the parse’s lexicon (Reading.languages). The twin of Ambiguity.alternative, which holds the reading NOT taken. None for a token the parse did not read: one ParsedName.replace() or Parser.revise() builds, or one loaded from a pickle written before 3.0. Not compared: two tokens equal without it, as before 3.0, and ParsedName.comparison_key() ignores it. Added in 3.0.

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 (a word READ as an initial — initial-shaped in a script that HAS initials, “J.” or “А.”, never “씨.”, a marked single-letter connective in a name written in one case, rules/P8, or any other bare letter in a name written in one case where its capital would be one, “j” as “J”, rules/T4), and joined (a continuation of the token before it — within one merged piece the tag is role-blind and every view joins the pair with a space, so it renders “Ph. D.” as one piece in whatever role it lands — Ph. D., Jr. gives family 'Ph. D.' — and since #436 it also spans the pieces of one SUFFIX entry, the run of post-nominals the writer wrote without a comma — “MD PhD” — which is the half the suffix view alone reads and joins with a space instead of “, “). 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, ...], alternative: Reading | None = None)[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).

alternative: Reading | None

The reading the parse did not take: what the tokens would have been read as, and the field they would have landed in, had the call gone the other way (the first token’s field, for a report on more than one). None where the kind reports no fork between two readings of a word (comma-structure, unbalanced-delimiter, segmentation; order is reserved), and where the parse cannot place the other reading in any field: a connective letter opening its part (“E JOHN SMITH”), with no word before it to join, or a chained particle whose piece ends in a title a connective’s join made (“Freiherr von Berg Dr. and Ed. Prof.”). Not compared: two ambiguities equal without it, as before 2.7, and ParsedName.comparison_key() ignores it.

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, except where a member says otherwise: the same token elsewhere in a name may present no choice at all and is then reported by nothing. COMMA_STRUCTURE and UNBALANCED_DELIMITER are the two that say otherwise – each reports a shape the parse could not recognize rather than a fork it chose between, and each says so on its own member below. Reporting is also partial – a kind listed here is not necessarily emitted everywhere its fork occurs (the comma’s structure decision reports no reading by design, except where it decides a member of the ambiguous credential class, where since 2.4 that decision is reported either way – #289; an attachment decided AFTER a family comma is a separate fork and has reported on its own since 2.3, e.g. “Berg, Jan vd”; the two structural kinds above were never covered by either silence), 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. A lone name word is GIVEN_OR_FAMILY’s, not this.

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 “(PhD)” escapes to suffix on vocabulary alone, and “Andrew Perkins (JD)” on its capitals, which settle it.

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”, written wholly in one case, keeps it as the name because none would, while in a mixed-case name the capitals decide outright – “Jack MA” is the credential and “Jack Ma” the name, rules/S4 and rules/S6) 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”). It also covers a family-comma listing, where a trailing abbreviation that is both a post-nominal and a surname particle joins the surname the comma already named: “Berg, Jan vd” takes vd for van der and declines the decoration. Which name part was declined depends on position and name_order, so detail names it rather than the kind. Since 2.7 a listed member its capitals settle in a mixed-case name is not reported at any slot below, as one written with its periods is not (“John Smith MA”, “Doe, John DO”, “John Smith, MA” are silent, rules/S6, #785), so the examples below that report are written in one case, in lower case or in Title case. The same doubt covers an input that is nothing BUT post-nominal vocabulary (“Rinpoche”, “QC MP”): with no name word beside it the first post-nominal is read as the name, because something has to be one, and only that word reports. WHERE it is emitted is narrower than where the doubt exists, and this is the boundary rather than an omission to be read past. The emitters cover the trailing slot of a name, the FIRST PIECE after a family comma – that piece and no further – the trailing slot of that listing’s GIVEN part, and the trailing slot of a maiden marker’s clause. A part beyond the second comma is not among them, so “Smith, John, MA” is silent; what such a part does report is rules/C2’s to say. Since 2.4 the given part’s trailing slot reports whichever way it read the word: “DOE, JOHN MA” reads suffix MA and says so, “Doe, John Ma” keeps middle Ma and says so too. Not in EVERY direction, though: where the member is also a particle and the particle rule keeps it, that rule reports at its own site and this kind stays out of the way – “Doe, John do” gives family do Doe and one PARTICLE_OR_GIVEN, never two reports of one word. Since #544 C4’s name-word count flips, reported once (C11), over a whole RUN after the comma (“John Smith, Ed Ma” reads suffix Ed Ma), and a member an unambiguous credential in front of it speaks for (rules/S7) is a pick like a counted one, reported where it was decided: by the peel, by the given part’s trailing slot, or by the reading of a part after a family comma that the company leaves holding no name word (“John Smith PhD MEng”, “Doe, Jane PhD MEng” and “Smith, PhD MEng” each report MEng). A member its own capitals already made the credential reports nothing, so “Smith, PhD MA” and “John Smith, PhD MA” read suffix PhD MA in silence. Since 2.4 a maiden marker’s clause reports at ITS trailing slot too, in both directions: “Jane Doe nee Smith ma” gives maiden Smith with suffix ma and says so, “Jane Doe nee Smith Ma” keeps maiden Smith Ma and says so too, and a member the clause keeps because it is the only word after the marker (“Jane Doe nee MA”) reports as well. Where no trailing rule reads the clause’s tail, nothing was decided and nothing reports: a clause in the FAMILY segment of a comma listing (“Smith nee Jones MA, Jane” keeps maiden Jones MA) is silent. After any other comma a marker is an ordinary word and makes no clause at all (rules/M2). FOUR positions stay silent, and all four are boundaries rather than omissions. The NO-READER clause just named is the first of them, and the three that follow are about the member’s own surroundings. Second: a member with something BEHIND it that the trailing reading does not take was never a fork – “Doe, John ma Smith” reads middle ma Smith, the ordinary reading, and nothing consulted the class, and inside a clause the same holds of a name word (“Jane Doe nee MA Smith” keeps maiden MA Smith), a bare trailing title among them (“Jane Doe nee Smith ma Prof” keeps maiden Smith ma Prof, where the period-marked “Jane Doe nee Smith ma Prof.” reads title Prof. and suffix ma and reports). A member its capitals settle is no such member: it starts the credential’s run (rules/S8), so “Doe, John MA Smith” reads suffix MA Smith. Third – pre-existing, and untouched by 2.4 – a member with no name word IN FRONT of it is not at this slot either, because the slot is the end of a given part and there is none: a TITLE or a POST-NOMINAL took that position – either one leaves the segment with no name word for the slot to be the end of. “Doe, Dr. MA” gives suffix MA and “Doe, Mr. MA PhD” suffix MA PhD, the credential-run gate reading those segments whole; “Doe, Dr. Ma” reaches the walk instead and makes Ma the given name itself, which the walk starts above. The first-piece emitter does not cover for it, reading the piece that stands immediately after the comma and nothing behind that piece: “Doe, MA Smith” reports its MA, and “Doe, Dr. MA Smith” – the same member, one title in front of it – reads given MA in silence. A maiden marker cannot reach this position: after a family comma it is an ordinary word (rules/M2), so “Doe, Dr. nee Smith ma” reads given nee, middle Smith and suffix ma – and reports, the ma ending a given part like any other. And fourth – pre-existing and untouched by 2.4 as well – a JOIN beside the member can take it out of this slot, from either side. Where a chain has swallowed the member into one piece there is no lone member to ask about, and the member may as well HEAD that piece as trail it: “Doe, John van Ma” reads middle van Ma in silence, and so does “Doe, John DO Ed”, where the member is itself the particle the chain runs on and the name word behind it joins the piece – though “Doe, John DO” alone reads the credential, its capitals settling it. The particle chain is the commonest joiner but not the only one: the bound-given join takes a member into its pair the same way, so “Berg, abdul ma” reads given abdul ma in silence; neither joins a member its capitals settle (“Berg, abdul MA” reads suffix MA). Where a particle the suffix vocabulary does not also claim as an unambiguous word stands BEHIND it, the given part ends at that particle as this walk reads it, and the attachment that moves the particle to the family runs a stage too late to re-open the question – so “Doe, John ma do” keeps middle ma beside family do Doe, reporting only the attachment’s own PARTICLE_OR_GIVEN. Neither half is a rule about particles as such, and the caps spellings show it: “Doe, John van MA” reads family van Doe, suffix MA, reporting the particle’s fork alone, and “Doe, John MA vd” reads suffix MA past a vd the suffix vocabulary claims outright. All four POSITIONS above are silent – those last two names are the boundary each one stops at, not instances of it. A DELIMITED maiden clause is quiet for a different reason and is not one of the four. Where a recognized marker stands inside a delimited span the whole span is the maiden name, whatever its last word is and whether or not the pair is a configured maiden delimiter: “Jane Doe (nee Smith MA)” and “Jane Doe (nee Smith Ma)” both give maiden Smith MA / Smith Ma and report nothing. The writer drew the boundary, so no fork was available to decline – a settled position rather than a fork left un-asked. The boundary cuts both ways: “Jane Doe (nee Smith) ma” gives suffix ma and reports, the member standing outside the span.

TITLE_OR_NAME = 'title-or-name'

An input the title peel eats down to one last word which is itself title vocabulary still has to name somebody, so that word is read as the name – “Lord Chancellor” gives family “Chancellor”. A convention, not evidence: this is a name parser rather than a title parser, and handed a string whose every remaining word is a title the alternative is a title with no name at all. detail names that word. The report is made before the rule that moves the word between fields, so the peel shape reports under either order and names no field; the join shape’s detail names the field it was placed in. The peel shape points at one token, the join shape at the whole unit. A LONE title word reports nothing: “Dr.” reads as a title standing by itself, the peel left no word to be read as a name, and no fork was taken. One name word that is a JOIN carrying title vocabulary reports this kind too (“John of Bishop”): the unit is read as a name, and whether the title word inside it is a title is the fork. Not a word listed only as a title (“Baron”), whose collision with a name is a question about the VOCABULARY and not this kind. A title that may also be a given name (“Dean”, rules/H9) reports it too, where it opens the name outside a title phrase and no comma has marked the given name: “Dean Smith” gives given “Dean”. Only a word some title phrase holds (a pair a title reading’s attaches property licenses) reports, so “Grace Smith” does not.

PARTICLE_OR_GIVEN = 'particle-or-given'

An ambiguous particle 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. Three 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”). The third shape is at the TAIL rather than the head: a family comma names the surname, and a particle written behind the given name after it – “Beethoven, Ludwig van”, the Dutch and Flemish filing convention – is attached to that surname, over its reading as an ordinary name word. The comma settles which piece is the family and nothing about this, so the fork is real and detail names the word it turned on.

CONJUNCTION_OR_INITIAL = 'conjunction-or-initial'

A single-letter connective in a name written wholly in ONE case – all upper or all lower alike – where the writing therefore says nothing about which reading was meant. The letter is read as an INITIAL and this reports the fork: “jose e maria santos” gives middle “e maria” and “JOSE E MARIA SANTOS” middle “E MARIA”, both flagged. Only a letter the vocabulary marks both ways reports – a conjunction reading written may_be_initial (Lexicon.words(kind="conjunction", may_be_initial=True)), “e” and “i” by default – because a letter outside it is not in doubt: “JUAN GARCIA Y LOPEZ” joins into family “GARCIA Y LOPEZ” and reports nothing, as does the Cyrillic “ХОСЕ И МАРИЯ САНТОС” – whose join lands in GIVEN “ХОСЕ И МАРИЯ” rather than FAMILY, but reports nothing all the same. Four things never reach the fork. MIXED-case input decides on the writing instead, so “Jose E Maria Santos” reads the capital as an initial and “John e Smith” the lowercase letter as the connective, neither reporting. A CASELESS letter has no case to read, so Arabic “محمد و علي” keeps its connective silently. A MULTI-letter connective (“and”, “та”, “και”) or a symbol (”&”) is no initial’s shape at any casing. And a letter inside a maiden or delimited clause never reaches the fork either (the own-words doctrine, rules/P3): appending “ née Jones” or a nickname changes no report, because the clause’s words were never eligible for it. A letter with no name word after it in its part – nothing before a comma, a clause or the end, or only post-nominals and titles, as in “JOSE E”, “rovira, i” or “jose e phd” – connects nothing, so it is read as an initial and not reported. Where such a letter is a roman numeral at the end of the name the numeral fork reports it instead (SUFFIX_OR_NAME: “HENRY I”, “john i phd”); one ending a comma part reports nothing (“rovira, i”). detail names the token, the kind naming neither the field nor the letter: which field the reading lands in follows the name’s shape and its name_order, the PARTICLE_OR_GIVEN precedent.

GIVEN_OR_FAMILY = 'given-or-family'

A name of one name word that nothing else decided had to be read as one field or the other, and both readings fit it equally well – “Andrew”, “Smith”. The convention picks the field Policy.lone_name_word names, the given name by default, under every order and the same way every time, so detail names the field the convention chose rather than the kind naming it: the field is the caller’s setting, as PARTICLE_OR_GIVEN’s is the order’s. A name something DID decide reports nothing: a title, a maiden name, a family comma that names a family, a script whose own convention settles the order, or the vocabulary claiming the word (a particle, a bound given name, an initial’s shape) each settle the reading, and a settled reading is not a fork; so does a nickname beside it, whatever suffix stands there too (“‘Smitty’ Jones Jr.”, N3). A suffix standing BESIDE the one name word does not settle it – “Smith Jr.” reports.

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. NOT a fork the parse called: it reports a shape the parse could not recognize, which is the carve-out this enum’s own docstring names for this member and for COMMA_STRUCTURE. That docstring said “each says so on its own member below” while only COMMA_STRUCTURE’s did; this sentence is the other half (2026-09-18). 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.

class nameparser.Reading(kind: ReadingKind, role: Role, source: ReadingSource, languages: tuple[str, ...] = (), second_surname: bool = False, first_surname: bool = False)[source]

One way of reading a word, as a decision of the parse weighed it. Token.reading holds the reading the parse took, and Ambiguity.alternative the reading it did NOT take, so a caller who would have read the name the other way knows which field the word would have gone to and why the reading was available at all.

kind: ReadingKind

What the word is read as.

role: Role

The field the word would land in under this reading, with every other word of the name read as the parse read it.

source: ReadingSource

Where the reading comes from: a listed word, a declared name, a shape, or position alone.

languages: tuple[str, ...]

The BCP 47 tags of the shipped vocabulary files that hold this reading of the word, on in the parse’s own Lexicon (("ca", "es", "fil", "fr", "it", "nl", "pt", "si") for the particle de by default, ("pt",) under a pt choice, which drops the others’ readings of it; ("mul",) for the title Dr.), so a choice of languages names the files it turns on and not those it drops; sorted. A caller’s add of a kind a file holds for the word turns that file’s reading on, so the file is named (add("st", kind="generation") names cs, hr, sk and sl), and a remove stops naming the files it took a reading from. A name reading takes the files listing the word as a name of its field, given or family (Ma read as a family name names en, which lists it so, and not fil, which lists it as a given name). Empty for a reading no file holds: a shape, position alone, a name read into a field no file lists it in (van as a family name, nl listing it as given), a caller’s add of a kind no file holds for the word, or a shipped list kept outside the per-word files (the Korean surnames that divide 김민준). A suffix reading is matched by the word’s suffix entry whatever its finer kind, a word having one. An Ambiguity.alternative names the files holding it for any word the report names. Not compared: two readings are equal without it, as in 2.x. Added in 3.0.

second_surname: bool

True for a word of the family’s second name unit, where a rule read the family as two (Márquez in Gabriel García Márquez under two_family_names): the words ParsedName.surname2 holds. False everywhere else, a family the parse read as one unit included, and in every Ambiguity.alternative. Not compared: two readings are equal without it. Added in 3.0.

first_surname: bool

True for a word of the family’s FIRST name unit where a rule read the family as two (García in Gabriel García Márquez under two_family_names) or folded middle names into it (middle_as_family): the words ParsedName.surname1 holds there, the folded middles not among them. False everywhere else, and in every Ambiguity.alternative. Not compared. Added in 3.0.

class nameparser.ReadingKind(*values)[source]

What a Reading reads a word as. A StrEnum, like AmbiguityKind: members ARE their string values. New kinds may be added in minor releases; existing values never change meaning. The kinds are the parse’s, not the vocabulary’s: a reading a decision weighed is named by the category the decision asked about, so "credential" covers a word listed as one and a word only shaped like one, which Reading.source tells apart.

CREDENTIAL = 'credential'

A post-nominal credential or other acronym suffix (“MA”, “X.Y.Z.”).

GENERATION = 'generation'

A generational suffix: a roman numeral, or a word such as “Jr.” or “fils”.

AGNOMEN = 'agnomen'

An agnomen, a family word that is also a suffix (“Filho”, “Neto”).

POST_NOMINAL = 'post-nominal'

A listed post-nominal word of another kind (“Rinpoche”, “Esq.”).

HONORIFIC = 'honorific'

A form of address written after the name (“さん”, “씨”, “先生”), the words ParsedName.honorifics holds. Added in 3.0.

TITLE = 'title'

A title (“Dean”, “Prince”). The one value shared with a Role, on purpose: a title reading is the title field’s, so ReadingKind.TITLE == Role.TITLE says something true.

PARTICLE = 'particle'

A family-name particle (“van”, “do”).

CONJUNCTION = 'conjunction'

A connective joining the words beside it (“e”, “i”).

INITIAL = 'initial'

An initial standing for a name word (“V” in “John V Smith”).

NAME = 'name'

An ordinary name word, given or family.

PATRONYMIC = 'patronymic'

A patronymic or matronymic: a word naming the parent (“bin Mohamad”, “Ivanovich”), in whichever field the parse put it, the words ParsedName.patronymic holds. Added in 3.0.

class nameparser.ReadingSource(*values)[source]

Where a Reading comes from. A StrEnum; new sources may be added in minor releases.

VOCABULARY = 'vocabulary'

The vocabulary lists the word in this reading’s category (“MA” as a credential, “van” as a particle).

DECLARED = 'declared'

The vocabulary declares the word a name as well as listing it in another category: “Ma” as a surname, “Dean” as a given name.

SHAPE = 'shape'

The word’s shape, not a list: a roman numeral, letters run together with periods (“X.Y.Z.”), a word in capitals.

POSITION = 'position'

Nothing lists or shapes the word into this reading; it is the reading any word has where it stands, an ordinary name word. A word a credential run took by position alone (“Jones” in “John Smith PhD Jones”) has this as its alternative, where “Ma” in “JOHN SMITH MA” has DECLARED. As a reading taken, it is also the source of a word a run took into its field by where it stands rather than by what it is: “Jones” itself, read into the suffix behind “PhD”, or an unlisted word past a second comma.

Matching

Comparing names from several sources: see Matching names across sources.

class nameparser.Matching(parser: Parser, accept: frozenset[Outcome] = frozenset({Outcome.FOLDED, Outcome.GENERATION_ONE_SIDE, Outcome.INITIAL, Outcome.INITIALS_AGREE, Outcome.LISTED_SPELLING, Outcome.NOTED, Outcome.ONE_SIDE, Outcome.PART_OF_OTHER, Outcome.PARTICLES_ASIDE, Outcome.SAME}), other_readings: bool = False)[source]

The settings of a name match: the parser that reads the names and the outcomes accepted as consistent, and whether each side’s other readings are searched.

accept takes any iterable of Outcome members or their names and is stored as a frozenset with same always present. Passing it replaces the default set rather than extending it, so extend the default matcher’s set: parser.matching(accept=parser.matching().accept | {“family-initial”}). conflict cannot be accepted.

other_readings, a bool, off by default, switches on the search for each side’s other readings where the names as parsed are not consistent (see check), and the keys of each reading searched (see keys). It is not an outcome: no row reads it, and each row keeps its own outcome under the reading used.

check(a: str | ParsedName, b: str | ParsedName) → MatchResult[source]

Compare two names role by role. A string is parsed with this settings object’s parser. The result is consistent when every role’s outcome is among the accepted ones; conflict never is, and an empty family role on either side is a conflict. The check is symmetric: swapping the names gives the mirrored result. It is not transitive: J. Smith is consistent with John Smith and with Jane Smith, which are not consistent with each other.

Where other_readings is on and the names as parsed are not consistent, each side’s other readings are tried too: one per Ambiguity with an alternative, its words moved to the alternative’s role, one ambiguity at a time and never two together, save an alternative into the title or suffix, which is not tried: a title never conflicts and a suffix only on its generation, so the move could only empty a role that does. The table reported is the one with the strongest outcomes, each row keeping its own outcome, and the result names in left_alternative and right_alternative the ambiguity whose other reading each side used.

Where across-roles is accepted, a given or family role whose outcome is not accepted is compared with a different role of the other name: a given name with each word of the other’s middle name and with its nickname, a family name with the other’s maiden name. Only a full name on both sides counts, at same, folded or listed-spelling, never an initial. The strongest match resolves the role, which then reads across-roles, and is recorded in the result’s across; a middle word it used is taken out of the middle comparison, the words left compared role to role. No cross-role match is tried where across-roles is not accepted. The family role must still agree, here directly or through the maiden name.

keys(name: str | ParsedName) → frozenset[str][source]

Grouping keys: for each word of the folded family base, and each half of a hyphenated one beside its joined and its whole form, a half that is a listed particle left out (Al-Saud keys saud, alsaud and al-saud, never al), one key word;g where g is the first letter of the given name (empty where there is none). A word is keyed by every word in its spelling set among surnames (itself and the words it is listed as a spelling of, read as written and folded, each folded), and a given name by the first letter of every word in its set among given names, where a word that is a name of its own as well (jo) contributes only itself unless written with its trailing period, the one spelling set the check reads (_spellings). The guarantee rests on this: two words the check reads as listed spellings have sets that meet, so they share a member and with it a key; every family-base word the default check compares is keyed, so two names it finds consistent share a key and grouping by key loses no match. A name with no family has no key.

Each accepted opt-in adds the keys its outcome needs: family-initial a key f;g for the first letter f of the family’s first word, given-one-side a bare key for every family part keyed and across-roles the maiden name’s words without particles as family words, and as given letters the first letter of each middle word and of the nickname’s first word, save a middle word or a nickname written in initials alone, which the check never matches across roles. Where other_readings is on, each other reading check tries adds its own keys.

class nameparser.MatchResult(consistent: bool, roles: tuple[RoleMatch, ...], across: tuple[CrossRoleMatch, ...] = (), left_alternative: Ambiguity | None = None, right_alternative: Ambiguity | None = None)[source]

The result of comparing two names: whether nothing in them contradicts the match under the accepted outcomes, one RoleMatch per Role, in Role order, the CrossRoleMatch behind each role reading across-roles, in Role order of the role it resolved (empty unless across-roles is accepted), and on each side the Ambiguity whose other reading the table used, or None where that side is read as parsed (always None unless other_readings is on). A side uses one other reading at a time; each row keeps its own outcome under it.

A row is read by its role, a Role member or its name (result[“suffix”]), and weakest is the weakest row’s outcome.

property weakest: Outcome

The weakest row’s outcome, by Outcome.strength.

mirrored() → MatchResult[source]

The same result with the two names swapped: each role’s left and right text exchanged and its more side flipped, each cross-role match’s roles and texts exchanged, and the two sides’ alternatives exchanged.

class nameparser.RoleMatch(role: Role, outcome: Outcome, left: str, right: str, more: Side)[source]

One role’s comparison: the outcome, both sides’ text, and which side carried more. On a row read across-roles, the side carrying more is the one whose name held both the role and the cross role the match used (J. Edgar Hoover / Edgar Hoover: left).

mirrored() → RoleMatch[source]

The same comparison with the two names swapped.

class nameparser.CrossRoleMatch(left_role: Role, right_role: Role, outcome: Outcome, left: str, right: str)[source]

A role of one name matched against a different role of the other (across-roles): the left name’s role and text, the right name’s role and text, and the outcome of comparing the two texts.

mirrored() → CrossRoleMatch[source]

The same match with the two names swapped.

class nameparser.Outcome(*values)[source]

How one role of one name compares with the same role of another, listed in the order check ranks them, strongest first. strength is that rank as a number, larger for a stronger outcome and lowest for conflict; rank outcomes by it, since < on an Outcome compares the names as strings (folded sorts before same). conflict is the one outcome that is never accepted. The search for each side’s other readings is not an outcome but a switch, Matching.other_readings, and the reading used is named in MatchResult.left_alternative and right_alternative.

property strength: int

The outcome’s rank as a number: conflict is 0, and each outcome declared before another is stronger than it, same the strongest.

class nameparser.Side(*values)[source]

Which side of a comparison carried more of the role’s text.

Configuration

class nameparser.Lexicon(*, surnames: Iterable[str] = frozenset({}), given_names: Iterable[str] = frozenset({}), capitalization_exceptions: Mapping[str, str] | Iterable[tuple[str, str]] = (), _base: object = 'empty', _edits: Iterable[_Edit] = (), _languages: Iterable[str] = frozenset({}), _strict: bool = False, **removed: object)[source]

The vocabulary a parser matches against: which words may be read as titles, particles, suffixes, and so on. Immutable and hashable. Start from default() (the shipped vocabulary) or empty(), configure it by READING – add() and remove() take words and a vocabulary kind as the language files write it, add("leed", "ap", kind="credential") – join fragments with |, and pass the result to Parser(lexicon=...). Ask what a word may be read as with readings_of().

Words are normalized as they are stored – lowercased, NFC-composed, edge full stops stripped – so matching is case-insensitive.

Since 3.0 the sets a parse reads are compiled from the readings and are internal; the 2.x set fields (titles, suffix_acronyms, …) are refused wherever they were written, each refusal naming the field and pointing at docs/migrate.rst’s table of the kind each field’s words take. Three fields stay, being outside the per-word model: surnames, given_names and capitalization_exceptions.

State is a base (the shipped vocabulary, none, or a pickled release’s), the choice of languages parser_for() records (languages, strict), the ordered log of add() and remove() calls applied after the choice, and the three fields; two lexicons are equal when all of them are, the log compared call by call as written, so two logs that read alike can still differ (decisions/vocabulary-model, 2026-11-02 #754, item 3).

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 (nameparser/config/_ko_surnames.py); Chinese surnames ship in locales.ZH because Han segmentation is opt-in. A list, outside the per-word model: add to it with a fragment, lexicon | Lexicon(surnames={...}).

given_names: frozenset[str]

Given names read by two-family-name handling alone (Policy.two_family_names, rules/O6): the word that rule would take into the family stays where it is when listed, so “José Luis Pérez” keeps family “Pérez”. A word matches as written or with its acute and grave accents and diaeresis dropped (the tilde on n and the cedilla on c kept), so an entry written “jose” matches “José” too. No other rule reads it. Empty by default, so the switch reads as without it; a caller passes a list of its own, and locales.ES carries a Spanish list, with the switch on. Added in 3.0.

classmethod empty() → Lexicon[source]

A lexicon holding no readings, no surnames, no given names and no case masks, equal to Lexicon(): the start for a fragment to join onto another with |, or for a vocabulary built from nothing. A parser over it still reads by shape and position (Dr. John Smith keeps title Dr., a period-marked word opening a name), and, the default Policy dividing unspaced Hangul, warns at construction that no surnames cover it.

classmethod default() → Lexicon[source]

The shipped vocabulary, the lexicon Parser() uses when given none: every reading the language files turn on by default, the Korean census surnames and the case masks, with no given_names and no choice of languages. Cached, so every call returns the same object, which being immutable is safe to share; configure a copy with add(), remove() or |.

property languages: frozenset[str]

The languages this lexicon’s readings are chosen under, as BCP 47 tags; empty where none was chosen. Set by parser_for(), which unions a base’s choice with its packs’ (decisions/vocabulary-model, 2026-11-02 #754, item 4): the shipped readings are filtered by the choice, and the lexicon’s own add() and remove() calls apply after it. Added in 3.0.

property strict: bool

Whether the choice is strict: only the chosen languages’ vocabulary files, mul.toml and native-script words’ readings (as a plain choice of the same languages keeps them) are read. Added in 3.0.

readings_of(word: str) → tuple[LexiconReading, ...][source]

The readings this lexicon holds for word: one per kind and properties, each with the shipped files that hold it, a reading a caller added naming none; empty for a word it holds no reading of. word is normalized as the lexicon stores it, a phrase ("z domu") whole. “Is X a title here?” is any(r.category == "title" for r in lexicon.readings_of(x)); whether a parse read it as one is Token.reading’s. Added in 3.0.

words(*, kind: str, **properties: object) → tuple[str, ...][source]

The words holding a reading of kind on in this lexicon, sorted: a kind or a category, as remove() takes it (words(kind="title") lists every title word, dean among them, whichever set its readings compile to), and properties, which a kind takes (title, suffix and particle are kinds as well as categories, and take their own kind’s properties), narrowing to the readings that carry them as add() spells them, each compared in its own reading’s kind (so through a category an unwritten value means each kind’s own: acronym=None under suffix lists the credentials, unwritten-true, and the suffix words, unwritten- false, alike) (words(kind="title", addresses="given"), the titles that address a person by the given name). A phrase the lexicon holds whole ("z domu") is one entry. Added in 3.0.

add(*words: str, kind: str | None = None, **properties: object) → Lexicon[source]

This lexicon with a reading of kind added to each word. kind is required; the signature’s default exists only so that a 2.x call, which names no kind, is refused pointing at docs/migrate.rst’s table of kinds. It is a vocabulary kind as the language files write it, a key of nameparser._reading_index.CATEGORY ("title", "office", "credential", "generation", "preposition", "conjunction", "bound_given", "birth_marker", "given", "family", …), and properties that kind’s behavioral properties as the files spell them (addresses="given", after_given=True, abbreviation=True, attaches=[...], acronym=..., attached="after", may_be_initial=True, completed_by=[...] on a bound_given reading, after_particle=True on a given reading, shared=True on a name reading, and same_as=... on a reading of any kind: the word this one is a spelling of, which name matching reads as a listed-spelling and keys alike in the roles that read the kind’s words (a given reading’s in the given name, middle name and nickname, a family reading’s in the family and maiden names, a title’s and a suffix’s in theirs), and which on a given reading also marks a given name written short; written on a word, it applies to the word’s given readings, so the word keeps no plain given reading and its link counts written bare, where a word the vocabulary also holds as a name of its own counts only written with its period); a property given its unwritten value (None, False) is removed. Where the word holds a reading of kind already, the add updates its properties in every file holding it; otherwise it adds the caller’s own reading, which names no file. Warns where it makes a title reading that a given reading keeps a title only inside a title phrase (rules/H9), naming the remove that makes it a title everywhere.

remove(*words: str, kind: str | None = None, every: bool = False, **removed: object) → Lexicon[source]

This lexicon without each word’s readings of kind: a kind, or a category, matching every kind of it (kind="title" takes out dean’s office reading, kind="name" its given and family ones), or None, taking out every reading, so the word leaves the vocabulary. With every=True, no words and a kind, every word’s readings of it go (remove(kind="title", every=True)), those a later choice of languages turns on included; a call with no words and no every raises, so an empty remove(*words, kind=...) never takes out a whole kind. Removing a reading the word does not hold changes nothing now, and is kept: a later choice of languages may turn that reading on.

class nameparser.LexiconReading(text: str, kind: str, category: str, properties: tuple[tuple[str, Any], ...] = (), languages: tuple[str, ...] = ())[source]

One reading a Lexicon holds for a word, as Lexicon.readings_of() returns it: what the word may be read as, in the vocabulary files’ terms (decisions/vocabulary-model, 2026-11-02 #754, item 2).

kind is a vocabulary kind as the language files write it ("office", "credential", "preposition", "given"), finer than ReadingKind, which is the parse’s; category its category ("title", "suffix", "particle", "conjunction", "bound_given", "relation", "name"); properties its behavioral properties as sorted (name, value) pairs, spelled as the files spell them (("addresses", "given")), a list value a sorted tuple; languages the BCP 47 tags of the shipped files holding it, sorted, empty for a reading a caller added. Whether the parse READ a word this way is the parse’s question, Token.reading: a title reading beside a given one is a title only inside a title phrase (rules/H9). Added in 3.0.

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, unlisted_dotted_suffixes: bool = True, unlisted_caps_suffixes: CapsSuffixes = CapsSuffixes.AFTER_COMMA, two_family_names: bool = False, two_given_names: bool = False, lone_name_word: Role = Role.GIVEN)[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 (a name of one name word is placed by lone_name_word instead). 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. A rotation restores the given-first reading a family-first listing hides, so under a declared FAMILY_FIRST or FAMILY_FIRST_GIVEN_LAST name_order it stands down and the declaration decides.

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). Set this for a clause that says nothing about itself, which is two kinds and not one: content with no marker word in it, and a LONE marker word. Since #335 a clause that opens with a marker word AND has a word after it reads as the maiden name whatever pair encloses it, so “Jane Smith (née Jones)” needs no configuration – unless S1 takes the clause first: a clause of credentials, generations or titles is set apart from the name, and other content ending in a period loses its brackets and is read as if written bare. A maiden_markers entry opening the enclosed content is dropped from the value – all of it, an entry being allowed to span more than one word (“z domu”) – but only where a word stands past it: a clause of nothing but its marker keeps its words, since a lone “(Nee)” is a maiden NAME and 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, the semicolon, and a hyphen or slash standing alone split suffix groups after a whole name and cannot be replaced – comma handling is structural (the same comma reading that parses “Family, Given” input), not a configurable delimiter, the semicolon is built in since 2.6 and the spaced hyphen and slash since 2.7 (rules/C10). A declared separator glued to a suffix word after the first comma counts too, as the semicolon does: “John Smith, MD; PhD” reads suffix “MD; PhD”, the word keeping its separator. The built-in hyphen and slash do not count glued, being a word’s own there (“al-”, “MD/PhD”); declaring “ - “ makes the hyphen count glued.

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”), and “john smith, v” the same way, a name written in one case reading as its capitals (rules/T4). 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.

unlisted_dotted_suffixes: bool

Reads an UNLISTED token of two or more period-separated chunks as a credential where the position allows it: “John Smith X.Y.Z.” gives suffix X.Y.Z. and “Jack X.Y.Z.” keeps family X.Y.Z., the same words-to-spare rule a listed ambiguous acronym takes, and either reading is reported. Case is irrelevant here – the periods are the signal, so “john smith x.y.z.” reads as the mixed-case spelling does. Whole-token vocabulary still wins (“M.A.”, “Ph.D.”, “A.B.C.”), and a single trailing period is not this shape (“John Smith Xyz.” keeps family Xyz.), and nor are paired initials, two single letters each followed by a period in a script with capitals (“Ram Bahadur B.K.” keeps family B.K.) except right after a comma, where they are the given name unless something in the part speaks for them. False reads such a token as name material everywhere, as 2.3 did for a token no chunk claimed; the roman-chunk retirement (rules/S3) is not behind this switch, and still reports the fork.

unlisted_caps_suffixes: CapsSuffixes

Where an UNLISTED all-caps word of two or more letters, with no period in it, reads as a credential (CapsSuffixes). The name must contrast it: a word of the name holding a capital whose last letter is lowercase (“Smith”, “DiCaprio”) that the vocabulary does not claim as a title, particle or credential – a record written wholly in capitals or wholly in lowercase keeps every word a name word. A listed member keeps its own case lean in every setting (“Jack MA” gives suffix MA), and the roman-numeral fork claims a bare numeral first (“Jack VI” is unaffected).

AFTER_COMMA, the default, reads it only in the part after a comma with two or more name words before it: “John Smith, XYZ” gives suffix XYZ, while “Smith, XYZ” keeps given XYZ (one name word) and “García Márquez, MJ” keeps given MJ – a lone two-letter word is how a person’s initials are written, and two words before the comma may be one surname, the case #563 decides for “M.J.” too. The all-caps SURNAME convention (“Jean DUPONT”, “DUPONT, Jean”) writes the capitals at the end of a name or before a comma, never after one behind a full name, which is why this position is on by default and the others are not.

EVERYWHERE adds the end of a name with no comma, the convention’s own position: “John Smith XYZ” gives suffix XYZ, and “Jean Pierre DUPONT” gives family Pierre, suffix DUPONT – the cost, a swallowed family name being the worse failure. The two-word “Jean DUPONT” and “Minjun KIM” keep their family even then (one word before the credential is never enough), but gain the fork’s report. OFF reads no such word as a credential anywhere and reports nothing, as 2.3 did. A digit disqualifies the token and a single capital stays an initial. isupper() is script-agnostic, so the same holds in any script with a case contrast (“Иван ИВАНОВ”, “Jean ÉCOLE”).

two_family_names: bool

Reads the last two name words of a name as the family, for data where a person carries two family names and writes both (rules/O6): “Gabriel García Márquez” gives given Gabriel, family García Márquez, and “Ana María García López” gives middle María, family García López. A particle counts with the word it attaches to (“Juan Pérez de la Vega” gives family Pérez de la Vega), a family already holding two words keeps what it has (“José María Ortega y Gasset”, “Ana María García-López”, “Ana María de la Vega Rodríguez”), titles and suffixes count as no name word, and a name of two name words is unchanged. The word taken must be one name word a family name could be, so an initial written alone (“Ana M. García”), hyphenated initials (“Ana M.-J. López”) and a bound given-name pair stay where they are; a hyphenated word counts as one when taken (“Andrés Sánchez-Cid Torres” gives family Sánchez-Cid Torres), so a hyphenated given name is taken unless every part of it is a listed given name or an initial (Lexicon.given_names). Under a family-first name_order the FIRST two name words are the family; a name its script reads in an order other than name_order (a spaced Han or Hangul name under the default) is left alone, though under a declared FAMILY_FIRST, which the script agrees with, it moves. A family comma decides by itself, so “García López, Ana” is unaffected. A name with one family name behind two given names loses one to the family (“Juan Carlos Pérez” gives family Carlos Pérez), which is most English and Argentine names of three words or more and many Portuguese and Brazilian ones as filed: switched on for a mixed population it costs more names than it mends, so it is off by default and reports nothing. Runs before middle_as_family, which then folds whatever middles are left.

two_given_names: bool

Reads the given name as two name units, for data where a person carries a compound given name and no middle name (rules/O7): “Juan Carlos Pérez” gives given Juan Carlos, family Pérez, and with two_family_names too, “María José García López” gives given María José, family García López. The middle unit beside the given name joins it – the one after it, or the one before it under FAMILY_FIRST_GIVEN_LAST with no family comma, after which the part reads given-first – and any middles past that stay middles; a particle chain the parse left in the middle counts with its word (“María del Carmen de la Vega” gives given María del Carmen), and an initial is taken, a given name written short. A given name already holding two words keeps what it has (a hyphenated or bound pair), the word taken must be one name word, and a word an ending rule reads as a patronymic stays where it is. A family comma leaves the given part to this rule (“Pérez, Juan Carlos”); a name its script reads in an order other than name_order is left alone, as two_family_names leaves it. Runs after two_family_names, which takes its word first (“Gabriel García Márquez” keeps given Gabriel), and before middle_as_family, which then folds whatever middles are left. A name whose second given word is a middle name gains it in the given name (“John Quincy Adams” gives given John Quincy), so it is off by default and reports nothing. Added in 3.0.

lone_name_word: Role

The field a name of one name word reads that word as, when nothing else decides it (rules/O5): Role.GIVEN (the default) or Role.FAMILY, under every name_order. “Anh” gives given Anh; with lone_name_word=Role.FAMILY it gives family Anh. Which field a word written alone fills is a fact about where the data came from – a list of surnames, a list of the names people go by – not about the order its longer names are written in, so it does not follow name_order. A title, a nickname, a maiden name, a family comma or a script that settles the order still decides first (“Mr. Johnson” gives family Johnson). The reading is reported as a given-or-family ambiguity either way, save a word its own reading claimed (a particle, a bound given name, an initial), which the switch places too and which reports nothing. Added in 3.0; through 2.x the field followed name_order.

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, unlisted_dotted_suffixes: bool | _Unset = _Unset.UNSET, unlisted_caps_suffixes: CapsSuffixes | _Unset = _Unset.UNSET, two_family_names: bool | _Unset = _Unset.UNSET, two_given_names: bool | _Unset = _Unset.UNSET, lone_name_word: Role | _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.

two_given_names: bool | _Unset

Patches Policy.two_given_names, a scalar (later wins). Added in 3.0.

lone_name_word: Role | _Unset

Patches Policy.lone_name_word, a scalar (later wins). Added in 3.0.

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, and a middle word with the ending fills ParsedName.patronymic. Enabled by locales.RU.

TURKIC = 'turkic'

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

class nameparser.CapsSuffixes(*values)[source]

Where Policy.unlisted_caps_suffixes reads an unlisted all-caps word as a credential (#516, #564).

OFF = 'off'

Nowhere: such a word is name material in every position, as 2.3 read it.

AFTER_COMMA = 'after-comma'

Only after a comma, behind two or more name words (“John Smith, XYZ” gives suffix XYZ). The default: the all-caps SURNAME convention (“Jean DUPONT”, “DUPONT, Jean”) never writes the capitals there.

EVERYWHERE = 'everywhere'

After a comma and ending a name with no comma (“John Smith XYZ” gives suffix XYZ), where the all-caps surname convention does write them – “Jean Pierre DUPONT” then gives family Pierre, suffix DUPONT.

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 and the conjoining jamo). 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, full-width and halfwidth (タロウ) alike. A PURE-katakana token may be a transcribed foreign name in its original order (マイケル) or a Japanese name’s reading written family-first (ヤマダ タロウ, and legacy halfwidth data), and the script cannot say which, so no default behavior keys on this member; it exists so the classifier can name what it deliberately declines. A caller who knows their katakana is Japanese maps it to FAMILY_FIRST in script_orders.

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 in kanji and kana other than katakana alone, 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), plus the halfwidth corner brackets 「」 that legacy JIS X 0201 data carries (#597). 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(), languages: frozenset[str] = frozenset({}))[source]

A named, shareable bundle of vocabulary and behavior for a naming tradition: a choice of languages, a lexicon fragment and 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 joined to the base parser’s lexicon, before it, so the base’s own readings win where the two disagree, and applied after the choice in languages, which selects among the base’s shipped readings (decisions/vocabulary-model, 2026-11-02 #754, item 3).

policy: PolicyPatch

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

languages: frozenset[str]

The languages chosen, as BCP 47 tags ({"pt"}, {"pt-BR"}): parser_for unions the packs’ languages, and the base’s recorded choice, into one choice, which turns on the chosen languages’ readings the default leaves off and, on a word a chosen language holds a vocabulary reading of, drops other languages’ vocabulary readings. A tag selects its own vocabulary file and its ancestors’ (pt-BR selects pt too), never its descendants’. Empty, the default, chooses nothing, and the default vocabulary stands as it is. Added in 3.0.

Locale packs: named (languages, 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/_types 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]'; the docstring of nameparser.locales.ja says what it wraps, what it declines, and its licensing, and Segmenters what a segmenter owes its caller.

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.

Default vocabulary

The default vocabulary is asked of a lexicon: Lexicon.words() lists the words holding a reading of a kind, and Lexicon.readings_of() what one word may be read as; to change a default for your parses, configure a Lexicon (Customizing the parser). The modules that exposed its word lists as constants through 2.x are gone in 3.0 (Migrating to 3.0), and surnames, the Korean surname list, is private: its words are Lexicon.default().surnames.

The default vocabulary’s data.

Every module here is private, and nameparser.Lexicon reads them: the vocabulary written per word in nameparser/vocab/*.toml, compiled, and _ko_surnames, the Korean surname list unspaced hangul is divided on, a segmentation list outside the per-word model, which Lexicon.default().surnames holds. Ask a lexicon what it holds with words() and readings_of(). Through 2.x this package also held one re-exporting module per word list (titles, particles, suffixes, …) and the compiled patterns in regexes, removed in 3.0 (#1007, #1008), the public surnames, made private in 3.0, and the 1.x Constants configuration, removed in 3.0 too (decisions/3-0-reevaluations).