normalize.py
Unicode normalization, visual and phonetic substitutions, and configurable string matching.
Setup and relationship to toolbox
Use import normalize with this file on the import path. Its text functions use the standard library. If pmblue_update is already importable, the module calls self_update(primary_mod=True) at import time; if it is absent, normalization still loads without bootstrapping it. The current file does not declare __version__.
toolbox.Matching contains a related, newer copy of this functionality. The standalone module does not include toolbox's Matching.Methods.levenshtein_local, and behavior is not identical in every option combination. Import the intended module explicitly.
Examples
import normalize
print(normalize.convert_to_normal("Héllô")) # hello
print(normalize.convert_to_single_letters("heeellooo")) # helo
print(normalize.Matching.match("Héllô", "hello")) # True
settings = normalize.Matching.Settings(ignore_spaces=True)
print(normalize.Matching.match("Main St", "mainst", settings)) # True
print(normalize.Matching.within("Main St", "10 Main Street")) # True
print(normalize.Matching.fuzzy_match("12345", "12344", threshold=.8)) # True
Normalization functions
| Function | Behavior / return |
|---|---|
word_to_digit(word) | Case-insensitive English words zero through twelve become integers; unsupported words return None. |
name_to_normal(name) | Maps a Unicode character-name string (for example, a value from unicodedata.name()) to a custom replacement or None. It does not format a person's name. |
convert_to_normal(txt) | Applies NFKD decomposition and custom Unicode-name mappings, drops unsupported character names, replaces dollar signs with s, and returns lowercase text. |
convert_to_single_letters(txt) | Collapses adjacent duplicate characters. It does not collapse nonadjacent repeats or transliterate text. |
equivilency_convert(txt) | Applies ordered, aggressive phonetic substitutions to lowercase text. The misspelling is part of the public function name. |
Normalization is lossy. Its visual substitutions deliberately merge some letters, digits, and symbols; preserve original input separately when displaying or storing user names.
Matching settings
settings = normalize.Matching.Settings(
normalize=True,
extraneous_characters=False,
ignore_spaces=False,
match_asterisk=False,
fuzzy_threshold=1,
phonetic=False,
)
Matching.match(a, b, settings=None) compares complete transformed strings; Matching.within(needle, text, settings=None) checks containment. Both return booleans. The shared Matching.default_settings is used when no settings object is passed.
- Apply
convert_to_normalwhen normalization is enabled. - Remove ordinary spaces if
ignore_spaces=True. - Lowercase and apply the fixed substitutions
0→o,@→a,$→s,1→i,z→2,+→t, even whennormalize=False. - Collapse consecutive duplicates when
extraneous_characters=True; punctuation is otherwise retained unless normalization removes it. - Apply phonetic substitutions when enabled.
- Compare exactly at
fuzzy_threshold=1, or use positional fuzzy comparison at another threshold.
Fuzzy and wildcard behavior
Matching.fuzzy_match(a, b, threshold=.8) compares corresponding lowercase characters and returns whether the fraction that match reaches the threshold. It skips normalization. fuzzy_within(needle, text, threshold=.8) applies that comparison to each same-length window in the second string.
An asterisk is a single-position wildcard for exact match(). It is not a glob spanning arbitrary text. In this standalone version, match_asterisk=True with a fuzzy threshold below 1 still calls ordinary fuzzy_match() without wildcard masking. For within(), the wildcard branch builds paired strings with zip(), which truncates the longer input; it does not search all possible wildcard offsets.
Input limits
- Pass strings. Inputs and thresholds are not validated or automatically coerced.
- Low-level fuzzy matching divides by compared length; an empty input can raise
ZeroDivisionError. - Fuzzy and wildcard comparisons stop at the shorter input, so unmatched trailing characters can be ignored.
- These fuzzy functions count matching positions rather than edit distance; an insertion can shift every subsequent character.
- Unsupported Unicode names may disappear, and unusual number-name inputs can trigger errors in the legacy mapping rules.
- Use separate Settings instances instead of mutating the shared default for unrelated callers.
Settings and function contracts
Matching is a namespace: call its functions on the class, for example Matching.match(a, b), rather than constructing Matching(). Matching.Settings is the configurable data object. Its six public attributes have the same names as the constructor arguments, and the matching functions read them when called. Creating a Settings object has no file or network side effects.
| Argument | How to choose it |
|---|---|
normalize=True | Use custom Unicode transliteration before comparing. Turn off to retain original Unicode characters; the high-level comparison still lowercases and applies the fixed substitutions. |
extraneous_characters=False | Enable only when adjacent repeated characters may be ignored, such as hellooo versus helo. It does not mean ignore arbitrary punctuation. |
ignore_spaces=False | When enabled, removes ordinary ASCII spaces, not every Unicode whitespace character. |
match_asterisk=False | Enable for positional masking and account for the wildcard limits documented above. |
fuzzy_threshold=1 | The exact path uses full transformed-string equality or containment. Other values use a fraction of equal character positions; provide a number between 0 and 1 yourself. |
phonetic=False | Enable ordered sound-like substitutions. These can collapse distinct names, so evaluate results against your own examples. |
match(txt1, txt2, settings=None) interprets both inputs as complete values. within(txt1, txt2, settings=None) interprets the first as the search term and the second as the containing text. fuzzy_match and fuzzy_within accept a numeric threshold, not a Settings object. Every comparison returns a boolean; these APIs do not return a similarity score or a list of positions.
Worked example: find candidates while preserving originals
This builds a candidate list using normalized containment and keeps the original strings for display. The settings object is dedicated to this search.
from normalize import Matching
names = ["Héloise Martin", "Ada Lovelace", "HELOISE DUPONT"]
search_settings = Matching.Settings(normalize=True, ignore_spaces=True)
query = "heloise"
matches = [name for name in names if Matching.within(query, name, search_settings)]
print(matches) # ['Héloise Martin', 'HELOISE DUPONT']
exact_settings = Matching.Settings(normalize=True, ignore_spaces=False)
print(Matching.match("Héloise", "HELOISE", exact_settings)) # True
print(Matching.match("Héloise", "Héloise Martin", exact_settings)) # False
Worked example: constrain positional fuzzy comparison
Check nonempty equal lengths before calling the low-level fuzzy function when trailing suffixes must count as different values. This guard handles two important limitations without changing the module.
from normalize import Matching
def similar_code(first, second, threshold=0.8):
if not first or not second or len(first) != len(second):
return False
return Matching.fuzzy_match(first, second, threshold=threshold)
print(similar_code("AB123", "AB124")) # True: 4 of 5 positions match
print(similar_code("AB123", "AB1234")) # False: lengths differ
print(similar_code("", "")) # False: no division by zero
masked = Matching.Settings(normalize=False, match_asterisk=True)
print(Matching.match("AB*23", "AB123", masked)) # True
Inspecting a Unicode mapping
Pass the Unicode name rather than the character to name_to_normal. For example, name_to_normal(unicodedata.name("€")) returns "e". The full convert_to_normal function also decomposes composed accents before invoking its custom name rules. A successful mapping is a comparison aid rather than a reversible encoding.
Complete source API
Generated from modules/normalize.py; no module version is declared. Includes public functions, classes, directly declared methods, properties, and Python protocol methods. Conditional APIs may require optional dependencies. Inherited members and dynamically assigned attributes are explained in the guide where relevant.
Signatures and docstrings are extracted without importing or running the module. A property or AsyncProperty decorator changes how a member is accessed; see its decorators and the guide.
Classes and objects
Module functions
word_to_digit(word)
Source line 9
| Parameter | Passing convention | Default / required |
|---|---|---|
word | positional or keyword | required |
name_to_normal(name)
Source line 26
| Parameter | Passing convention | Default / required |
|---|---|---|
name | positional or keyword | required |
convert_to_normal(txt)
Source line 402
| Parameter | Passing convention | Default / required |
|---|---|---|
txt | positional or keyword | required |
convert_to_single_letters(txt)
Source line 512
| Parameter | Passing convention | Default / required |
|---|---|---|
txt | positional or keyword | required |
equivilency_convert(txt)
Source line 528
| Parameter | Passing convention | Default / required |
|---|---|---|
txt | positional or keyword | required |
class Matching
Source line 556
No constructor is declared here. Some utility classes group functions for direct class access; use the call style shown in the guide.
Declared functions, properties, and nested objects:
Matching.Settings— nested classMatching.fuzzy_match— methodMatching.fuzzy_within— methodMatching.match— methodMatching.within— method
class Matching.Settings
Source line 557
Construct: Matching.Settings(normalize=True, extraneous_characters=False, ignore_spaces=False, match_asterisk=False, fuzzy_threshold=1, phonetic=False)
Fields assigned by the constructor: extraneous_characters, fuzzy_threshold, ignore_spaces, match_asterisk, normalize, phonetic. Some assignments may be conditional; see the object guide for meaning and lifecycle.
Declared functions, properties, and nested objects:
Matching.Settings.__init__— method
Matching.Settings.__init__(self, normalize=True, extraneous_characters=False, ignore_spaces=False, match_asterisk=False, fuzzy_threshold=1, phonetic=False)
Source line 558
| Parameter | Passing convention | Default / required |
|---|---|---|
normalize | positional or keyword | True |
extraneous_characters | positional or keyword | False |
ignore_spaces | positional or keyword | False |
match_asterisk | positional or keyword | False |
fuzzy_threshold | positional or keyword | 1 |
phonetic | positional or keyword | False |
Matching.fuzzy_match(txt1, txt2, threshold=0.8)
Source line 582
| Parameter | Passing convention | Default / required |
|---|---|---|
txt1 | positional or keyword | required |
txt2 | positional or keyword | required |
threshold | positional or keyword | 0.8 |
Matching.fuzzy_within(txt1, txt2, threshold=0.8)
Source line 590
| Parameter | Passing convention | Default / required |
|---|---|---|
txt1 | positional or keyword | required |
txt2 | positional or keyword | required |
threshold | positional or keyword | 0.8 |
Matching.match(txt1, txt2, settings=None)
Source line 597
| Parameter | Passing convention | Default / required |
|---|---|---|
txt1 | positional or keyword | required |
txt2 | positional or keyword | required |
settings | positional or keyword | None |
Matching.within(txt1, txt2, settings=None)
Source line 624
| Parameter | Passing convention | Default / required |
|---|---|---|
txt1 | positional or keyword | required |
txt2 | positional or keyword | required |
settings | positional or keyword | None |