Skip to content

stringprep Module Complexity

The stringprep module exposes the tables of RFC 3454, the "stringprep" framework that protocols such as IDNA's nameprep build on. It prepares nothing itself: each function answers one question about one character, either whether it belongs to a table or what the table maps it to. A profile such as encodings.idna.nameprep() is the loop that applies them to a string.

Every function takes a single character, a str of length 1, and not a code point: an int or a longer string raises TypeError in all but in_table_c11(), which simply returns False. So there is no size variable in the table below. n is the characters in a string you prepare with them. Category and bidirectional lookups read Unicode 3.2 data, whatever Unicode version the interpreter's unicodedata reports.

Complexity Reference

Table A: unassigned code points

Operation Time Space Notes
stringprep.in_table_a1(code) O(1) O(1) Unassigned in Unicode 3.2, so a character assigned later still counts

Table B: mappings

Operation Time Space Notes
stringprep.in_table_b1(code) O(1) O(1) Characters commonly mapped to nothing
stringprep.map_table_b3(code) O(1) O(1) Case folding for use without normalization
stringprep.map_table_b2(code) O(1) O(1) Case folding for use with NFKC normalization

Table C: prohibited characters

Operation Time Space Notes
stringprep.in_table_c11(code), stringprep.in_table_c12(code), stringprep.in_table_c11_c12(code) O(1) O(1) ASCII space, non-ASCII spaces, and both
stringprep.in_table_c21(code), stringprep.in_table_c22(code), stringprep.in_table_c21_c22(code) O(1) O(1) ASCII controls, non-ASCII controls, and both
stringprep.in_table_c3(code) O(1) O(1) Private use
stringprep.in_table_c4(code) O(1) O(1) Non-character code points
stringprep.in_table_c5(code) O(1) O(1) Surrogate codes
stringprep.in_table_c6(code) O(1) O(1) Inappropriate for plain text
stringprep.in_table_c7(code) O(1) O(1) Inappropriate for canonical representation
stringprep.in_table_c8(code) O(1) O(1) Change display properties or are deprecated
stringprep.in_table_c9(code) O(1) O(1) Tagging characters

Table D: bidirectional categories

Operation Time Space Notes
stringprep.in_table_d1(code) O(1) O(1) Bidirectional category R or AL
stringprep.in_table_d2(code) O(1) O(1) Bidirectional category L

Checking Characters

Each predicate does a fixed amount of work on one character. Pass the character itself; its code point is the wrong type.

import stringprep

assert stringprep.in_table_b1('\u00ad')  # O(1) - soft hyphen maps to nothing
assert stringprep.in_table_c11(' ')  # O(1) - ASCII space
assert stringprep.in_table_c12('\u3000')  # O(1) - ideographic space
assert stringprep.in_table_c21('\x07')  # O(1) - ASCII control
assert stringprep.in_table_c3('\ue000')  # O(1) - private use
assert stringprep.in_table_d1('\u05d0')  # O(1) - Hebrew letter, category R
assert stringprep.in_table_d2('A')  # O(1) - category L

try:
    stringprep.in_table_b1(ord('A'))
except TypeError:
    pass
else:
    raise AssertionError('a code point was accepted')

Mapping Characters

Both mappings return a string, because folding one character can produce several. map_table_b2 is the one a profile applies before NFKC normalization, as nameprep does; map_table_b3 is the folding for a profile that does not normalize.

import stringprep

assert stringprep.map_table_b3('A') == 'a'        # O(1)
assert stringprep.map_table_b3('\u00df') == 'ss'  # O(1) - sharp s folds to two characters
assert stringprep.map_table_b2('\u2122') == 'tm'  # O(1) - trademark sign

Preparing a String

A profile asks the tables a fixed set of questions about each character, so preparing a string is O(n) time and O(n) space for the result. encodings.idna.nameprep() is that loop for domain labels: it maps with B.1 and B.2, normalizes, and then checks the prohibited and bidirectional tables.

from encodings.idna import nameprep

assert nameprep('Stra\u00dfe\u00ad') == 'strasse'  # O(n) - fold, drop the soft hyphen

Unicode 3.2 Tables

RFC 3454 is defined against Unicode 3.2, and the module's category lookups use that version's data. A character assigned in a later Unicode version is unassigned as far as table A.1 is concerned.

import stringprep
import unicodedata

emoji = '\U0001F600'  # assigned after Unicode 3.2
assert unicodedata.category(emoji) == 'So'
assert stringprep.in_table_a1(emoji)  # O(1) - unassigned in Unicode 3.2

Performance Best Practices

✅ Do:

  • Use encodings.idna.nameprep(), or the idna codec, for domain labels: it is the O(n) loop over these tables
  • Pass one-character strings, such as the items of iterating a str

❌ Avoid:

  • Passing ord(c): every function but in_table_c11() raises TypeError, and that one silently returns False
  • Treating in_table_a1() as "unassigned today"; it answers for Unicode 3.2

Version Notes

  • All Python 3: Category and bidirectional lookups use Unicode 3.2 through unicodedata.ucd_3_2_0
  • encodings - encodings.idna.nameprep() applies these tables to a label
  • codecs - the idna codec that calls nameprep
  • unicodedata - the current Unicode database, and ucd_3_2_0