Strings, UTF-8 Encoding, and Slicing
Strings, UTF-8 Encoding, and Slicing
After reading this chapter, you will understand how CPython represents Unicode text, master string slicing mechanics, and author clean modern f-strings.
Mental model
In Python 3, str is an immutable sequence of Unicode code points. CPython internally optimizes string storage (PEP 393: Flexible String Representation) using the smallest necessary byte width per character: - Pure ASCII strings use 1 byte per character (Latin-1). - Extended multilingual strings use 2 bytes (UCS-2). - Emojis and complex scripts use 4 bytes (UCS-4).
String Slicing Mechanics:
String: P y t h o n
+ Index: 0 1 2 3 4 5
- Index: -6 -5 -4 -3 -2 -1
Slice Syntax: s[start:stop:step]
Interval: Half-open [start, stop) â includes start, excludes stop.
Minimal example
Save as string_slicing.py:
# string_slicing.py
def main() -> None:
text = "telemetry_prod_us_east_1"
# Slicing
prefix = text[:9] # From start to index 9 (exclusive)
env = text[10:14] # "prod"
suffix = text[-9:] # Last 9 characters
reversed_text = text[::-1] # Step -1 reverses sequence
print(f"Original : {text}")
print(f"Prefix : {prefix}")
print(f"Env : {env}")
print(f"Suffix : {suffix}")
print(f"Reversed : {reversed_text}")
# Modern f-string debugging specifier (=)
latency_ms = 4.2819
print(f"\nDebugging format: {latency_ms=:.2f}")
if __name__ == "__main__":
main()Run via uv run python string_slicing.py:
Original : telemetry_prod_us_east_1
Prefix : telemetry
Env : prod
Suffix : us_east_1
Reversed : 1_tsae_su_dorp_yrtmelet
Debugging format: latency_ms=4.28
Worked examples
Case 1: String building performance (join vs +=)
Strings are immutable. Concatenating strings with += in a loop creates a new string and copies data on every iteration (\(O(N^2)\) time). Using ''.join() is \(O(N)\).
# string_builder.py
import time
def benchmark() -> None:
n = 100_000
words = ["chunk"] * n
# Idiomatic O(N) method
start = time.perf_counter()
result = "".join(words)
duration = time.perf_counter() - start
print(f"join() duration: {duration:.4f}s (length={len(result)})")
if __name__ == "__main__":
benchmark()Run:
uv run python string_builder.pyCase 2: Encoding strings to bytes and decoding back
When sending data over network sockets or files, you must explicitly encode str (characters) to bytes (raw binary):
# unicode_codec.py
def main() -> None:
original = "Microservices ð [Ξs]"
# UTF-8 serialization
encoded_bytes = original.encode("utf-8")
print(f"Encoded bytes ({len(encoded_bytes)} bytes):\n{encoded_bytes}")
# Deserialization
decoded_str = encoded_bytes.decode("utf-8")
print(f"\nDecoded str: {decoded_str}")
print(f"Match: {original == decoded_str}")
if __name__ == "__main__":
main()Run:
uv run python unicode_codec.pyPitfalls
Pitfall 1: Confusing bytes and str
str and bytes cannot be implicitly combined. Concatenating b"hello" + "world" raises an immediate TypeError.
Pitfall 2: Slice out-of-range behavior
Index lookups like s[100] raise IndexError. However, slices like s[100:200] do not fail; they return an empty string "".
Exercises
- Write a function
is_palindrome(s: str) -> boolthat strips non-alphanumeric characters, converts to lowercase, and checks if the string equals its reverse slice (s[::-1]). - Parse an ISO timestamp string
"2026-09-06T20:15:30Z"into separate date and time components using slicing. - Format a tabular output using f-string alignment specifiers: left-aligned 20 chars (
<20), right-aligned 10 chars (>10), and float formatted to 2 decimals.
Further reading
- PEP 393: Flexible String Representation.
- Python Standard Library: Text Sequence Type â str.
- Ned Batchelder: Pragmatic Unicode.