Strings and Text

This section documents the current Python release line as published at the official Python documentation, which is the reference these pages are written and verified against. No specific patch version is pinned.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

A Python str is an immutable sequence of Unicode code points — every "modification" method returns a new string, and text is always kept distinct from raw bytes. This page covers literals, the methods you reach for daily, the three ways to format text, and the str/bytes split that underlies all of it.

String Literals, Immutability, Indexing and Slicing

Single and double quotes are interchangeable single-line literals; triple-quoted strings ('''…​''' or """…​""") span multiple lines and are how docstrings are written. A r prefix makes a raw string, where backslashes are kept literally — essential for regular expressions and Windows paths — while a plain string interprets escape sequences such as \n, \t, and \\. Details are in the tutorial’s Strings section and the full escape-sequence table in the lexical analysis reference’s String and Bytes literals:

single = 'hello'
double = "hello"
triple = """line one
line two"""

path = r"C:\Users\name\docs"      # raw: backslashes are literal
escaped = "C:\\Users\\name\\docs"  # same characters, escaped manually
print(path == escaped)             # True

A str is a sequence type — see Text Sequence Type — str — so indexing and slicing work exactly like on a list, except the result of any slice is itself a str. Because strings are immutable, indexed or sliced assignment is not possible; build a new string instead:

word = "Python"
print(word[0])        # 'P'
print(word[-1])       # 'n'  (negative index counts from the end)
print(word[2:5])      # 'tho'
print(word[:3])       # 'Pyt'
print(word[::-1])     # 'nohtyP'  (reversed, via a step of -1)

# word[0] = "J"        # TypeError: 'str' object does not support item assignment
new_word = "J" + word[1:]
print(new_word)        # 'Jython'

Common String Methods

Every method below returns a new string (or list); the original is never changed in place. The full catalogue is under String Methods:

raw = "  Hello, World!  "

print(raw.strip())                    # 'Hello, World!'         (both ends)
print(raw.strip().lower())            # 'hello, world!'
print(raw.strip().upper())            # 'HELLO, WORLD!'

csv_line = "alice,30,engineer"
fields = csv_line.split(",")          # ['alice', '30', 'engineer']
print(fields)
print("-".join(fields))               # 'alice-30-engineer'

print(raw.strip().replace("World", "Python"))   # 'Hello, Python!'

print("report.CSV".endswith(".csv"))            # False: case-sensitive
print("report.CSV".lower().endswith(".csv"))    # True
print("report.csv".startswith("report"))        # True

Formatting: f-strings, .format(), and %

f-strings (the modern default)

A formatted string literal — an f prefix — evaluates any expression inside \{ \} and inserts its result. This is the preferred way to format text in modern Python; the grammar lives in the reference’s Formatted string literals, and the mini-language after a : (widths, alignment, precision, thousands separators, and so on) is the same Format Specification Mini-Language used by .format():

name = "Ada"
age = 36
print(f"Hello, {name}! You are {age} years old.")   # Hello, Ada! You are 36 years old.
print(f"{age * 2}")                                  # 72 -- any expression is allowed

pi = 3.14159265
print(f"{pi:.2f}")            # '3.14'      -- fixed-point, 2 decimals
print(f"{1_000_000:,}")       # '1,000,000' -- thousands separator
print(f"{name:>10}")          # '       Ada' -- right-aligned in a width-10 field

Since Python 3.8, adding = after an expression prints both the source text and its value — a quick way to inline debug prints without writing the variable name twice:

count = 7
print(f"{count=}")            # count=7
print(f"{count * 2=}")        # count * 2=14

.format()

str.format() predates f-strings and is still common in code that assembles a template string separately from the values (translated strings, logging templates). Positional and keyword placeholders work the same way, documented at Format String Syntax:

template = "{0} scored {1} points"
print(template.format("Ada", 95))              # Ada scored 95 points

template2 = "{name} scored {score} points"
print(template2.format(name="Ada", score=95))   # Ada scored 95 points

data = {"name": "Ada", "score": 95}
print(template2.format(**data))                 # Ada scored 95 points -- unpack a dict as keywords

%-formatting (legacy — read, don’t write)

The oldest style, borrowed from C’s printf, is documented at printf-style String Formatting. It still turns up in older codebases and logging calls, so it is worth recognizing, but new code should prefer f-strings:

name = "Ada"
score = 95
print("%s scored %d points" % (name, score))   # Ada scored 95 points

str vs. bytes: Unicode Fundamentals

Python 3 draws a hard line between text (str, a sequence of Unicode code points) and binary data (bytes, a sequence of integers 0-255) — there is no implicit conversion between them, unlike Python 2’s blurred str/unicode split. The design rationale and history are in the Unicode HOWTO. Converting between the two is always explicit: .encode() turns str into bytes using a named encoding (UTF-8 by default and the overwhelming default in practice), and .decode() reverses it:

text = "café"                      # str: Unicode code points
encoded = text.encode("utf-8")     # bytes: b'caf\xc3\xa9'
print(encoded)
print(type(encoded))               # <class 'bytes'>

decoded = encoded.decode("utf-8")  # back to str
print(decoded)                     # café
print(decoded == text)             # True

# text + encoded                   # TypeError: can only concatenate str (not "bytes") to str

A bytes literal uses a b prefix and can only contain ASCII bytes; a mismatched encoding when decoding raises rather than silently corrupting data:

>>> b"hello"
b'hello'
>>> "café".encode("ascii")
Traceback (most recent call last):
  ...
UnicodeEncodeError: 'ascii' codec can't encode character '\xe9' in position 3: ordinal not in range(128)
>>> "café".encode("utf-8").decode("ascii")
Traceback (most recent call last):
  ...
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 3: ordinal not in range(128)

Keeping str and bytes separate types — rather than one type that is "text until it isn’t" — is exactly what unified Python 3’s text handling: a function that expects text can never be silently handed undecoded bytes, and vice versa.

See Also

  • Collections — lists, tuples, dicts, and sets that strings are often split into or joined from.

  • Standard Library Tour — where re, textwrap, and other text-processing modules live.