Using the parser¶
Requires Python 3.11+. pip install nameparser
Parse a name¶
>>> from nameparser import parse
>>> name = parse("Dr. Juan Q. Xavier de la Vega III")
>>> name.given, name.family
('Juan', 'de la Vega')
>>> name.title, name.middle, name.suffix
('Dr.', 'Q. Xavier', 'III')
A parsed name has seven fields: title, given, middle,
family, suffix, nickname, and maiden. Parsing never
raises; unparseable input yields a ParsedName with
empty fields plus any ambiguities the parser noticed along the way
(see When the parser had to guess below, and How the parser works for why
they exist). A name with no fields set is falsy, which is how you tell
“nothing parsed” from “parsed to something”:
>>> bool(parse("")), bool(parse(" ")), bool(parse("John"))
(False, False, True)
Input shapes¶
Three arrangements are understood, and every piece of each is optional:
Title Given "Nickname" Middle Middle Family SuffixFamily [Suffix], Title Given (Nickname) Middle Middle[,] Suffix [, Suffix]Title Given Middle Family [Suffix], Suffix [, Suffix]
The last two differ in what the comma is doing. In form 2 it separates the family name from the rest, so the family name comes first; in form 3 it only sets off suffixes, and the name before it is still given-then-family:
>>> parse("de la Vega, Juan Q. Xavier III").family # form 2
'de la Vega'
>>> parse("Doe Jr., John").suffix # form 2, suffix before the comma
'Jr.'
>>> parse("John Doe, Jr.").family # form 3
'Doe'
For family-first input without a comma — common outside Europe — set
name_order; see Customizing the parser.
Words that attach to their neighbors¶
Input shapes tell you where the fields sit. This tells you which words merge into one field instead of standing alone — between them, that is most of what decides a parse.
Most words stand alone. A few pull in a neighbor, and each kind pulls into one particular field — so this table is also a map of which fields get built by attachment rather than by position. Titles and suffixes attach only to adjacent words of their own kind (a run of titles chains into one, but a title never swallows a plain name); the rest reach forward to pull in the next word, whatever it is. Follow a row’s name to its full vocabulary set:
Words |
Attach to |
Field |
Example |
|---|---|---|---|
adjacent titles |
|
|
|
adjacent suffixes |
|
|
|
the following word |
|
|
|
the following surname |
|
|
|
the following name |
|
|
|
the words on both sides |
any |
|
Conjunctions are the exception the last row names: they take the field
of whatever sits on both sides, so the same and pulls two given
names together as easily as two surnames:
>>> parse("John and Jane Smith").given
'John and Jane'
>>> parse("Juan de la Vega y Rodriguez").family
'de la Vega y Rodriguez'
Position matters in exactly one place: the start of a name. A particle there has no surname to attach to yet, so it either becomes the given name or turns the whole name into a surname, depending on whether it is one that can double as a given name:
>>> parse("van Gogh").given # 'van' can be a given name
'van'
>>> parse("de Mesnil").given # 'de' cannot
''
>>> parse("de Mesnil").family
'de Mesnil'
Customizing the parser covers how to change which words are in each of these sets, including which particles may double as given names.
Aggregate views¶
>>> name.given_names # given + middle
'Juan Q. Xavier'
>>> name.family_base, name.family_particles # family, split apart
('Vega', 'de la')
surnames (middle + family) is the mirror-image aggregate.
The plural is the tell: given and family are single fields,
while given_names and surnames roll several fields together —
the same sense in which a passport form asks for your “given names” as
one blank that can hold more than one word.
family_base is the one to sort on. Dutch and Belgian directories
file a name under the base surname and ignore the tussenvoegsel — the
van, de or van der in front of it — and the same convention
applies wherever particles are common:
>>> names = [parse(s) for s in
... ["Vincent van Gogh", "Juan de la Vega", "John Smith"]]
>>> [n.family_base for n in sorted(names, key=lambda n: n.family_base.lower())]
['Gogh', 'Smith', 'Vega']
Sorting on family instead files those under “d”, “S” and “v”, which
is the problem the split exists to solve:
>>> [n.family for n in sorted(names, key=lambda n: n.family.lower())]
['de la Vega', 'Smith', 'van Gogh']
Dicts and strings¶
>>> name.as_dict(include_empty=False)
{'title': 'Dr.', 'given': 'Juan', 'middle': 'Q. Xavier', 'family': 'de la Vega', 'suffix': 'III'}
>>> str(name)
'Dr. Juan Q. Xavier de la Vega III'
>>> name.render("{family}, {given}")
'de la Vega, Juan'
>>> name.initials()
'J. Q. X. V.'
Fixing case¶
>>> str(parse("juan de la vega").capitalized())
'Juan de la Vega'
Nicknames and maiden names¶
>>> parse("Jonathan 'Jack' Kennedy").nickname
'Jack'
>>> parse("Jane Smith née Jones").maiden
'Jones'
Both fields appear in the default str() rendering — the nickname
quoted after the given name, the maiden name parenthesized after the
family name:
>>> str(parse("Jane (Janie) Smith née Jones"))
'Jane "Janie" Smith (Jones)'
That default is built for display, and the two fields differ in what survives it. The quoted nickname reparses as a nickname; the parenthesized maiden name reparses as a nickname too, so a parse-render-reparse round trip silently loses it:
>>> parse('Jane "Janie" Smith (Jones)').maiden
''
>>> parse('Jane "Janie" Smith (Jones)').nickname
'Janie Jones'
If the rendered string has to survive a reparse — storing names as text and reading them back, for instance — render the marker explicitly instead of relying on the default:
>>> name = parse("Jane Smith née Jones")
>>> text = name.render("{given} {family} née {maiden}")
>>> text
'Jane Smith née Jones'
>>> parse(text).maiden
'Jones'
Delimited content is not always a nickname. If what’s inside is a known suffix, or simply ends in a period, it is read as a suffix instead — parenthesized credentials and retired ranks are far more common than parenthesized nicknames that happen to be credentials:
>>> parse("Andrew Perkins (MBA)").suffix
'MBA'
>>> parse("Andrew Perkins (Ret.)").suffix
'Ret.'
The exception to that exception is an ambiguous acronym — one that is also a plausible name or set of initials. Standing alone inside delimiters, it keeps the nickname reading:
>>> parse("JEFFREY (JD) BRICKEN").nickname
'JD'
JD is in suffix_acronyms_ambiguous; see Customizing the parser for
what that field marks and how to add to it.
Titles you didn’t configure¶
A leading word that ends in a period is read as a title even when it is in no vocabulary list, which is what lets unfamiliar ranks, honorifics and abbreviations work without configuring anything:
>>> parse("Major. Dona Smith").title
'Major.'
>>> parse("Foo. Xyz. John Smith").title # chains
'Foo. Xyz.'
The rule is bounded in three ways, so it doesn’t swallow ordinary names. Single initials are left alone, so are abbreviations with interior periods, and the rule only applies to the leading run — the same word after the given name is a middle name:
>>> parse("J. Smith").given
'J.'
>>> parse("E.T. Jones").given
'E.T.'
>>> parse("John Major. Smith").middle
'Major.'
Because this is structural rather than vocabulary-driven, emptying
titles does not switch it off; see Customizing the parser.
Comparing names¶
== is strict value equality — two ParsedName
instances are equal only if every field matches exactly. For “is this
the same name, allowing for order and case?” use matches() or
comparison_key() instead.
>>> parse("de la Vega, Juan").matches("Juan de la Vega")
True
>>> parse("JUAN DE LA VEGA").comparison_key() == parse("Juan de la Vega").comparison_key()
True
When the parser had to guess¶
Some names have no single correct reading. "Van Johnson" could be
the given name Van, or the family-name particle van. 2.0 takes
the more likely reading and records the choice on ambiguities
rather than deciding silently:
>>> name = parse("Van Johnson")
>>> name.given, name.family
('Van', 'Johnson')
>>> for a in name.ambiguities:
... print(a.kind.value, "-", a.detail)
particle-or-given - leading 'Van' may be a family-name particle; read as a given name
AmbiguityKind members are their own string values,
so branching on a kind needs no import:
>>> name.ambiguities[0].kind == "particle-or-given"
True
>>> [t.text for t in name.ambiguities[0].tokens]
['Van']
The post-nominals that double as ordinary surnames report the same way.
MA after a full name is read as a credential, but after a single
given name it stays the surname — either way the choice is recorded:
>>> parse("John Smith MA").suffix
'MA'
>>> [a.kind.value for a in parse("John Smith MA").ambiguities]
['suffix-or-name']
>>> parse("Jack MA").family
'MA'
A reading the vocabulary settles on its own is not a guess and reports
nothing — periods make M.A. unambiguously a credential:
>>> parse("John Smith M.A.").ambiguities
()
Most names report none. A non-empty ambiguities is a useful signal
for routing a record to human review instead of trusting it silently.
Tokens and spans¶
The seven fields are the convenient view. Underneath, each one is backed by tokens carrying their exact offsets into the original string, so you can always get back to the text a field came from:
>>> name = parse("Juan de la Vega")
>>> for tok in name.tokens:
... print(f"{tok.text!r:8} {tok.role.value:8} {tuple(tok.span)}")
'Juan' given (0, 4)
'de' family (5, 7)
'la' family (8, 10)
'Vega' family (11, 15)
>>> tok = name.tokens[1]
>>> name.original[tok.span.start:tok.span.end]
'de'
tokens_for() narrows that to a single role:
>>> from nameparser import Role
>>> [t.text for t in name.tokens_for(Role.FAMILY)]
['de', 'la', 'Vega']
Correcting a parse¶
ParsedName is immutable, so a correction is a new
value: replace() returns a copy with the given fields changed and
everything else carried over.
>>> name = parse("Juan de la Vega")
>>> corrected = name.replace(title="Dr.")
>>> str(corrected)
'Dr. Juan de la Vega'
>>> name.title
''
Command line¶
python -m nameparser parses one name and prints what it found. It
is the quickest way to see how a particular string comes out — worth
reaching for when a name parsed unexpectedly and you want to check a
variation of it, or when trying a locale pack before wiring one into
code:
$ python -m nameparser "dr. juan de la vega iii"
Parsed:
<ParsedName: [
title: 'dr.'
given: 'juan'
family: 'de la vega'
suffix: 'iii'
]>
Capitalized:
<ParsedName: [
title: 'Dr.'
given: 'Juan'
family: 'de la Vega'
suffix: 'III'
]>
Initials: j. v.
--json prints the fields as a single line instead, which is the
form to pipe somewhere:
$ python -m nameparser --json "Doe, John"
{"title": "", "given": "John", "middle": "", "family": "Doe", "suffix": "", "nickname": "", "maiden": ""}
Add --locale to parse with a locale pack (for example --locale
ru); see Locale packs.
Where next¶
How the parser works — how the parser works
Customizing the parser — your own vocabulary and behavior
Locale packs — locale packs
Migrating from HumanName — coming from 1.x
HumanName