API reference¶
Classes and functions¶
Parsing¶
- nameparser.parse(text: str) ParsedName[source]¶
Parse a name with the default configuration and return a
ParsedName. Equivalent toParser().parse(text); build your ownParser(or useparser_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 aPolicy(behavior), both defaulted when omitted. Build one when you need non-default configuration, build it once, and callparse()many times – it is immutable and thread-safe.An optional keyword-only
segmenter(aSegmenter) 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.languagesread through: set byparser_for(), empty where none was chosen. Added in 3.0.
- property strict: bool¶
Whether the choice is strict,
Lexicon.strictread 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 configuredsegmenter, 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, sorevise(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 throughextra_suffix_delimitersparts 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
Matchingbound to THIS parser, so itscheckandkeysparse str arguments with this parser’s lexicon and policy. A ParsedName argument is compared as given, whatever parser produced it.acceptis the iterable of outcomes counted as consistent; None takes the default set, and any other value replaces it, so extend the default withparser.matching(accept=parser.matching().accept | {"family-initial"}).other_readings=Truealso searches each side’s other readings (Matching.other_readings). A value is validated byMatching, 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’
languagesand 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 throughbase, does not change it:parser_for(EN, base=parser_for(PT))reads asparser_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’saddandremoveapply 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=Truemakes 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)readsKim Do Hyunwith no Portuguese particledo). A caller’s own readings and the packs’ fragments apply after it, as after a plain choice, and the policy patches unchanged.stricthas three states, assegmenterdoes: omitted (UNSET) keeps base’s recorded strictness,Falseon a strict base raises ValueError, a strict choice not being undone by a later call, andTrueon 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
segmenteris 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 sameUNSETspelling 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 explicitNoneCLEARS 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 intotoken[:2]andtoken[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 RAISESValueErroron 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 aSegmentationthat divides it, orNoneto 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 inPolicy.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 throughtokens/tokens_for(); correct a parse withreplace()(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 withrender(),initials(),capitalized(), orstr().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().
- 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_FIRSTorFAMILY_FIRST_GIVEN_LAST, the declaredname_orderor thescript_ordersentry that overrode it, andGIVEN_FIRSTfor a name with a family comma.str()andrender()’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==andcomparison_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 namesmiddle_as_familyfolds 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,credentialsandhonorificsdivide 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_delimitersencloses, 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 asfamilywrites them;''for a family with none. A family thatreplace()or Parser.revise wrote carries no particle tags, so its words are all infamily_base.
- property family_base: str¶
familywithout its particles (“Vega” in “Juan de la Vega”), space-joined asfamilywrites 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
suffixjoins 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
suffixjoins them;''for a name with none. A suffix word with noToken.reading(onereplace()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
suffixjoins 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 orderfamilywrites them. Added in 3.0.
- property surname2: str¶
The family’s second name unit where the parse read the family as two (
MárquezinGabriel García Márquezundertwo_family_names,李in王李小明underlocales.ZH), whichfamilykeeps;''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 oncereplace()or Parser.revise rewritesfamily, 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íainGabriel García Márquezundertwo_family_names,de la VegainJuan de la Vega del Valleunder it too),surname2being the other; elsewhere the wholefamily. A middle namemiddle_as_familyfolds into the family is in neither (María GarcíainAna María García Lópezundermiddle_as_family, whose surname1 isLópez). Added in 3.0.
- property surname1_particles: str¶
surname1’s particles, asfamily_particlesreads the whole family’s (de lainJuan de la Vega del Valleundertwo_family_names), so the two units’ particles are the family’s. Added in 3.0.
- property surname1_base: str¶
surname1without its particles, asfamily_basereads the whole family (PérezinJuan Pérez de la Vegaundertwo_family_names, wherefamily_baseisPé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", …), inRoleorder, each value the string its property gives,''included;include_empty=Falseleaves 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 whatstr()passes, lays the name out inorder: 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 thesuffixfield 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.tokensorParsedName.tokens_for(); you only construct one directly when hand-building aParsedName.- 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.
- 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 ofAmbiguity.alternative, which holds the reading NOT taken. None for a token the parse did not read: oneParsedName.replace()orParser.revise()builds, or one loaded from a pickle written before 3.0. Not compared: two tokens equal without it, as before 3.0, andParsedName.comparison_key()ignores it. Added in 3.0.
- nameparser.STABLE_TAGS = frozenset({"particle", "conjunction", "initial", "joined"})¶
The four
Token.tagsvalues that are stable API:particle(a word from the particle vocabulary, “de”/”van”, wherever it lands — combine withRole.FAMILYfor 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), andjoined(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
Tokencame from: a character range intoParsedName.originalsuch thatoriginal[start:end]is the token’s source text (endexclusive). A plain two-int NamedTuple;NoneinToken.spanmarks 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, likeAmbiguityKind: members ARE their string values, sotoken.role == "given"compares directly andstr(Role.GIVEN) == "given". Members order as strings, sosorted()yields alphabetical order – iterateRoleitself 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.ambiguitiesinstead 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;
orderis 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, andParsedName.comparison_key()ignores it.
- class nameparser.AmbiguityKind(*values)[source]¶
The stable vocabulary of
Ambiguitykinds. A StrEnum: members ARE their string values, sokind == "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
vdfor van der and declines the decoration. Which name part was declined depends on position andname_order, sodetailnames 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 suffixMAand says so, “Doe, John Ma” keeps middleMaand 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 familydo Doeand onePARTICLE_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 suffixEd 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 reportMEng). A member its own capitals already made the credential reports nothing, so “Smith, PhD MA” and “John Smith, PhD MA” read suffixPhD MAin silence. Since 2.4 a maiden marker’s clause reports at ITS trailing slot too, in both directions: “Jane Doe nee Smith ma” gives maidenSmithwith suffixmaand says so, “Jane Doe nee Smith Ma” keeps maidenSmith Maand 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 maidenJones 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 middlema 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 maidenMA Smith), a bare trailing title among them (“Jane Doe nee Smith ma Prof” keeps maidenSmith ma Prof, where the period-marked “Jane Doe nee Smith ma Prof.” reads titleProf.and suffixmaand reports). A member its capitals settle is no such member: it starts the credential’s run (rules/S8), so “Doe, John MA Smith” reads suffixMA 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 suffixMAand “Doe, Mr. MA PhD” suffixMA PhD, the credential-run gate reading those segments whole; “Doe, Dr. Ma” reaches the walk instead and makesMathe 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 itsMA, and “Doe, Dr. MA Smith” – the same member, one title in front of it – reads givenMAin 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 givennee, middleSmithand suffixma– and reports, themaending 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 middlevan Main 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 givenabdul main silence; neither joins a member its capitals settle (“Berg, abdul MA” reads suffixMA). 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 middlemabeside familydo Doe, reporting only the attachment’s ownPARTICLE_OR_GIVEN. Neither half is a rule about particles as such, and the caps spellings show it: “Doe, John van MA” reads familyvan Doe, suffixMA, reporting the particle’s fork alone, and “Doe, John MA vd” reads suffixMApast avdthe 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 maidenSmith MA/Smith Maand 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 suffixmaand 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.
detailnames 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’sdetailnames 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’sattachesproperty 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
detailis what tells them apart. A particle left standing alone chained nothing and was assigned a role, whichdetailnames (“read as a given name”) – that role is whatever assignment gave it, so it followsname_orderand anyscript_ordersentry, 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, anddetailsays 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 anddetailnames 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”).detailnames the token, the kind naming neither the field nor the letter: which field the reading lands in follows the name’s shape and itsname_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_wordnames, the given name by default, under every order and the same way every time, sodetailnames 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
detailnames 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, anddetailnames 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.readingholds the reading the parse took, andAmbiguity.alternativethe 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 particledeby default,("pt",)under aptchoice, which drops the others’ readings of it;("mul",)for the titleDr.), so a choice of languages names the files it turns on and not those it drops; sorted. A caller’saddof a kind a file holds for the word turns that file’s reading on, so the file is named (add("st", kind="generation")namescs,hr,skandsl), and aremovestops 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 (Maread as a family name namesen, which lists it so, and notfil, 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 (vanas a family name,nllisting it as given), a caller’saddof 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. AnAmbiguity.alternativenames 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árquezinGabriel García Márquezundertwo_family_names): the wordsParsedName.surname2holds. False everywhere else, a family the parse read as one unit included, and in everyAmbiguity.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íainGabriel García Márquezundertwo_family_names) or folded middle names into it (middle_as_family): the wordsParsedName.surname1holds there, the folded middles not among them. False everywhere else, and in everyAmbiguity.alternative. Not compared. Added in 3.0.
- class nameparser.ReadingKind(*values)[source]¶
What a
Readingreads a word as. A StrEnum, likeAmbiguityKind: 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, whichReading.sourcetells 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.honorificsholds. 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, soReadingKind.TITLE == Role.TITLEsays 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.patronymicholds. Added in 3.0.
- class nameparser.ReadingSource(*values)[source]¶
Where a
Readingcomes 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.
- 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).
- 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.
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) orempty(), configure it by READING –add()andremove()take words and a vocabulary kind as the language files write it,add("leed", "ap", kind="credential")– join fragments with|, and pass the result toParser(lexicon=...). Ask what a word may be read as withreadings_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_namesandcapitalization_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 ofadd()andremove()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_scriptsactivates. 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, andlocales.EScarries 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 Smithkeeps titleDr., a period-marked word opening a name), and, the defaultPolicydividing 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 nogiven_namesand no choice of languages. Cached, so every call returns the same object, which being immutable is safe to share; configure a copy withadd(),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 ownadd()andremove()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?” isany(r.category == "title" for r in lexicon.readings_of(x)); whether a parse read it as one isToken.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,deanamong them, whichever set its readings compile to), and properties, which a kind takes (title,suffixandparticleare kinds as well as categories, and take their own kind’s properties), narrowing to the readings that carry them asadd()spells them, each compared in its own reading’s kind (so through a category an unwritten value means each kind’s own:acronym=Noneundersuffixlists 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 abound_givenreading,after_particle=Trueon agivenreading,shared=Trueon a name reading, andsame_as=...on a reading of any kind: the word this one is a spelling of, which name matching reads as alisted-spellingand keys alike in the roles that read the kind’s words (agivenreading’s in the given name, middle name and nickname, afamilyreading’s in the family and maiden names, a title’s and a suffix’s in theirs), and which on agivenreading 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 agivenreading keeps a title only inside a title phrase (rules/H9), naming theremovethat 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 outdean’sofficereading,kind="name"itsgivenandfamilyones), or None, taking out every reading, so the word leaves the vocabulary. Withevery=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 emptyremove(*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
Lexiconholds for a word, asLexicon.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).kindis a vocabulary kind as the language files write it ("office","credential","preposition","given"), finer thanReadingKind, which is the parse’s;categoryits category ("title","suffix","particle","conjunction","bound_given","relation","name");propertiesits behavioral properties as sorted(name, value)pairs, spelled as the files spell them (("addresses", "given")), a list value a sorted tuple;languagesthe 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 agivenone 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 toParser(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 withscript_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 aSegmenterif 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 withsegment_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 familyX.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 familyXyz.), and nor are paired initials, two single letters each followed by a period in a script with capitals (“Ram Bahadur B.K.” keeps familyB.K.) except right after a comma, where they are the given name unless something in the part speaks for them.Falsereads 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 suffixMA), 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 suffixXYZ, while “Smith, XYZ” keeps givenXYZ(one name word) and “García Márquez, MJ” keeps givenMJ– 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.EVERYWHEREadds the end of a name with no comma, the convention’s own position: “John Smith XYZ” gives suffixXYZ, and “Jean Pierre DUPONT” gives familyPierre, suffixDUPONT– 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.OFFreads 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, familyGarcía Márquez, and “Ana María García López” gives middleMaría, familyGarcía López. A particle counts with the word it attaches to (“Juan Pérez de la Vega” gives familyPé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 familySá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 familyCarlos 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, familyPérez, and withtwo_family_namestoo, “María José García López” gives givenMaría José, familyGarcí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 givenMarí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 givenGabriel), 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 givenJohn 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) orRole.FAMILY, under every name_order. “Anh” gives givenAnh; withlone_name_word=Role.FAMILYit gives familyAnh. 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 familyJohnson). The reading is reported as agiven-or-familyambiguity 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
PolicyPatchonto 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
PolicyPatchfield, distinguishable from every real value includingNoneandFalse. 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_suffixesreads 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 familyPierre, suffixDUPONT.
- 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_orderstable: 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 theHIRAGANAentry 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 overname_order, including aname_orderyou set explicitly —name_ordergoverns 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 passscript_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_delimitersset: 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 tomaiden, just list it inmaiden_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 innameparser.locales; building your own needs no registration – construct one and pass it toparser_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_forunions 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-BRselectspttoo), 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 ofnameparser.locales.jasays 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).