normalize.py

No __version__ declared · Source: modules/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

FunctionBehavior / 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.

  1. Apply convert_to_normal when normalization is enabled.
  2. Remove ordinary spaces if ignore_spaces=True.
  3. Lowercase and apply the fixed substitutions 0→o, @→a, $→s, 1→i, z→2, +→t, even when normalize=False.
  4. Collapse consecutive duplicates when extraneous_characters=True; punctuation is otherwise retained unless normalization removes it.
  5. Apply phonetic substitutions when enabled.
  6. 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

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.

ArgumentHow to choose it
normalize=TrueUse 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=FalseEnable only when adjacent repeated characters may be ignored, such as hellooo versus helo. It does not mean ignore arbitrary punctuation.
ignore_spaces=FalseWhen enabled, removes ordinary ASCII spaces, not every Unicode whitespace character.
match_asterisk=FalseEnable for positional masking and account for the wildcard limits documented above.
fuzzy_threshold=1The 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=FalseEnable 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.

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

ParameterPassing conventionDefault / required
wordpositional or keywordrequired
name_to_normal(name)

Source line 26

ParameterPassing conventionDefault / required
namepositional or keywordrequired
convert_to_normal(txt)

Source line 402

ParameterPassing conventionDefault / required
txtpositional or keywordrequired
convert_to_single_letters(txt)

Source line 512

ParameterPassing conventionDefault / required
txtpositional or keywordrequired
equivilency_convert(txt)

Source line 528

ParameterPassing conventionDefault / required
txtpositional or keywordrequired
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:

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__(self, normalize=True, extraneous_characters=False, ignore_spaces=False, match_asterisk=False, fuzzy_threshold=1, phonetic=False)

Source line 558

ParameterPassing conventionDefault / required
normalizepositional or keywordTrue
extraneous_characterspositional or keywordFalse
ignore_spacespositional or keywordFalse
match_asteriskpositional or keywordFalse
fuzzy_thresholdpositional or keyword1
phoneticpositional or keywordFalse
Matching.fuzzy_match(txt1, txt2, threshold=0.8)

Source line 582

ParameterPassing conventionDefault / required
txt1positional or keywordrequired
txt2positional or keywordrequired
thresholdpositional or keyword0.8
Matching.fuzzy_within(txt1, txt2, threshold=0.8)

Source line 590

ParameterPassing conventionDefault / required
txt1positional or keywordrequired
txt2positional or keywordrequired
thresholdpositional or keyword0.8
Matching.match(txt1, txt2, settings=None)

Source line 597

ParameterPassing conventionDefault / required
txt1positional or keywordrequired
txt2positional or keywordrequired
settingspositional or keywordNone
Matching.within(txt1, txt2, settings=None)

Source line 624

ParameterPassing conventionDefault / required
txt1positional or keywordrequired
txt2positional or keywordrequired
settingspositional or keywordNone