API reference¶
The 2.0 API¶
Parsing¶
- nameparser.parse(text: str) ParsedName[source]¶
Parse a name with the default configuration and return a
ParsedName. Equivalent 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.md#A1’s Accepted clause): parse-totality gains its one exception – an exception raised by the segmenter propagates, because a user-supplied callable’s own error is a user-code error, not a content error – and this Parser pickles only if its segmenter does (a module-level function pickles; a lambda or closure does not). With no segmenter, both promises hold unconditionally: all validity checking happens at construction, so a Parser that constructs successfully cannot fail at parse time on any str content.(The None field defaults resolve in __post_init__; after construction lexicon and policy are always non-None – the annotations state the steady-state truth, hence the assignment ignores on the defaults.)
- segmenter: Callable[[str], Segmentation | None] | None¶
An optional hook supplying outside knowledge of where an unspaced token divides – see the class docstring; None leaves such tokens whole. Keyword-only, so the reserved growth stays additive: positional construction keeps its two-argument shape.
- parse(text: str) ParsedName[source]¶
Parse one name string into a
ParsedName. Never raises on string content (unparseable input yields empty fields plus ambiguities); non-str raises TypeError eagerly, with a decode hint for bytes (bytes support ended with 1.x). The one exception to that totality is a 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.
- capitalized(name: ParsedName, *, force: bool = False) ParsedName[source]¶
ParsedName.capitalized()under THIS parser’s lexicon. The no-argument form of that method uses the DEFAULT lexicon – for names parsed with a custom Parser, use this method.
- nameparser.parser_for(*locales: Locale, base: Parser | None = None, segmenter: Callable[[str], Segmentation | None] | None | _Unset = _Unset.UNSET) Parser[source]¶
Lexicon fragments unioned left-to-right onto base’s; policy patches applied left-to-right (later wins; set-valued fields union per the patch metadata). Validation errors raised while applying a pack are wrapped with that pack’s identity (rule D2) – PolicyPatch validates lazily, so with stacked packs the raw error would otherwise point at nothing. Two packs setting the same SCALAR field is a declared conflict: UserWarning, later wins.
A
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, ...] = ())[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); 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).
- tokens_for(role: Role | str) tuple[Token, ...][source]¶
The tokens of one field, in document order. Takes a Role member or its string value; anything else raises ValueError naming the valid roles.
- replace(**fields: str) ParsedName[source]¶
Return a new ParsedName with the named fields re-tokenized as synthetic tokens (span=None). Whitespace-splits each value; an empty value clears the field. original is unchanged (provenance). Ambiguities referencing replaced tokens are dropped.
Replacement tokens carry no 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 = '{title} {given} "{nickname}" {middle} {family} ({maiden}) {suffix}') str[source]¶
Fill the str.format spec from the seven role fields and the derived views; empty fields collapse (#254), including the default spec’s decorations (empty ‘””’ and ‘()’ wrappers). The default shows every non-empty field: the nickname quoted after the given name, the maiden name parenthesized after the family name. Unknown keys raise KeyError naming the valid fields.
- initials(spec: str = '{given} {middle} {family}', delimiter: str = '.', separator: str = ' ') str[source]¶
Initials per group; v1’s initials_format/_delimiter/_separator become call-site arguments instead of Config-wide settings. 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.md#R5). Idempotent.
- class nameparser.Token(text: str, span: Span | None, role: Role, tags: frozenset[str] = frozenset({}))[source]¶
One classified word of a parsed name: its text, where it came from, which field it belongs to, and how it was classified. Read tokens off
ParsedName.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.
- 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 “씨.”, or a marked single-letter connective in a name written in one case, rules.md#P3), 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.giveslast_list['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, ...])[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).
- 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; planned for 2.x. 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 “(MBA)” escapes to suffix on vocabulary alone.
- SUFFIX_OR_NAME = 'suffix-or-name'¶
A trailing word reads plausibly as either a post-nominal or an ordinary name part. Covers an ambiguous acronym written without periods (“John Smith MA” takes MA as a credential because a family name remains; “JACK MA”, 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.md#S2) 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. 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.md#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 C1’s name-word count reports its flip once 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.md#S2) 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 new, 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.md#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). 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.md#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 and reports. 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. Where a particle the suffix vocabulary does not also claim 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 middleMA, capitals and all, beside 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, suffixMAand reports both forks, 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 Prince”): the unit is read as a name, and whether the title word inside it is a title is the fork. Not the title-vs-given-name collision on a word like “Baron”, which is a question about the VOCABULARY and is not this kind.
- 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 –
Lexicon.conjunctions_ambiguous, “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.md#P3): appending “ née Jones” or a nickname changes no report, because the clause’s words were never eligible for it.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 given name under the default order and the family name under a declared family-first one, the same way every time, so
detailnames the field the convention chose rather than the kind naming it: the same reason PARTICLE_OR_GIVEN cannot. 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. A nickname or a suffix standing BESIDE the one name word does not settle it – “‘Smitty’ Jones Jr.” and “Smith Jr.” both report.
- 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.
Configuration¶
- class nameparser.Lexicon(titles: frozenset[str] = frozenset({}), given_name_titles: frozenset[str] = frozenset({}), suffix_acronyms: frozenset[str] = frozenset({}), suffix_words: frozenset[str] = frozenset({}), suffix_acronyms_ambiguous: frozenset[str] = frozenset({}), particles: frozenset[str] = frozenset({}), particles_ambiguous: frozenset[str] = frozenset({}), conjunctions: frozenset[str] = frozenset({}), conjunctions_ambiguous: frozenset[str] = frozenset({}), bound_given_names: frozenset[str] = frozenset({}), maiden_markers: frozenset[str] = frozenset({}), surnames: frozenset[str] = frozenset({}), honorific_tails: frozenset[str] = frozenset({}), capitalization_exceptions: tuple[tuple[str, str], ...] = ())[source]¶
The vocabulary a parser matches against: which words are titles, particles, suffixes, and so on. Immutable and hashable. Start from
default()(the shipped vocabulary) orempty(), derive variants withadd()/remove()/|(union), and pass the result toParser(lexicon=...). Entries are normalized at construction – lowercased, NFC-composed, edge full stops stripped – so matching is case-insensitive. Vocabulary entries are single words – a multi-word entry warns at construction and can never match. Two fields are exempt, and they differ in HOW they match:given_name_titlesis looked up against the run of words the parse has ALREADY read as titles – the whole run space-joined, or that run’s last word – whilemaiden_markersis matched by lookahead, longest first, over words that need not be markers on their own ("z domu"). Field docs below show examples, not full contents; inspect any field’s shipped vocabulary directly, e.g.Lexicon.default().conjunctions.- given_name_titles: frozenset[str]¶
Titles whose single following name reads as a GIVEN name (“sheikh”, “sister”, …) rather than a family name. Full default list:
GIVEN_NAME_TITLES.
- suffix_acronyms: frozenset[str]¶
Post-nominal acronym suffixes, matched with or without periods (“phd” matches “PhD” and “Ph.D.”). Full default list:
SUFFIX_ACRONYMS.
- suffix_words: frozenset[str]¶
Post-nominal word suffixes (“jr”, “esquire”, “iii”, …). Full default list:
SUFFIX_WORDS.
- suffix_acronyms_ambiguous: frozenset[str]¶
Subset of suffix_acronyms whose bare forms are also common surnames (“ma”, “do”). Written with periods one is a suffix outright; written bare, capitals decide in a mixed-case name (“Jack MA” is the credential, “Jack Ma” keeps his family name) and otherwise the word counts as a suffix only where a name word is left beside it (“John Smith ma”), rules.md#S2. Full default list:
SUFFIX_ACRONYMS_AMBIGUOUS.
- particles: frozenset[str]¶
Family-name particles that chain onto the following piece (“van”, “de”, “bin”, …). Full default list:
PARTICLES.
- particles_ambiguous: frozenset[str]¶
Subset of particles that can also BE a given name (“Van Johnson”, but also “Van Buren”). Membership decides nothing about chaining: the prefix chain skips the name’s first piece and never consults this set, so it leaves a leading particle a piece of its own whether listed or not – “de Mesnil” groups into two pieces exactly as “van Gogh” does, and since #367 the NAME in “Dr. de Mesnil” and “Dr. Van Johnson” groups into those same two pieces behind the title piece, a title not being part of the name it precedes. What membership decides is what becomes of that piece afterwards. Under ANY
name_ordera member records a particle-or-given ambiguity and a non-member records none, and a non-member is additionally folded back into the family name once roles exist WHERE IT OPENS THE NAME, so the whole name is the surname (“de Mesnil” – a bare “de”, with nothing to fold into, is left alone). That fold is order-independent too (#359) – not because the word could not be a given name, which it can where position forces it (parse("de")reports given “de”), but because a particle OPENING the name has the rest of the name to join forward to, and that evidence is positional rather than vocabulary, so no declared order contradicts it. Which field a MEMBER’s piece lands in isname_order’s question, not this set’s. No constant of its own – the default derives as particles minusNON_GIVEN_NAME_PARTICLES(which marks the opposite, never-given subset).
- conjunctions: frozenset[str]¶
Words or characters that join surrounding pieces into one (“and”, “&”, “y”, “и”, …). Full default list:
CONJUNCTIONS.
- conjunctions_ambiguous: frozenset[str]¶
Subset of conjunctions that read as an INITIAL rather than a connective in a name written wholly in one case (“e”: “jose e maria santos” reads middle “e maria”, where “juan garcia y lopez” joins because “y” is not a member). Mixed-case input is decided by the writing instead and never consults this set, and neither does a caseless letter (Arabic و), which has no case to read. A member additionally reports
CONJUNCTION_OR_INITIAL; a non-member reports nothing, its reading not being in doubt. Full default list:CONJUNCTIONS_AMBIGUOUS. Entries need not also be inconjunctions; one that is not is simply never consulted, so it is inert rather than an error.
- bound_given_names: frozenset[str]¶
Given-name prefixes that bind to the following word to form one given name (“abdul” -> “Abdul Salam”); never standalone names. Full default list:
BOUND_GIVEN_NAMES.
- maiden_markers: frozenset[str]¶
Marker words introducing a birth surname, routed to the maiden field (“née”, “geb.”, “rozená”, …). An entry may be a PHRASE (“z domu”): entries are matched by lookahead, longest first, so a phrase wins where it matches and a word entry that starts one still matches on its own everywhere else. A phrase matches only where its words stand together – a bracketed clause or a comma between them ends the run. Full default list:
MAIDEN_MARKERS.
- surnames: frozenset[str]¶
Family names for the unspaced-name segmentation stage (#271), matched longest-first against the start of the FIRST token written wholly in a script
Policy.segment_scriptsactivates. The default carries the Korean census list (KOREAN_SURNAMES); Chinese surnames ship in locales.ZH because Han segmentation is opt-in.
- honorific_tails: frozenset[str]¶
Honorifics that may be peeled off the END of a name token (#308), matched longest-first: 田中さん splits into 田中 and さん before the tokens are classified. Every entry must also be a
suffix_wordsentry – the peeled tail is claimed by suffix classification like any other post-nominal. Deliberately NOT gated onPolicy.segment_scripts(unlikesurnamesabove): 田中さん peels under the default policy, where HAN is in no activation set, because a tail entry carries its own license to fire. Entries are matched against the RAW token text, and only within a name containing a non-ASCII character, so an ASCII or mixed-case entry is at best conditionally active – a"Jr"entry is stored"jr"and matches only lowercase text. The field is effectively CJK-scoped in 2.1, which is what the shipped vocabulary is. Full default list:GLUED_HONORIFICS.
- class nameparser.Policy(name_order: tuple[Role, Role, Role] = (Role.GIVEN, Role.MIDDLE, Role.FAMILY), script_orders: tuple[tuple[Script, tuple[Role, Role, Role]], ...] = ((Script.HAN, (Role.FAMILY, Role.GIVEN, Role.MIDDLE)), (Script.HANGUL, (Role.FAMILY, Role.GIVEN, Role.MIDDLE)), (Script.HIRAGANA, (Role.FAMILY, Role.GIVEN, Role.MIDDLE))), segment_scripts: frozenset[Script] = frozenset({Script.HANGUL}), patronymic_rules: frozenset[PatronymicRule] = frozenset({}), middle_as_family: bool = False, nickname_delimiters: frozenset[tuple[str, str]] = frozenset({('"', '"'), ("'", "'"), ('(', ')'), ('«', '»'), ('»', '«'), ('“', '”'), ('”', '”'), ('„', '“'), ('「', '」'), ('『', '』'), ('(', ')'), ('「', '」')}), maiden_delimiters: frozenset[tuple[str, str]] = frozenset({}), extra_suffix_delimiters: frozenset[str] = frozenset({}), lenient_comma_suffixes: bool = True, strip_emoji: bool = True, strip_bidi: bool = True, unlisted_dotted_suffixes: bool = True, unlisted_caps_suffixes: CapsSuffixes = CapsSuffixes.AFTER_COMMA)[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. 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 the content is suffix-shaped, which is taken first, the brackets dropped and the content 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 always splits suffix groups and cannot be replaced – comma handling is structural (the same comma reading that parses “Family, Given” input), not a configurable delimiter.
- lenient_comma_suffixes: bool¶
Governs “Family, Suffix”-shaped input where the suffix word is also initial-shaped (a single letter, bare or period-written – of the default vocabulary that means the roman numerals “I” and “V”): “John Smith, V” reads as John Smith the fifth when True (the default, v1 behavior); False reads “V” as a given-name initial instead (family “John Smith”, given “V”). Multi-letter suffixes (“III”, “MD”) parse the same either way.
- strip_emoji: bool¶
Excludes emoji from tokenization: they appear in no token, field, or rendered view. The original string keeps them (input is never modified – spans stay true).
- strip_bidi: bool¶
Excludes bidirectional control characters from tokenization: they appear in no token, field, or rendered view; the original string keeps them.
- 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.).Falsereads such a token as name material everywhere, as 2.3 did for a token no chunk claimed; the roman-chunk retirement (rules.md#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”).
- 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)[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.
- 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. Enabled by locales.RU.
- TURKIC = 'turkic'¶
Turkic patronymic markers: a standalone “oglu”/”qizi”/”kyzy” (etc.) binds to the preceding name as a patronymic. Enabled by locales.TR_AZ.
- class nameparser.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). 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())[source]¶
A named, shareable bundle of vocabulary and behavior for a naming tradition: a lexicon fragment plus a policy patch, applied together by
nameparser.parser_for(). The packs shipped with nameparser live 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 ADDED to the base parser’s lexicon (unioned; a pack never removes base vocabulary).
- policy: PolicyPatch¶
Behavior changes folded onto the base policy (set-valued fields union; scalars override, later pack wins).
Locale packs: named (lexicon fragment, PolicyPatch) deltas folded in by parser_for (mechanisms.md#LOCALE-PACKS-PURE-DATA). Packs are pure data with no privileged capabilities; they dissolve at parser construction.
Loaded lazily (PEP 562): importing nameparser.locales never imports a
pack module; locales.RU triggers nameparser.locales.ru on
first access and caches the Locale here.
Layering: this package imports pack modules, which import _locale/ _lexicon/_policy only (enforced by tests/v2/test_layering.py).
- nameparser.locales.available() tuple[str, ...][source]¶
The registered locale codes, lowercase, sorted.
- nameparser.locales.get(code: str) Locale[source]¶
Dynamic lookup by code (‘ru’); raises KeyError listing the available codes.
- nameparser.locales.ja_segmenter(*, gbdt: bool = False) Segmenter[source]¶
The Japanese segmenter factory (#272), needing the optional
pip install nameparser[ja]; seenameparser.locales.jafor what it wraps, what it declines, and its licensing.A real function rather than a _REGISTRY entry, because the registry resolves LOCALES: __getattr__ is annotated to return one, so routing a callable through it would type every pack access as “Locale or Segmenter” for no gain. Laziness is preserved here instead – the pack module (and with it the optional dependency) loads on the call, not on importing this package.
1.x compatibility layer¶
Note
HumanName and nameparser.config are the 1.x API, kept
working through 2.x and removed in 3.0. New code should use the
2.0 API above; see Migrating from HumanName.
HumanName.parser¶
- class nameparser.parser.HumanName[source]
- class nameparser.parser.HumanName(full_name: str = '', constants: Constants | None = <Constants : [ prefixes: 75 suffix_acronyms: 609 suffix_not_acronyms: 155 titles: 798 first_name_titles: 49 conjunctions: 15 bound_first_names: 12 non_first_name_prefixes: 35 suffix_acronyms_ambiguous: 7 ]>, string_format: str | None = None, initials_format: str | None = None, initials_delimiter: str | None = None, initials_separator: str | None = None, suffix_delimiter: str | None = None, first: str | list[str] | None = None, middle: str | list[str] | None = None, last: str | list[str] | None = None, title: str | list[str] | None = None, suffix: str | list[str] | None = None, nickname: str | list[str] | None = None, maiden: str | list[str] | None = None)[source]¶
v1
HumanNamefacade: a mutable wrapper over a frozenParsedName, delegating all parsing to the coreParserresolved from the boundConstantsshim (dirty-tracked via its_generation). Keeps every v1 spelling (first/lastover the coregiven/family); the v1 parsing hooks are never called (#280). Deleted with the facade layer in 3.0.- __init__(full_name: str = '', constants: Constants | None = <Constants : [ prefixes: 75 suffix_acronyms: 609 suffix_not_acronyms: 155 titles: 798 first_name_titles: 49 conjunctions: 15 bound_first_names: 12 non_first_name_prefixes: 35 suffix_acronyms_ambiguous: 7 ]>, string_format: str | None = None, initials_format: str | None = None, initials_delimiter: str | None = None, initials_separator: str | None = None, suffix_delimiter: str | None = None, first: str | list[str] | None = None, middle: str | list[str] | None = None, last: str | list[str] | None = None, title: str | list[str] | None = None, suffix: str | list[str] | None = None, nickname: str | list[str] | None = None, maiden: str | list[str] | None = None) None[source]¶
- parse_full_name() None[source]¶
Re-parse the stored
full_name(v1’s documented re-parse trigger, docs/customize.rst): mutatename.Cthen call this to force a re-parse without reassigningfull_name. The v1 parsing INTERNALS this name evokes live in the coreParser, not here; a subclass overriding this method still triggers the #280 hook-override warning, and full_name assignment never consults it.
- capitalize(force: bool | None = None) None[source]¶
Re-capitalize the current parse against the bound lexicon. force=None reads the bound Constants’ render default (force_mixed_case_capitalization); the core’s capitalized() implements the one-case gate (rules.md#R5, suffixes left out since #492) – not re-implemented here.
- property has_own_config: bool¶
True when this instance is not using the shared module-level CONSTANTS.
- matches(other: str | HumanName) bool[source]¶
Component-wise case-insensitive comparison (v1 parity); a str argument is parsed with this instance’s resolved parser.
HumanName.config¶
v1 import-path preservation (mechanisms.md#CONFIG-SHIM-SNAPSHOT): the Constants shim lives in nameparser._config_shim.
Two unrelated things share this package. The names re-exported below –
Constants, CONSTANTS, SetManager, TupleManager,
RegexTupleManager – are v1 compatibility surface, and go with the
rest of the facade in 3.0. The vocabulary data modules beside them
(titles, particles,
…) are not compatibility surface at all: they are the word lists the
2.0 Lexicon is built from, they are named for its
fields since 2.2 (#293), and its documentation cross-references them.
RegexTupleManager is re-exported unchanged from the shim purely for
pickle compatibility: a v1.4 Constants blob’s regexes field was
pickled as nameparser.config.RegexTupleManager(...), and unpickling
resolves and reconstructs that nested object before
Constants.__setstate__ ever runs. Without this name, loading such a
blob raises AttributeError looking up the class, not a clean
compatibility failure.
- class nameparser.config.Constants(*, prefixes: Iterable[str] | object = <object object>, suffix_acronyms: Iterable[str] | object = <object object>, suffix_not_acronyms: Iterable[str] | object = <object object>, suffix_acronyms_ambiguous: Iterable[str] | object = <object object>, titles: Iterable[str] | object = <object object>, first_name_titles: Iterable[str] | object = <object object>, conjunctions: Iterable[str] | object = <object object>, bound_first_names: Iterable[str] | object = <object object>, non_first_name_prefixes: Iterable[str] | object = <object object>, capitalization_exceptions: Mapping[str, str] | ~collections.abc.Iterable[tuple[str, str]] | object=<object object>, regexes: object = <object object>, patronymic_name_order: bool = False, middle_name_as_last: bool = False)[source]¶
v1
Constantsshim: a mutable container whose state resolves to a frozen(Lexicon, Policy, _RenderDefaults)snapshot via_snapshot()._generationincrements on every mutation; facades compare it against a cached value to decide whether their snapshot is stale (dirty-tracking – the facade side lives in nameparser/_facade.py).The module-level
CONSTANTSsingleton (below) has_sharedflipped toTrue: any mutation reached through it emitsDeprecationWarningpointing atLexicon/PolicyandHumanName(constants=...). A privateConstants()never warns – only the shared instance is on the 3.0 removal path.
- class nameparser.config.RegexTupleManager(*args: object, _on_change: Callable[[], None] | None = None, **kwargs: object)[source]¶
Pickle-compat alias only: v1.4’s
Constants.regexesfield was anameparser.config.RegexTupleManagerinstance (aTupleManagersubclass whose__getattr__fell back toEMPTY_REGEXfor an unknown key). Unpickling a v1.4 blob resolves and constructs this class – viaTupleManager.__reduce__’s(type(self), (), state)– beforeConstants.__setstate__runs, so the name must exist here even though the shim’sConstants._snapshot()never reads it:regexesis a read-only_RegexesProxyin 2.0 (see above), andConstants.__setstate__below deliberately ignores an incomingregexeskey rather than restoring it from this reconstructed (and otherwise unused) instance.
- class nameparser.config.SetManager(elements: Iterable[str] = (), _on_change: Callable[[], None] | None = None, _field: str | None = None)[source]¶
v1
SetManagersurface over a plain set oflc()-normalized strings. Mutations call_on_change(the owning Constants’ generation bump, wired by the facade).__call__and the missing-member-tolerantremove()are gone per the #243 schedule (warned 1.3.0, removed 2.0):remove()of a missing member raisesKeyError, matchingset.remove.- add(*strings: str) SetManager[source]¶
Add the normalized string arguments to the set. Returns
selffor chaining.
- remove(*strings: str) SetManager[source]¶
Remove the normalized string arguments from the set. Raises
KeyErrorif any argument is not a member. Returnsselffor chaining.
- discard(*strings: str) SetManager[source]¶
Remove the normalized string arguments from the set if present; missing members are ignored, like
set.discard. Returnsselffor chaining.
- clear() SetManager[source]¶
Remove all entries from the set. Returns
selffor chaining.
- class nameparser.config.TupleManager(*args: object, _on_change: Callable[[], None] | None = None, **kwargs: object)[source]¶
v1
TupleManager: a dict with dot-notation access. Backscapitalization_exceptions. Unknown-key attribute access raisesAttributeErrornaming the key (#256, warned 1.4, enforced 2.0 – the v1DeprecationWarningis gone, this shim only speaks 2.0). Mutations call_on_change(the owning Constants’ generation bump, wired by the facade).- pop(k[, d]) v, remove specified key and return the corresponding value.[source]¶
If the key is not found, return the default if given; otherwise, raise a KeyError.
- popitem() tuple[str, object][source]¶
Remove and return a (key, value) pair as a 2-tuple.
Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.
HumanName.config Defaults¶
- nameparser.config.titles.GIVEN_NAME_TITLES = frozenset({'aunt', 'auntie', 'baba', 'brother', 'cheikh', 'dame', 'father', 'guru', 'king', 'lama', 'maid', 'master', 'mother', 'pope', 'prince', 'princess', 'queen', 'shaik', 'shaikh', 'shayk', 'shaykh', 'sheik', 'sheikh', 'shekh', 'sir', 'sister', 'swami', 'uncle', 'أستاذ', 'أستاذة', 'الأستاذ', 'الأستاذة', 'الحاج', 'الحاجة', 'الدكتور', 'الدكتورة', 'الشيخ', 'الشيخة', 'دكتور', 'دكتورة', 'مهندس', 'गुरु', 'बाबा', 'संत', 'स्वामी', 'গুরু', 'বাবা', 'শ্রীল', 'স্বামী'})¶
When these titles appear with a single other name, that name is a given name, e.g. “Sir John”, “Sister Mary”, “Queen Elizabeth”.
- nameparser.config.titles.TITLES = frozenset({'10th', '1lt', '1sgt', '1st', '1stlt', '1stsgt', '2lt', '2nd', '2ndlt', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', 'a1c', 'ab', 'abbess', 'abbot', 'abolitionist', 'academic', 'acolyte', 'activist', 'actor', 'actress', 'adept', 'adjutant', 'adm', 'administrator', 'admiral', 'advertising', 'adviser', 'advocate', 'air', 'akhoond', 'alderman', 'almoner', 'ambassador', 'amn', 'analytics', 'anarchist', 'animator', 'anthropologist', 'appellate', 'apprentice', 'arbitrator', 'archbishop', 'archdeacon', 'archdruid', 'archduchess', 'archduke', 'archeologist', 'architect', 'arhat', 'army', 'arranger', 'assistant', 'assoc', 'associate', 'asst', 'astronomer', 'attache', 'attaché', 'attorney', 'aunt', 'auntie', 'author', 'award-winning', 'ayatollah', 'baba', 'bailiff', 'ballet', 'bandleader', 'banker', 'banner', 'bard', 'baron', 'baroness', 'barrister', 'baseball', 'batoni', 'bearer', 'behavioral', 'bench', 'bg', 'bgen', 'biblical', 'bibliographer', 'binibini', 'biochemist', 'biographer', 'biologist', 'bishop', 'blessed', 'blogger', 'blues', 'bodhisattva', 'bookseller', 'botanist', 'bp', 'brigadier', 'briggen', 'british', 'broadcaster', 'brother', 'buddha', 'burgess', 'burlesque', 'business', 'businessman', 'businesswoman', 'bwana', 'canon', 'capt', 'captain', 'cardinal', 'cartographer', 'cartoonist', 'catholicos', 'ccmsgt', 'cdr', 'celebrity', 'ceo', 'cfo', 'chair', 'chairs', 'chancellor', 'chaplain', 'charge', 'chargé', 'chef', 'cheikh', 'chemist', 'chief', 'chieftain', 'choreographer', 'civil', 'classical', 'clergyman', 'clerk', 'cmsaf', 'cmsgt', 'co-chair', 'co-chairs', 'co-founder', 'coach', 'col', 'collector', 'colonel', 'comedian', 'comedienne', 'comic', 'commander', 'commander-in-chief', 'commissioner', 'commodore', 'composer', 'compositeur', 'comptroller', 'computer', 'comtesse', 'conductor', 'consultant', 'controller', 'corporal', 'corporate', 'correspondent', 'councillor', 'counsel', 'counselor', 'count', 'countess', 'courtier', 'cpl', 'cpo', 'cpt', 'credit', 'criminal', 'criminologist', 'critic', 'csm', 'curator', 'customs', 'cwo-2', 'cwo-3', 'cwo-4', 'cwo-5', 'cwo2', 'cwo3', 'cwo4', 'cwo5', 'cyclist', "d'affaires", 'dame', 'dancer', 'dcn', 'deacon', 'delegate', 'deputy', 'designated', 'designer', 'detective', 'developer', 'dhr', 'dipl.-ing', 'diplomat', 'dir', 'director', 'discovery', 'dissident', 'district', 'division', 'doamna', 'doamnă', 'docent', 'docket', 'doctor', 'domnișoara', 'domnul', 'doyen', 'dpty', 'dr', 'dra', 'dramatist', 'druid', 'drummer', 'duchesse', 'dutchess', 'ecologist', 'economist', 'editor', 'edler', 'edmi', 'edohen', 'educator', 'effendi', 'ekegbian', 'elerunwon', 'eminence', 'emperor', 'empress', 'encik', 'engineer', 'english', 'ens', 'entertainer', 'entrepreneur', 'envoy', 'erzbischof', 'essayist', 'evangelist', 'excellency', 'excellent', 'exec', 'executive', 'expert', 'fadm', 'familie', 'family', 'father', 'federal', 'fh-prof', 'field', 'film', 'financial', 'first', 'flag', 'flying', 'foreign', 'forester', 'founder', 'fr', 'frau', 'freifrau', 'freiherr', 'friar', 'frk', 'fru', 'fräulein', 'fröken', 'frøken', 'frú', 'fürst', 'fürsterzbischof', 'gaf', 'gen', 'general', 'generalissimo', 'gentiluomo', 'giani', 'ginang', 'ginoo', 'goodman', 'goodwife', 'gospodin', 'gospođa', 'gospođica', 'governor', 'graaf', 'graf', 'grafas', 'grafi', 'grand', 'greifi', 'greve', 'group', 'großfürst', 'gräfin', 'guitarist', 'guru', 'gyani', 'gysgt', 'hajji', 'headman', 'heir', 'heiress', 'her', 'hereditary', 'heren', 'herr', 'herra', 'herren', 'herrn', 'herzog', 'high', 'highness', 'his', 'historian', 'historicus', 'historien', 'holiness', 'hon', 'honorable', 'honourable', 'host', 'hr', 'hrabia', 'härra', 'illustrator', 'imam', 'industrialist', 'information', 'instructor', 'intelligence', 'intendant', 'inventor', 'investigator', 'investor', 'journalist', 'journeyman', 'judge', 'judicial', 'juffrouw', 'jurist', 'kalbatoni', 'keyboardist', 'king', "king's", 'kingdom', 'knight', 'knowledge', 'krahv', 'kreivi', 'lady', 'lama', 'lamido', 'law', 'lawyer', 'lcdr', 'lcpl', 'leader', 'lecturer', 'legal', 'librarian', 'lieutenant', 'linguist', 'literary', 'lord', 'lt', 'ltc', 'ltcol', 'ltg', 'ltgen', 'ltjg', 'lyricist', 'madam', 'madame', 'mademoiselle', 'mag', 'mag-judge', 'mag/judge', 'magistrate', 'magistrate-judge', 'magnate', 'maharajah', 'maharani', 'mahdi', 'maid', 'maj', 'majesty', 'majgen', 'manager', 'marcher', 'marchess', 'marchioness', 'marketing', 'marquess', 'marquis', 'marquise', 'master', 'mathematician', 'mathematics', 'matriarch', 'mayor', 'mcpo', 'mcpoc', 'mcpon', 'md', 'me', 'member', 'memoirist', 'meneer', 'merchant', 'met', 'metropolitan', 'mevr', 'mevrouw', 'mevrouwe', 'mg', 'mgr', 'mgysgt', 'military', 'minister', 'miss', 'misses', 'missionary', 'mister', 'mlle', 'mme', 'mobster', 'model', 'monk', 'monseigneur', 'monsieur', 'monsignor', 'most', 'mother', 'mountaineer', 'mpco-cg', 'mr', 'mrs', 'ms', 'msg', 'msgt', 'mufti', 'mullah', 'municipal', 'murshid', 'musician', 'musicologist', 'mx', 'mystery', 'nanny', 'narrator', 'national', 'naturalist', 'navy', 'neiti', 'neuroscientist', 'novelist', 'nurse', 'obstetritian', 'officer', 'opera', 'operating', 'ornithologist', 'painter', 'paleontologist', 'paní', 'pastor', 'patriarch', 'pd', 'pediatrician', 'personality', 'petty', 'pfc', 'pharaoh', 'philantropist', 'philosopher', 'photographer', 'physician', 'physicist', 'pianist', 'pilot', 'pioneer', 'pir', 'player', 'playwright', 'po1', 'po2', 'po3', 'poet', 'police', 'political', 'politician', 'ponas', 'ponia', 'pope', 'prefect', 'preili', 'prelate', 'premier', 'pres', 'presbyter', 'president', 'presiding', 'priest', 'priestess', 'primate', 'prime', 'prin', 'prince', 'princess', 'principal', 'printer', 'printmaker', 'prinz', 'prior', 'priv.-doz', 'private', 'pro', 'producer', 'prof', 'professor', 'proua', 'provost', 'pslc', 'psychiatrist', 'psychologist', 'publisher', 'pursuivant', 'pv2', 'pvt', 'queen', "queen's", 'ra', 'rabbi', 'radio', 'radm', 'rangatira', 'ranger', 'rdml', 'rear', 'rebbe', 'registrar', 'reichsgraf', 'rep', 'representative', 'researcher', 'resident', 'rev', 'revenue', 'reverend', 'right', 'risk', 'ritter', 'rock', 'rouva', 'royal', 'rt', 'sa', 'sailor', 'saint', 'sainte', 'saoshyant', 'satirist', 'scholar', 'schoolmaster', 'scientist', 'scpo', 'screenwriter', 'secretary', 'security', 'seigneur', 'senator', 'senhor', 'senhora', 'senhorita', 'senior', 'senior-judge', 'sergeant', 'servant', 'señor', 'señora', 'señores', 'señorita', 'señoritas', 'sfc', 'sgm', 'sgt', 'sgtmaj', 'sgtmajmc', 'shaik', 'shaikh', 'shayk', 'shaykh', 'sheik', 'sheikh', 'shekh', 'sheriff', 'siddha', 'signor', 'signora', 'signore', 'signorina', 'singer', 'singer-songwriter', 'sir', 'sister', 'slečna', 'sma', 'smsgt', 'sn', 'soccer', 'social', 'sociologist', 'software', 'soldier', 'solicitor', 'soprano', 'spc', 'speaker', 'special', 'sr', 'sra', 'sres', 'srta', 'srtas', 'ssg', 'ssgt', 'st', 'staff', 'state', 'states', 'strategy', 'subaltern', 'subedar', 'suffragist', 'sultan', 'sultana', 'superior', 'supreme', 'surgeon', 'swami', 'swordbearer', 'sysselmann', 'tax', 'teacher', 'technical', 'technologist', 'television', 'tenor', 'theater', 'theatre', 'theologian', 'theorist', 'timi', 'tirthankar', 'translator', 'travel', 'treasurer', 'tsar', 'tsarina', 'tsgt', 'uk', 'uncle', 'ungfrú', 'united', 'univ.prof', 'us', 'vadm', 'vardapet', 'vc', 'venerable', 'verderer', 'vicar', 'vice', 'viscount', 'vizier', 'vocalist', 'voice', 'vrouwe', 'warden', 'warrant', 'wing', 'wm', 'wo-1', 'wo1', 'wo2', 'wo3', 'wo4', 'wo5', 'woodman', 'wp', 'writer', 'zoologist', 'δρ', 'κα', 'καθ', 'κος', 'акад', 'г-жа', 'г-н', 'д-р', 'пан', 'пані', 'проф', "גב'", 'גברת', 'גב׳', 'ד"ר', 'ד״ר', 'הרב', 'מר', 'עו"ד', 'עו״ד', "פרופ'", 'פרופסור', 'פרופ׳', 'أستاذ', 'أستاذة', 'الأستاذ', 'الأستاذة', 'الحاج', 'الحاجة', 'الدكتور', 'الدكتورة', 'الشيخ', 'الشيخة', 'دكتور', 'دكتورة', 'مهندس', 'गुरु', 'जनाब', 'डा', 'डॉ', 'डॉक्टर', 'न्यायमूर्ति', 'पं', 'पंडित', 'प्रा', 'प्राध्यापक', 'प्रो', 'प्रोफेसर', 'बाबा', 'बाबू', 'महात्मा', 'महाराजा', 'मौलाना', 'श्री', 'श्रीमती', 'श्रीमान', 'श्रीयुत', 'संत', 'सरदार', 'सुश्री', 'सौ', 'स्वामी', 'অধ্যাপক', 'আলহাজ', 'আলহাজ্ব', 'গুরু', 'জনাব', 'ড', 'ডঃ', 'ডক্টর', 'ডা', 'ডাঃ', 'ডাক্তার', 'প্রফেসর', 'বাবা', 'বিচারপতি', 'মাওলানা', 'মি', 'মিঃ', 'মিসেস', 'মুফতি', 'মো', 'মোঃ', 'মোছা', 'মোছাঃ', 'মোসা', 'মোসাঃ', 'শ্রী', 'শ্রীমতী', 'শ্রীল', 'স্বামী'})¶
Cannot include things that could also be given names, e.g. “dean”. Many of these from wikipedia: https://en.wikipedia.org/wiki/Title. The parser recognizes chains of these including conjunctions allowing recognition titles like “Deputy Secretary of State”.
- nameparser.config.suffixes.SUFFIX_WORDS = frozenset({'2', 'eldri', 'esq', 'esquire', 'i', 'ii', 'iii', 'iv', 'jaunesnioji', 'jaunesnysis', 'jaunākais', 'jaunākā', 'jnr', 'jr', 'junior', 'mladšia', 'mladší', 'mlajša', 'mlajši', 'mlađa', 'mlađi', 'nuorempi', 'ret', 'rinpoche', 'snr', 'sr', 'starejša', 'starejši', 'starija', 'stariji', 'staršia', 'starší', 'v', 'vanhempi', 'vecākais', 'vecākā', 'vet', 'vyresnioji', 'vyresnysis', 'yngri', 'ƙarami', 'σίνιορ', 'τζούνιορ', 'малодшая', 'малодшы', 'мл', 'млади', 'младша', 'младшая', 'младши', 'младший', 'млађа', 'млађи', 'молодша', 'молодший', 'помладиот', 'постариот', 'стари', 'старија', 'старији', 'старша', 'старшая', 'старши', 'старший', 'старэйшая', 'старэйшы', 'կրտսեր', "ג'וניור", 'ג׳וניור', 'ג’וניור', 'האב', 'הבן', 'ז"ל', 'ז״ל', 'שליט"א', 'שליט״א', 'الأب', 'الإبن', 'الابن', 'جونئیر', 'جونيئر', 'جونيور', 'جونیئر', 'جونیور', 'سينيور', 'سینئر', 'سینیئر', 'سینیور', 'जी', 'जुनियर', 'जूनियर', 'ज्युनियर', 'ज्यूनियर', 'महाराज', 'साहब', 'साहिब', 'साहेब', 'सीनियर', 'কনিষ্ঠ', 'জুনিয়র', 'জুনিয়ৰ', 'জ্যেষ্ঠ', 'বাবু', 'মহারাজ', 'সাহেব', 'সিনিয়র', 'ਜੂਨੀਅਰ', 'ਸੀਨੀਅਰ', 'જુનિયર', 'જૂનિયર', 'સિનિયર', 'ଜୁନିଅର', 'ଜୁନିୟର୍', 'இளையவர்', 'ஜூனியர்', 'மூத்தவர்', 'జూనియర్', 'సీనియర్', 'ಜೂನಿಯರ್', 'ജൂനിയർ', 'സീനിയർ', 'ජූනියර්', 'จูเนียร์', 'ซีเนียร์', 'ຈູເນຍ', 'ဂျူနီယာ', 'ၵျူႇၼီႇယႃႇ', 'უმცროსი', 'უფროსი', 'ጁኒየር', 'ጁንየር', 'ជូនៀរ', 'くん', 'さま', 'さん', 'ちゃん', 'ジュニア', '先生', '博士', '女士', '小姐', '教授', '様', '殿', '氏', '교수님', '군', '님', '박사', '박사님', '선생님', '시니어', '씨', '양', '주니어'})¶
Post-nominal suffixes matched as WORDS: the lookup uses the normalized token, so only EDGE periods come off and interior ones survive – “Junior.” matches here, “J.u.n.o.r.” does not and stays name text (“John J.u.n.o.r.” parses a family name, on both APIs). The example is deliberately not “J.u.n.i.o.r.”, which fails this lookup too: an interior-period token that no whole-token set claims goes to
period_joined_vocab, which splits it on its periods and, no chunk being a title, calls the whole thing a suffix if ANY chunk is suffix vocabulary – except where every chunk it matches is a SINGLE ASCII CHARACTER, which since 2.4 retires rather than claims (decisions.md#S2): the one-character suffix vocabulary is the Roman numerals and the digit “2” – ASCII is load-bearing there, since the seven single-character CJK honorifics in THIS set (様, 殿, 氏, 군, 님, 씨, 양 – three of them also glued-honorific heads below) must KEEP their chunk claim (“J.씨” still derives from 씨) – and a word built of single ASCII letters is not about generations. So “J.u.n.i.o.r.” no longer reaches this set’s “i” entry as a generational claim; it is read by POSITION instead, as an unlisted multi-chunk word (Policy.unlisted_dotted_suffixes, default on), and with words to spare it is still a suffix (rules.md#S3). A multi-character chunk match is untouched – “Msc.Ed.” still derives fromedand reads as a suffix outright, and “Lt.Gov.” still derives a TITLE from its own multi-character chunk match. So membership here is not the last word on a dotted token; the sentence is about this set’s lookup alone.SUFFIX_ACRONYMSis the set matched with every period removed, so it alone covers a multi-dot spelling: both “P.H.D.” and the bare “PhD” reach its phd entry, the two normalizing to the same string once the periods come off. The two sets are asserted DISJOINT (see the guard block at the bottom): a post-nominal belongs to one of them or the other, and which one holds it is what decides whether its multi-dot spelling reaches a whole-token lookup at all –period_joined_vocabmay still claim the token chunk by chunk, as “Msc.Ed.” and “Lt.Gov.” above show – or read it by position instead, as “J.u.n.i.o.r.” now does.
- nameparser.config.suffixes.GLUED_HONORIFICS = frozenset({'くん', 'さま', 'さん', 'ちゃん', '先生', '女士', '小姐', '教授', '様', '교수님', '님', '박사', '박사님', '선생님', '씨'})¶
The subset of
SUFFIX_WORDSa name token may end WITH, peeled off as its own token before segmentation (#308). Deliberately harsher than the spaced set, because a glued tail has no writer-drawn token boundary to lean on – these entries are recognized in the SPACED position only:양, 군 – 김지양 and 김지군 are given names ending in these syllables, and 양 is a top-tier surname besides.
氏 – 王氏 is a historical name form (“the Wang woman”).
博士 – glued 田中博士 IS Tanaka Hiroshi, an attested given name.
殿 – some ninety Japanese surnames END in it, 鵜殿 (Udono) and 真殿 (Madono) with four-figure populations, so peeling it would cut a real family name in two. Spaced 殿 is safe for the reason 양/군 are: a 殿-surnamed person’s name LEADS, and the suffix gate is trailing-only.
The Indic trailing set (जी, साहब, साहिब, साहेब, महाराज, সাহেব, বাবু, মহারাজ) and Latin rinpoche are spaced-only for the same reason 殿 is: जी ends Banerjee/Mukherjee/Chatterjee (बनर्जी, मुखर्जी, चटर्जी) and a glued peel would strand बनर् on a bare virama. The criterion is not CJK-specific, which is the point (decisions.md#indic-honorifics).
Three more are in NEITHER set, so neither spelling is recognized. 君: 王君 is a complete Chinese name (君 is a common given-name final), so the honorific reading never gets the benefit of the doubt – while its kana spelling くん ships glued, above. Bare 선생 and 교수: they read as common nouns as readily as address terms, and only their -님 forms ship.
- nameparser.config.suffixes.SUFFIX_ACRONYMS_AMBIGUOUS = frozenset({'ba', 'do', 'ed', 'jd', 'lac', 'ma', 'meng'})¶
Acronym suffixes from SUFFIX_ACRONYMS that also plausibly collide with a word borne as a name – a given name, nickname or surname. Not a partition of SUFFIX_ACRONYMS – a small, standalone exception list, read wherever the pipeline decides an acronym’s bare reading (rules.md#S2).
- nameparser.config.suffixes.SUFFIX_ACRONYMS = frozenset({'8-vsb', 'aas', 'aba', 'abc', 'abd', 'abpp', 'abr', 'aca', 'acas', 'ace', 'acha', 'acp', 'ae', 'aem', 'afasma', 'afc', 'afm', 'agsf', 'aia', 'aicp', 'ala', 'alc', 'alp', 'am', 'amd', 'ame', 'amieee', 'ams', 'aphr', 'apn', 'apr', 'aprn', 'apss', 'aqp', 'arm', 'arrc', 'asa', 'asc', 'asid', 'asla', 'asp', 'atc', 'awb', 'ba', 'bca', 'bcl', 'bcss', 'bds', 'bem', 'bls-i', 'bn', 'bpe', 'bpi', 'bpt', 'bsc', 'bt', 'btcs', 'bts', 'cacts', 'cae', 'caha', 'caia', 'cams', 'cap', 'capa', 'capm', 'capp', 'caps', 'caro', 'cas', 'casp', 'cb', 'cbe', 'cbm', 'cbne', 'cbnt', 'cbp', 'cbrte', 'cbs', 'cbsp', 'cbt', 'cbte', 'cbv', 'cca', 'ccc', 'ccca', 'cccm', 'cce', 'cchp', 'ccie', 'ccim', 'cciso', 'ccm', 'ccmt', 'ccna', 'ccnp', 'ccp', 'ccp-c', 'ccpr', 'ccs', 'ccufc', 'cd', 'cdal', 'cdfm', 'cdmp', 'cds', 'cdt', 'cea', 'ceas', 'cebs', 'ceds', 'ceh', 'cela', 'cem', 'cep', 'cera', 'cet', 'cfa', 'cfc', 'cfcc', 'cfce', 'cfcm', 'cfe', 'cfeds', 'cfi', 'cfm', 'cfp', 'cfps', 'cfr', 'cfre', 'cga', 'cgap', 'cgb', 'cgc', 'cgfm', 'cgfo', 'cgm', 'cgma', 'cgp', 'cgr', 'cgsp', 'ch', 'chba', 'chdm', 'che', 'ches', 'chfc', 'chi', 'chmc', 'chmm', 'chp', 'chpa', 'chpe', 'chpln', 'chpse', 'chrm', 'chsc', 'chse', 'chse-a', 'chsos', 'chss', 'cht', 'cia', 'cic', 'cie', 'cig', 'cip', 'cipm', 'cips', 'ciro', 'cisa', 'cism', 'cissp', 'cla', 'clsd', 'cltd', 'clu', 'cm', 'cma', 'cmas', 'cmc', 'cmfo', 'cmg', 'cmp', 'cms', 'cmsp', 'cmt', 'cna', 'cnm', 'cnp', 'cp', 'cp-c', 'cpa', 'cpacc', 'cpbe', 'cpcm', 'cpcu', 'cpe', 'cpfa', 'cpfo', 'cpg', 'cph', 'cpht', 'cpim', 'cpl', 'cplp', 'cpm', 'cpo', 'cpp', 'cppm', 'cprc', 'cpre', 'cprp', 'cpsc', 'cpsi', 'cpss', 'cpt', 'cpwa', 'crde', 'crisc', 'crma', 'crme', 'crna', 'cro', 'crp', 'crt', 'crtt', 'csa', 'csbe', 'csc', 'cscp', 'cscu', 'csep', 'csi', 'csm', 'csp', 'cspo', 'csre', 'csrte', 'csslp', 'cssm', 'cst', 'cste', 'ctbs', 'ctfa', 'cto', 'ctp', 'cts', 'cua', 'cusp', 'cva', 'cva[22]', 'cvo', 'cvp', 'cvrs', 'cwap', 'cwb', 'cwdp', 'cwep', 'cwna', 'cwne', 'cwp', 'cwsp', 'cxa', 'cyds', 'cysa', 'dabfm', 'dabvlm', 'dacvim', 'dbe', 'dc', 'dcb', 'dcm', 'dcmg', 'dcvo', 'dd', 'dds', 'ded', 'dep', 'dfc', 'dfm', 'diplac', 'diplom', 'djur', 'dma', 'dmd', 'dmin', 'dnp', 'do', 'dpm', 'dpt', 'drb', 'drmp', 'drph', 'dsc', 'dsm', 'dso', 'dss', 'dtr', 'dvep', 'dvm', 'ea', 'ed', 'edd', 'ei', 'eit', 'els', 'emd', 'emt-b', 'emt-i/85', 'emt-i/99', 'emt-p', 'enp', 'erd', 'evp', 'faafp', 'faan', 'faap', 'fac-c', 'facc', 'facd', 'facem', 'facep', 'facha', 'facofp', 'facog', 'facp', 'facph', 'facs', 'faia', 'faicp', 'fala', 'fashp', 'fasid', 'fasla', 'fasma', 'faspen', 'fca', 'fcas', 'fcela', 'fd', 'fec', 'fhames', 'fic', 'ficf', 'fieee', 'fmp', 'fmva', 'fnss', 'fp&a', 'fp-c', 'fpc', 'frm', 'fsa', 'fsdp', 'fws', 'gaee[14]', 'gba', 'gbe', 'gc', 'gcb', 'gchs', 'gcie', 'gcmg', 'gcsi', 'gcvo', 'gisp', 'git', 'gm', 'gmb', 'gmr', 'gphr', 'gri', 'grp', 'gsmieee', 'hccp', 'hrs', 'iaccp', 'iaee', 'iccm-d', 'iccm-f', 'idsm', 'ifgict', 'iom', 'ipep', 'ipm', 'iso', 'issp-csp', 'issp-sa', 'itil', 'jd', 'jp', 'kbe', 'kcb', 'kchs/dchs', 'kcie', 'kcmg', 'kcsi', 'kcvo', 'kg', 'khs/dhs', 'knt', 'kp', 'kt', 'lac', 'lcmt', 'lcpc', 'lcsw', 'lg', 'litk', 'litl', 'litp', 'llm', 'lm', 'lmsw', 'lmt', 'lp', 'lpa', 'lpc', 'lpn', 'lpss', 'lsi', 'lsit', 'lt', 'lvn', 'lvo', 'lvt', 'ma', 'maaa', 'mai', 'mba', 'mbe', 'mbs', 'mc', 'mcct', 'mcdba', 'mches', 'mcm', 'mcp', 'mcpd', 'mcsa', 'mcsd', 'mcse', 'mct', 'md', 'mda', 'mdb', 'mdbb', 'mdep', 'mdhb', 'mdiv', 'mdl', 'mem', 'meng', 'mfa', 'micp', 'mieee', 'mirm', 'mle', 'mls', 'mlse', 'mlt', 'mm', 'mmad', 'mmas', 'mnaa', 'mnae', 'mp', 'mpa', 'mph', 'mpse', 'mra', 'ms', 'msa', 'msc', 'mscmsm', 'msm', 'mt', 'mts', 'mvo', 'nbc-his', 'nbcch', 'nbcch-ps', 'nbcdch', 'nbcdch-ps', 'nbcfch', 'nbcfch-ps', 'nbct', 'ncarb', 'nccp', 'ncidq', 'ncps', 'ncso', 'ncto', 'nd', 'ndtr', 'nmd', 'np', 'np[18]', 'nraemt', 'nremr', 'nremt', 'nrp', 'obe', 'obi', 'oca', 'ocm', 'ocp', 'od', 'om', 'oscp', 'ot', 'pa-c', 'pcc', 'pci', 'pe', 'pfmp', 'pg', 'pgmp', 'pharmd', 'phc', 'phd', 'phr', 'phrca', 'pla', 'pls', 'pmc', 'pmi-acp', 'pmp', 'pp', 'pps', 'prm', 'psm', 'psp', 'psyd', 'pt', 'pta', 'qam', 'qc', 'qcsw', 'qfsm', 'qgm', 'qpm', 'qsd', 'qsp', 'ra', 'rba', 'rci', 'rcp', 'rd', 'rdcs', 'rdh', 'rdms', 'rdn', 'res', 'rfp', 'rhca', 'rid', 'rls', 'rmsks', 'rn', 'rp', 'rpa', 'rph', 'rpl', 'rrc', 'rrt', 'rrt-accs', 'rrt-nps', 'rrt-sds', 'rtrp', 'rvm', 'rvt', 'sa', 'same', 'sasm', 'sccp', 'scmp', 'se', 'secb', 'sfp', 'sgm', 'shrm-cp', 'shrm-scp', 'si', 'siie', 'smieee', 'sphr', 'sscp', 'stb', 'stmieee', 'tbr-ct', 'td', 'thd', 'thm', 'ud', 'usa', 'usaf', 'usar', 'uscg', 'usmc', 'usn', 'usnr', 'uxc', 'uxmc', 'vc', 'vcp', 'vd', 'vrd'})¶
Post-nominal acronyms. Titles, degrees and other things people stick after their name that may or may not have periods between the letters. The parser removes periods when matching against these pieces.
- nameparser.config.particles.NON_GIVEN_NAME_PARTICLES = frozenset({"'t", 'af', 'auf', 'av', 'bint', 'das', 'de', "de'", 'degli', 'dei', 'delle', 'delli', 'dello', 'dem', 'der', 'dos', 'het', 'ibn', 'las', 'los', 'mc', 'ní', 'op', 'ste', 'ter', 'vd', 'vom', 'zu', 'ó', 'בן', 'בת', 'آل', 'ابن', 'بن', 'بنت'})¶
The sub-set of
PARTICLESthat no culture uses as a standalone given name. Where one of these stands ALONE as the piece opening a name, that name has no given name – the whole thing is a surname (e.g. “de Mesnil” -> family name “de Mesnil”) – and that reading holds under EVERYname_order(#359). It is not scoped to the default order the way the rest of the positional read is: what settles it is that a particle opening the name has the rest of the name to JOIN FORWARD TO, which is evidence about the writing rather than about the word, soPolicy(name_order=FAMILY_FIRST)reads “de Mesnil” as the family name too. Membership here does NOT mean the word can never be a given name – where position forces the reading it is one, and every member of this set reports given “de”-style for a bare parse. What is asked about is the opening piece, not the first word of the string: a particle that has already chained onto the word behind it is part of that piece rather than standing alone. OPENING the name is the whole of that rule’s subject (rules.md#P1; the pre-2.2 docstrings called it rule 1b). A member standing alone where a declared order puts the GIVEN name was a second site until #467, and is not one now: that slot holds what the caller declared, soPolicy(name_order=FAMILY_FIRST)reads “Mesnil de” as family “Mesnil”, given “de”. A member ENDING the name under a family-first order is rules.md#P6’s, which joins it to the family. A bare “de” stays as it is. Where a chain is reported as the given name anyway it is because the member is no longer standing alone: underPolicy(name_order=FAMILY_FIRST)the given position of “Juan de la Vega” holds the whole three-token chain, so the rule declines and given “de la Vega” stands. A title in front is NOT such a case – since #367 a title is transparent to the leading-particle exception, so “Sir de Mesnil” leaves “de” a lone piece and reads family “de Mesnil”, exactly as the untitled form does. Membership also decides the ambiguity report – seePARTICLESbelow. Curated to exclude anything that can be a given name in some culture (al, van, von, della, di, del, da, vander, …) and anything that is also a bound given-name particle (abu). When unsure, leave a word out: a missing member just means that name is not auto-fixed, whereas a wrong member misparses a real person. Must stay a subset ofPARTICLESand disjoint fromBOUND_GIVEN_NAMES.
- nameparser.config.bound_given_names.BOUND_GIVEN_NAMES: frozenset[str] = frozenset({'abd', 'abdal', 'abdel', 'abdul', 'abou', 'abu', 'umm', 'أبو', 'أم', 'ابو', 'ام', 'عبد'})¶
Bound Arabic given-name prefixes that attach to the following word to form one given name (e.g. “abdul salam smith” → given name “abdul salam”). They are never standalone names. The join is a group-stage rule on the FIRST non-title piece, so it is not about roles – it fires whatever name_order later assigns. It reserves a piece for what follows: three pieces that are neither title nor suffix in a main segment – counting the bound word’s OWN piece even where that word is also suffix vocabulary, since it is the piece the rule has claimed rather than one left to spare – which is why two-word “abdul salam” stays given “abdul” plus family “salam”; only two after a family comma, where the family name is already fixed (“salam, abdul rahman” → given “abdul rahman”). Mirrors
PARTICLES, which chains onto the piece that follows it.
- nameparser.config.conjunctions.CONJUNCTIONS = frozenset({'&', 'and', 'e', 'et', 'i', 'of', 'the', 'und', 'y', 'και', 'и', 'й', 'та', 'і', 'و'})¶
Pieces that should join to their neighboring pieces, e.g. “and”, “y” and “&”. “of” and “the” are also include to facilitate joining multiple titles, e.g. “President of the United States”.
- nameparser.config.conjunctions.CONJUNCTIONS_AMBIGUOUS = frozenset({'e', 'i'})¶
Single-letter entries of
CONJUNCTIONSthat read as an initial, not a connective, in a name written wholly in one case.
- nameparser.config.maiden_markers.MAIDEN_MARKERS = frozenset({'född', 'fødd', 'født', 'geb', 'geboren', 'geborene', 'nee', 'né', 'née', 'rozená', 'z domu', 'урожд', 'урожденная', 'урожденный', 'урождённая', 'урождённый', '旧姓'})¶
Markers that introduce a birth surname, e.g. “Jane Smith née Jones” (#274). French née/né/nee, German geb./geborene, Dutch geboren, Czech/Slovak rozená, Danish/Norwegian født (Nynorsk fødd), Swedish född, Russian урожд./урождённая/урождённый (both ё and е spellings — case normalization does not fold them, and running text routinely writes е), and Polish “z domu” (#434). Both grammatical genders are listed where #274 or review attested them (née/né, урождённая/урождённый); Czech masculine rozený awaits the same vetting, and would be vetted on its own merits rather than following the feminine form in — the abbreviation roz. shared by both genders is deliberately absent, below. Entries are stored normalized: lowercase, no periods.
An entry may be a PHRASE, and “z domu” (“of the house”) is the first one shipped. Phrases are stored space-joined and folded per word, and matched by lookahead, longest first, over a run of whole tokens that stand TOGETHER – a phrase divided by a bracketed clause or a comma is not that phrase, so “Anna z (domu) Nowak” keeps its family name just as “Anna z Nowak” does. That mechanism is what makes this entry safe where its words are not: z is an ordinary Polish preposition and domu an ordinary noun, and the whole-phrase claim never fires on either alone. Splitting a phrase into its words is what the library used to advise (the dead-entry warning, which no longer fires for this field), and it produced exactly the misreadings the phrase avoids: maiden “domu Nowak” for “Maria Kowalska z domu Nowak”, and no family name at all for “Anna z Nowak”. A caller who added z and domu separately should remove both and let the shipped entry work.
Japanese 旧姓 is here rather than in locales.JA, on the rule that admitted the Cyrillic entries: a native-script marker cannot collide with a Latin-script name, and matching is whole-token, so it is safe as a default. (Whole-token is still the rule for a phrase entry: it claims a run of whole tokens, one per word, and no part of one.) Neither character appears in any shipped surname, title, suffix, conjunction, particle or bound-given vocabulary. locales.JA is for what needs the my-data-is-Japanese declaration – segmentation, where a pure-Han string cannot say which language wrote it – and this needs none, since it can only ever match Han text.
Matching being whole-token, the marker has to BE a token – which for Japanese means something has to divide it from the name it marks. A space does, and so does a delimiter: extract masks the whole bracketed region, delimiter characters included, before tokenize runs, so a bracket bounds a token exactly as a space does and “山田(旧姓 佐藤)” needs no space in front of 旧姓 at all. The bare “山田花子 旧姓 佐藤” and the bracketed “山田 花子(旧姓 佐藤)” alike give maiden 佐藤, and since #335 the bracketed form needs no configuration to do it: the fullwidth pair is a NICKNAME delimiter by default, and rules.md#M3 reads a clause that opens with a marker word AND carries a word after it as the maiden name, whichever bucket its pair sits in. The second word is part of the condition, not a detail of it – a lone “(旧姓)” is a word in brackets and stays a nickname. #329, which drops the marker from inside the clause, is what makes the value 佐藤 rather than “旧姓 佐藤”. Declaring the pair in Policy(maiden_delimiters=…) reaches the same reading by M1’s path. What divides nothing is the fullwidth colon that the form Japanese more often writes puts after the marker: “山田(旧姓:佐藤)” under Policy(maiden_delimiters=…) still yields maiden “旧姓:佐藤” with the marker and its colon attached. Not because delimited content escapes classification – classify tags a marker wherever it is a token – but because : is no separator tokenize knows, so marker and name arrive as ONE token and there is nothing to drop. M3 does not reach that form either, and by its own test rather than by tokenize’s: it splits the clause on WHITESPACE, and “旧姓:佐藤” is one whitespace word, so there is no marker word for the clause to open with and by default it stays a nickname. The two tests agree here and are deliberately not the same test – see decisions.md#M3. The wholly unspaced “山田花子(旧姓佐藤)” reads as one token for the same reason. Peeling a marker off the head of a token is #317’s job.
Consumed by the 2.0 parser’s default lexicon. The 1.x parser does not read this module.
Deliberately absent: the Scandinavian abbreviation “f.” (collides with the initial “F.” — only the full participles are safe), and the Czech/Slovak abbreviation “roz.” (shipped through 2.1 and removed in 2.2). Roz is an ordinary English diminutive of Rosalind, and matching is whole-token, case-folded and period-insensitive, so “Roz”, “roz” and “roz.” are one string to this set: with it shipped, “Rosalind Roz Smith” read maiden “Smith” and NO family name at all. The collision is in the position the claim acts on, which is the test decisions.md#vocabulary-collisions states, and this set has no ambiguous subset to express a partial answer in — see decisions.md’s Excluded (MAIDEN_MARKERS) block. The full participle rozená stays, being unambiguous; a caller who needs the abbreviation adds it to their own Lexicon.
- nameparser.config.surnames.KOREAN_SURNAMES = frozenset({'가', '강', '경', '계', '고', '공', '곽', '구', '국', '권', '금', '기', '길', '김', '나', '남', '남궁', '노', '도', '독고', '동방', '두', '류', '마', '망절', '맹', '명', '모', '목', '문', '민', '박', '반', '방', '배', '백', '변', '복', '봉', '부', '사', '사공', '서', '서문', '석', '선', '선우', '설', '성', '소', '손', '송', '신', '심', '안', '양', '엄', '여', '연', '염', '예', '오', '옥', '왕', '용', '우', '원', '위', '유', '육', '윤', '은', '이', '인', '임', '장', '전', '정', '제', '제갈', '조', '주', '지', '진', '차', '채', '천', '최', '추', '탁', '태', '편', '표', '피', '하', '한', '함', '허', '현', '형', '홍', '황', '황보'})¶
Korean surnames (#271), used by the 2.0 API’s unspaced-name segmentation (
Lexicon.default().surnames): a hangul token like “김민준” splits into surname + given name by longest match. This ships as DEFAULT vocabulary because it is self-selecting – a hangul entry can only ever match hangul text – and hangul text is unambiguously Korean. Chinese surnames deliberately live innameparser.locales.zhinstead (Han segmentation is opt-in; a zh list corrupts Japanese kanji names).Source: the 2015 South Korean census surname tables – the most common single-syllable surnames (Kim/Lee/Park alone cover ~45% of the population) plus the two-syllable surnames in current use. A coverage floor, not the complete census roster: extend with
Lexicon.default().add(surnames={...}).Consumed by the 2.0 parser’s default lexicon. The 1.x parser does not read this module.
- nameparser.config.capitalization.CAPITALIZATION_EXCEPTIONS = {'bsc': 'BSc', 'bt': 'Bt', 'chfc': 'ChFC', 'cpht': 'CPhT', 'diplac': 'DiplAc', 'dmin': 'DMin', 'drph': 'DrPH', 'dsc': 'DSc', 'knt': 'Knt', 'kt': 'Kt', 'mdiv': 'MDiv', 'msc': 'MSc', 'ní': 'Ní', 'pharmd': 'PharmD', 'phc': 'PhC', 'phd': 'PhD', 'psyd': 'PsyD', 'rph': 'RPh', 'thd': 'ThD', 'thm': 'ThM', 'ua': 'Ua', 'ó': 'Ó'}¶
Words whose case
str.capitalize()gets wrong, each mapped to a case MASK: the key’s own letters and digits, each in the case it should take. Case repair lays the mask over the word as it was written and keeps every other character where the writer put it, so'phd': 'PhD'repairsphdtoPhDandph.d.toPh.D.. The mask’s own punctuation marks where its letters are joined into one run versus split apart, and is never written into the word – repair keeps the writer’s own punctuation. A value that does not spell its key’s letters and digits is aValueErrorwhen aLexiconis built from it. An acronym the suffix vocabulary already lists, written plainly or in dotted form, and a roman numeral need no entry: case repair writes a suffix of either kind in capitals by itself. The map carries most of the listed acronyms whose conventional spelling is NOT all capitals (DSc,PsyD,PharmD), sopsydrepairs toPsyDrather thanPSYD. The exception is an acronym that is also a name word (meng,edd): a mask applies in every role, so it gets none, andjohn smith eddrepairs toEDD(decisions.md#R4’s Excluded block for CAPITALIZATION_EXCEPTIONS – meng, edd, lac, ded). A suffix the writer spelled in more than one case (EdD) needs no entry either: repair keeps it as written unless forced. A caller’s OWN acronym – one absent fromsuffix_acronyms– depends on how it is written. Written PLAINLY (dphil) it parses as an ordinary name word and repairs as one (Dphil). Written DOTTED (d.phil.) it is a suffix by shape alone, with no vocabulary entry needed to read it as one, and repairs in all capitals the same as a listed acronym does (D.PHIL.). Either way, a caller who wants a specific spelling needs asuffix_acronymsentry (Lexicon.add(suffix_acronyms={...})in the 2.0 API,constants.suffix_acronyms.add(...)in the v1 one) or a mask of its own.
- nameparser.config.regexes.REGEXES = {'bidi': re.compile('[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]+'), 'commas': re.compile('[,،,]'), 'double_quotes': re.compile('\\"(.*?)\\"'), 'east_slavic_patronymic': re.compile('(ovich|ovna|evich|evna|ichna|ilyich|kuzmich|lukich|fomich|fokich)$', re.IGNORECASE), 'east_slavic_patronymic_cyrillic': re.compile('(ович|овна|евич|евна|ична|ильич|кузьмич|лукич|фомич|фокич)$', re.IGNORECASE), 'emoji': re.compile('[🌀-🙏🚀-\U0001f6ff☀-⛿✀-➿]+'), 'initial': re.compile('^(\\w\\.|[A-Z])?$'), 'mac': re.compile('^(ma?c)(\\w{2,})', re.IGNORECASE), 'parenthesis': re.compile('\\((.*?)\\)'), 'period_abbreviation': re.compile('^(?:[^\\W\\d_][̀-ͯ᪰-\u1aff᷀-᷿⃐-\u20ff︠-︯]*){2,}\\.$'), 'period_not_at_end': re.compile('.*\\..+$', re.IGNORECASE), 'phd': re.compile('\\s(ph\\.?\\s+d\\.?)', re.IGNORECASE), 'quoted_word': re.compile("(?<!\\w)\\'([^\\s]*?)\\'(?!\\w)"), 'roman_numeral': re.compile('^(X|IX|IV|V?I{0,3})$', re.IGNORECASE), 'space_before_comma': re.compile('\\s+,'), 'spaces': re.compile('\\s+'), 'turkic_patronymic_marker': re.compile("^(oglu|oğlu|ogly|ogli|o['’ʻ]g['’ʻ]li|qizi|qızı|kizi|kyzy|gyzy|uly|uulu)$", re.IGNORECASE), 'turkic_patronymic_marker_cyrillic': re.compile('^(оглу|оглы|оғлу|ўғли|угли|кызы|гызы|қызы|қизи|улы|ұлы|уулу)$', re.IGNORECASE), 'word': re.compile('(\\w|\\.)+')}¶
All regular expressions used by the parser are precompiled and stored in the config.