Python chr Function: Converting Integers to Characters
python chr function: Learn how to use Python's chr() function to convert integers to Unicode characters, handle errors, and avoid common pitfalls.
The python chr function takes an integer and returns the Unicode character corresponding to that code point. It is the inverse of ord(), which does the opposite: given a one-character string, ord() returns its integer code point. For example, chr(65) returns 'A', and ord('A') returns 65. This pairing is fundamental when working with text encoding, data serialization, or any scenario where you need to translate between numeric representations and human-readable characters.
How chr() Works: Syntax and Return Value
The syntax is straightforward:
chr(i)
The argument i must be an integer. The function returns a string of length 1 containing the character whose Unicode code point is exactly i. The valid range for i is from 0 to 0x10FFFF (1,114,111 in decimal), which covers all assigned and unassigned Unicode code points. Any integer outside this range raises a ValueError.
>>> chr(97) 'a' >>> chr(8364) '€' >>> chr(128512) '😀'
The return value is always a string, even if the code point represents a control character or a non-printable character. For instance, chr(0) returns '\x00', which is a null character.
The Relationship Between chr() and ord()
chr() and ord() are exact inverses within the valid Unicode range. If you apply ord() to the result of chr(i), you get back i:
>>> ord(chr(65)) 65
Similarly, chr(ord('B')) returns 'B'. This symmetry is useful for round-tripping data. For example, when you need to store characters as integers in a database or a file, you can use ord() to convert them, and later use chr() to reconstruct the original string.
| Function | Input | Output | Example |
|---|---|---|---|
chr() | integer | one-character string | chr(65) → 'A' |
ord() | one-character string | integer | ord('A') → 65 |
Keep in mind that ord() only accepts a string of length 1. Passing a longer string raises a TypeError. This asymmetry is intentional: a single code point maps to exactly one character, but a character may have multiple code points when combining marks are involved.
Practical Examples: Converting Integers to Characters
A common use case is generating a sequence of characters from a numeric range. For example, to create a list of uppercase letters:
uppercase_letters = [chr(code) for code in range(65, 91)] print(uppercase_letters) # ['A', 'B', 'C', ..., 'Z']
You can also iterate over a range of code points to see which characters they represent. This is helpful when exploring Unicode blocks or when you need to build a lookup table for a custom encoding.
for code in range(0x1F600, 0x1F64F): print(f"{code}: {chr(code)}")
Another practical scenario is converting a list of ASCII codes received from a network protocol into a string:
byte_values = [72, 101, 108, 108, 111] message = ''.join(chr(b) for b in byte_values) print(message) # Hello
This pattern is common when parsing binary formats where text is stored as sequences of byte values.
Handling Unicode Code Points and Surrogates
Unicode code points range from 0 to 0x10FFFF. However, the range 0xD800 to 0xDFFF is reserved for UTF-16 surrogate pairs. These are not valid characters on their own; they only make sense as part of a surrogate pair in UTF-16 encoding. Python's chr() will happily return a string containing a surrogate code point, but that string is not a valid Unicode character in most contexts.
>>> chr(0xD800) '\ud800'
This can cause issues if you later try to encode the string using a standard codec like UTF-8. For example:
>>> chr(0xD800).encode('utf-8') Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeEncodeError: 'utf-8' codec can't encode character '\ud800' in position 0: surrogates not allowed
If you are working with arbitrary integers that might fall into the surrogate range, you should validate the input before passing it to chr() or handle the UnicodeEncodeError when encoding. For most applications, you will only use code points that correspond to actual characters.
Error Handling: What Happens When the Input Is Out of Range
Passing an integer outside 0–0x10FFFF raises a ValueError with a clear message:
>>> chr(-1) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: chr() arg not in range(0x110000)
The same occurs for values above the maximum:
>>> chr(0x110000) ValueError: chr() arg not in range(0x110000)
In production code, you should catch this exception when the input comes from an untrusted source. For example, when parsing user-supplied integers that are meant to represent code points:
def safe_chr(code): try: return chr(code) except ValueError: return None
Returning None or a replacement character like '�' is a common strategy. The choice depends on whether you need to preserve the original error or simply avoid a crash.
Performance and Memory Considerations
chr() is a built-in function implemented in C. It performs a simple bounds check and creates a new string object. The overhead is minimal, comparable to other built-in conversions like str() or int(). There is no need to optimize calls to chr() in typical Python code; the interpreter handles the operation efficiently.
Memory usage is also negligible because each resulting string is a single Unicode character. Python strings are immutable and may be interned in some cases, but you should not rely on that for performance tuning. If you are generating many characters in a loop, the main cost is the creation of many small string objects, which is unavoidable if you need them individually. If you only need the final concatenated string, consider building a list and joining it once, as shown in the earlier example.
Common Pitfalls and How to Avoid Them
One common mistake is passing a string to chr() instead of an integer. This raises a TypeError:
>>> chr('65') TypeError: an integer is required (got type str)
If you are reading numeric data from a text file, remember to convert it with int() first.
Another pitfall is misunderstanding the range of valid code points. Some developers assume that only ASCII values (0–127) are valid, but Unicode extends far beyond that. Using chr() with values above 127 is perfectly safe and returns the corresponding Unicode character.
A more subtle issue arises when combining chr() with ord() on characters that are represented by multiple code points, such as accented characters composed of a base letter and a combining mark. For example, 'é' can be represented as a single code point U+00E9 or as the sequence 'e' + '\u0301'. ord() only works on a single character, so applying it to the composed string 'é' (if it is the single code point version) returns 233, but if you use the decomposed form, you need to handle each code point separately. This is not a limitation of chr() itself but a property of Unicode normalization.
Using chr() in Real-World Scenarios
A practical use case is generating Unicode characters for testing or data generation. For instance, you might want to produce a random string of characters from a specific Unicode block:
import random def random_emoji(): return chr(random.randint(0x1F600, 0x1F64F))
Another scenario is implementing a simple Caesar cipher that works with Unicode code points. While chr() and ord() can handle any code point, shifting by a fixed amount may not preserve the intended character set. For ASCII text, the classic approach works:
def caesar_shift(text, shift): result = [] for char in text: if 'a' <= char <= 'z': shifted = (ord(char) - ord('a') + shift) % 26 + ord('a') result.append(chr(shifted)) elif 'A' <= char <= 'Z': shifted = (ord(char) - ord('A') + shift) % 26 + ord('A') result.append(chr(shifted)) else: result.append(char) return ''.join(result)
Here, chr() converts the shifted integer back into a character. This pattern is common in text processing algorithms that need to manipulate code points directly.
When working with binary data, you often need to convert bytes to characters. For example, decoding a byte string using a specific encoding can be done with .decode(), but if you have a raw byte value and want to treat it as a Latin-1 character, chr(byte) works because Latin-1 maps byte values directly to code points 0–255. This is a useful shortcut when you know the encoding is Latin-1.
Finally, remember that chr() is not limited to ASCII. You can use it to generate any Unicode character, including symbols, mathematical operators, and scripts from various languages. This makes it a versatile tool for internationalization and text processing tasks.