Python Strings
Learn Python strings: literals, escapes, indexing, slicing, methods, formatting, Unicode, bytes, parsing, validation, and efficient text construction.
A Python string is an ordered, immutable sequence of Unicode characters. Strings use the built-in str type and represent text such as names, identifiers, messages, file contents, user input, and structured data.
Python does not have a separate single-character type. A value containing one character, such as 'A', is still a string. This differs from languages that provide a distinct char type or commonly represent text as a character array. Python strings are not lists or character arrays: they support sequence operations, but their elements are one-character strings and the string itself cannot be changed in place. They also differ from numbers, which support arithmetic, and from bytes, which contain binary values rather than Unicode text.
You can experiment with the examples in the Python interactive prompt.
Creating string literals
A string literal is text written directly in Python source code. Single and double quotes create the same type of value.
name = 'Ada'
greeting = "Hello, Python"
empty = ''
quote1 = "She said, 'hello'."
quote2 = 'The word "string" is quoted.'
quote3 = 'It\'s useful to choose the other delimiter.'
Choose an alternate delimiter when the text contains a quote. You can also escape a quote with a backslash.
Triple single or triple double quotes create multiline literals. They are useful for paragraphs and documentation strings.
message = """First line
Second line
Third line"""
def area(width, height):
"""Return the area of a rectangle."""
return width * height
Adjacent literals are concatenated by Python before the program runs. This is useful for long fixed messages.
label = ('This is one logical string '
'written over two source lines.')
A raw string literal, prefixed with r or R, keeps most backslashes literal. Raw strings are convenient for regular-expression patterns and Windows-like paths, although pathlib is usually preferable for path manipulation.
pattern = r'\d+\.\d+'
path = r'C:\Users\Ada\notes.txt'
Raw strings still recognize the quote delimiter and cannot end with a single backslash, because that backslash would escape the closing quote. Use an additional backslash, a normal escaped string, or a path-handling tool instead.
Python source files can contain Unicode directly when the file is saved using a suitable encoding. Unicode escape forms are also available: \u takes four hexadecimal digits and \U takes eight.
direct = 'café ☕'
short_escape = '\u03bb' # λ
long_escape = '\U0001F6E9' # 🛩
String literal forms and escaping
| Form | Purpose | Key behavior | Caveats |
|---|---|---|---|
'text' | Ordinary text | Single-quoted literal | Escape an internal apostrophe or use double quotes |
"text" | Ordinary text | Double-quoted literal | Escape an internal double quote or use single quotes |
'''text''' or """text""" | Multiline text and docstrings | Preserves line breaks in the value | The indentation and final newline may become part of the string |
r'text' | Backslash-heavy text | Backslashes are generally literal | Cannot end with one backslash; quotes still matter |
'a' 'b' | Long fixed literals | Adjacent literals concatenate | Only literal expressions are combined this way |
Escape sequences and representations
An escape sequence begins with a backslash and gives special meaning to the following characters. Common sequences include \n for newline, \t for tab, \r for carriage return, \\ for a backslash, and \' or \" for quotes.
text = 'Name:\tAda\nRole:\tProgrammer'
print(text)
print(repr(text))
print produces human-readable output, so the newline and tab are displayed as formatting. repr produces a debugging-oriented representation, so escape sequences are visible. The representation is not necessarily what the user should see.
Indexing and slicing
Strings are sequences, so indexing retrieves one-character strings. Indexes start at zero. Negative indexes count from the end: -1 is the last character.
word = 'Python'
print(word[0]) # P
print(word[2]) # t
print(word[-1]) # n
print(word[-2]) # o
An invalid direct index raises IndexError. A slice has the form text[start:stop:step]. The start is included, the stop is excluded, and omitted bounds use the beginning or end as appropriate.
word = 'Python'
print(word[1:4]) # yth
print(word[:2]) # Py
print(word[2:]) # thon
print(word[::2]) # Pto
print(word[-3:]) # hon
print(word[::-1]) # nohtyP
print(word[20:30]) # '' rather than IndexError
Slicing beyond the available range generally returns the portion that exists. This differs from direct indexing, which requires an actual position. A negative step moves from right to left and is commonly used to reverse a string.
Indexing and slicing reference
| Expression pattern | Meaning | Example outcome | Out-of-range behavior |
|---|---|---|---|
s[i] | Character at index i | 'Python'[0] is 'P' | Raises IndexError |
s[-1] | Last character | 'Python'[-1] is 'n' | Raises IndexError for an empty string |
s[a:b] | From a, up to but excluding b | 'Python'[1:4] is 'yth' | Clips to the available portion |
s[:b] | Beginning through b - 1 | 'Python'[:2] is 'Py' | Clips safely |
s[a:] | a through the end | 'Python'[2:] is 'thon' | Clips safely |
s[::step] | Every step character | 'Python'[::-1] reverses the string | May return an empty string |
Immutability
Immutable means that a string object cannot be changed after it is created. Indexed assignment therefore fails.
word = 'cat'
# word[0] = 'b' # TypeError: 'str' object does not support item assignment
word = 'b' + word[1:] # create a replacement and rebind word
word = word.replace('b', 'B')
Methods such as replace, upper, and strip return new strings. They do not modify the original. Rebinding a variable changes which object the variable refers to; it does not mutate the old object.
Operators and basic operations
a = 'Py'
b = 'thon'
print(a + b) # Python: concatenation
print('ha' * 3) # hahaha: repetition
print('th' in 'Python')
print('x' not in 'Python')
print(len('Python'))
for character in 'cat':
print(character)
String comparisons use lexicographic ordering, which compares Unicode code point values from left to right. Thus 'apple' < 'banana' is true, while case and Unicode details can make human-language ordering different from Python's simple comparison.
Repeatedly adding strings in a loop can be inefficient or obscure. Collect pieces and use join when constructing many fragments.
String methods
Case conversion and whitespace
text = ' Python Programming '
print(text.lower())
print(text.upper())
print('Straße'.casefold())
print('hello world'.capitalize())
print('hello world'.title())
print('PyTHon'.swapcase())
print(text.strip())
print(text.lstrip())
print(text.rstrip())
casefold is more aggressive than lower and is often better for case-insensitive matching. Case conversion is language-sensitive in practice; it is not a complete solution for every human-language collation problem.
Frequently used string methods
| Method | Purpose | Returns | Important behavior |
|---|---|---|---|
find, rfind | Find first or last substring position | Integer | Returns -1 if absent |
index, rindex | Find first or last substring position | Integer | Raises ValueError if absent |
count | Count non-overlapping occurrences | Integer | Does not count overlapping matches |
startswith, endswith | Check a prefix or suffix | Boolean | Can accept a tuple of alternatives |
replace(old, new, count) | Replace text | New string | count limits replacements |
split, rsplit | Separate fields | List of strings | Whitespace splitting differs from explicit delimiters |
splitlines | Separate lines | List of strings | Recognizes common line boundaries |
partition, rpartition | Split around one delimiter | Three-item tuple | Retains delimiter position as the middle item |
join | Combine strings | New string | Every item must be a string |
strip, lstrip, rstrip | Remove surrounding characters | New string | Removes from the relevant edge, not arbitrary internal text |
center, ljust, rjust, zfill | Align or pad text | New string | Useful for columns and numeric-looking output |
removeprefix, removesuffix | Remove an exact prefix or suffix | New string | Leaves the value unchanged when it does not match |
translate | Apply character substitutions | New string | Uses a table made by maketrans |
encode | Convert text to bytes | bytes | Uses a named encoding such as UTF-8 |
Searching, splitting, and replacing
text = 'red, green, blue'
print(text.find('green'))
print(text.index('green'))
print(text.count(','))
print(text.startswith('red'))
print(text.endswith('blue'))
print(text.replace(', ', ' | ', 1))
fields = [field.strip() for field in text.split(',')]
left, separator, right = text.partition(', ')
print(fields)
print(left, separator, right)
Use find when absence is an expected result and index when absence should be treated as an error. For a delimiter that may occur once, partition makes the three outcomes explicit. Use split for multiple separated fields. With split() and no argument, consecutive whitespace is grouped and surrounding whitespace is ignored; with an explicit delimiter, consecutive delimiters create empty fields.
print('a b'.split()) # ['a', 'b']
print('a,,b'.split(',')) # ['a', '', 'b']
print('a:b:c'.rsplit(':', 1))
print('one\ntwo\r\nthree'.splitlines())
Alignment, classification, and translation
print('cat'.center(7, '-'))
print('cat'.ljust(6, '.'))
print('cat'.rjust(6, '.'))
print('42'.zfill(5))
value = 'User_42'
print(value.isalpha(), value.isdigit(), value.isnumeric())
print(value.isdecimal(), value.isalnum(), value.isspace())
print(value.islower(), value.isupper(), value.istitle())
print(value.isidentifier(), value.isascii())
table = str.maketrans({'a': '@', 'e': '3'})
print('safe place'.translate(table))
Classification methods are Unicode-aware. For example, isdigit, isnumeric, and isdecimal are not interchangeable, and they may accept characters outside ASCII. isidentifier checks Python identifier syntax but does not check whether the text is a reserved keyword.
import keyword
candidate = 'class'
valid_name = candidate.isidentifier() and not keyword.iskeyword(candidate)
print(valid_name) # False
For regular, complex, or repeated patterns, use the re module rather than combining many ordinary string operations.
Finding, validating, and extracting text
Filename extension example
filename = 'report.final.pdf'
# Slicing can inspect the final four characters.
print(filename[-4:]) # .pdf
print(filename.endswith('.pdf'))
# rpartition handles the final dot directly.
stem, dot, extension = filename.rpartition('.')
if dot:
print(stem, extension) # report.final pdf
else:
print('No extension')
The negative slice demonstrates sequence indexing, while rpartition is clearer when the final delimiter separates a stem and extension. endswith checks a suffix without manual slicing.
Normalize user-entered text
raw = ' YES, please '
normalized = raw.strip().casefold().replace(',', '')
print(normalized) # yes please
Each method returns a replacement string, so the result must be assigned or passed onward. Classification methods can validate simple input, but they do not replace domain-specific validation.
Parse a comma-separated record
record = 'Ada Lovelace, 36, London'
parts = [part.strip() for part in record.split(',')]
if len(parts) != 3:
print('Expected three fields')
else:
name, age_text, city = parts
print(name, age_text, city)
Real CSV data can contain quoted commas, so use the csv module instead of a simple split when the format requires CSV rules.
String formatting
Formatted string literals, usually called f-strings, are the preferred modern approach for combining text and values. Expressions go inside braces.
name = 'Ada'
age = 36
score = 0.875
print(f'{name} is {age} years old.')
print(f'Score: {score:.1%}')
print(f'Balance: ${1234567.5:,.2f}')
print(f'[{name:^10}]')
print(f'Value: {score!s}')
print(f'Debug: {score!r}')
print(f'{{name}} is a literal brace example')
Format specifications can control width and alignment (<, ^, >), precision such as .2f, signs such as +, integer bases such as b, o, and x, percentages such as .1%, and thousands separators such as ,. The !s and !r conversion flags request string-oriented and representation-oriented conversion.
number = 255
print(f'{number:b} {number:o} {number:x}')
print(f'{number:+d}')
Use doubled braces, {{ and }}, for literal braces in an f-string. Formatting avoids the TypeError caused by trying to add a string and a number manually.
count = 3
# 'Items: ' + count # TypeError
print(f'Items: {count}')
print('Items: {}'.format(count))
print('Coordinates: {0}, {1}'.format(10, 20))
print('User: {name}, city: {city}'.format(name='Ada', city='London'))
record = {'name': 'Ada'}
print('{0[name]}'.format(record))
str.format supports positional, named, attribute, and item fields. Percent-style formatting is legacy syntax that you may encounter in existing code.
print('Count: %d, name: %s' % (count, name))
Text formatting approaches
| Approach | Typical use | Strengths | Limitations |
|---|---|---|---|
| f-string | New Python code | Readable and evaluates expressions directly | Requires a modern Python version |
str.format | Reusable templates and older code | Supports named, positional, attribute, and item fields | More verbose than f-strings |
| Percent formatting | Legacy code | Still widely recognizable | Less flexible and less readable for complex formatting |
| Manual concatenation | Small, all-string combinations | Simple for a few text fragments | Needs explicit conversions and becomes hard to read |
Unicode, characters, and normalization
Python str values represent Unicode text. A Unicode code point is a numeric value assigned to a character or symbol. A user-perceived character can consist of several code points, such as a base letter followed by a combining accent. Emoji sequences can also combine multiple code points.
import unicodedata
text = 'e\u0301'
print(len(text))
print(unicodedata.normalize('NFC', text))
Consequently, len and indexing count Python string elements, not necessarily what a user sees as one character. Case conversion also has language-specific caveats. When visually equivalent text must compare consistently, normalize both values with the same form, commonly NFC or NFKC, according to the application's requirements.
Text encoding and bytes
str is text. bytes is a sequence of binary values. Encoding converts a string to bytes; decoding converts bytes back to a string. UTF-8 is a common encoding that can represent all Unicode code points.
text = 'café ☕'
data = text.encode('utf-8')
restored = data.decode('utf-8')
print(data)
print(restored)
UnicodeEncodeError can occur when an encoding cannot represent a string. UnicodeDecodeError can occur when bytes do not follow the selected encoding. Identify the actual data encoding rather than hiding errors automatically. Error modes such as errors='replace' or errors='ignore' should be used only when losing or substituting data is acceptable.
str versus bytes
| Aspect | str | bytes |
|---|---|---|
| Meaning | Unicode text | Binary data |
| Element | One-character string | Integer from 0 through 255 |
| Conversion | Use encode to produce bytes | Use decode to produce text |
| Typical use | User-visible text and parsed content | Files, network protocols, and encoded data |
| Length | Number of code-point elements | Number of bytes |
with open('notes.txt', 'w', encoding='utf-8') as file:
file.write('Unicode text\n')
with open('notes.txt', 'r', encoding='utf-8') as file:
content = file.read()
Specify the encoding when reading and writing text files so behavior does not depend on the operating system's default.
Input, output, and representations
input always returns a string. Convert it when a numeric value is required.
answer = input('How many? ')
quantity = int(answer)
print(f'You entered {quantity}.')
print is intended for human-readable output. It normally ends with a newline; use the end argument to choose another ending.
print('Loading', end='...')
print('done')
print('one\ntwo\nthree')
str(value) gives a user-oriented conversion, while repr(value) aims to make details visible for debugging, including quotes and escape sequences.
Efficient string construction
Strings are immutable, so repeated concatenation in a loop can create many intermediate strings. For multiple pieces, accumulate strings in a list and join them once.
lines = []
for number in range(1, 4):
lines.append(f'Line {number}')
report = '\n'.join(lines)
print(report)
names = ['Ada', 'Grace', 'Linus']
message = ', '.join(name.upper() for name in names)
print(message)
A generator expression avoids creating an additional list when values can be produced one at a time. For advanced stream-like construction, io.StringIO provides an in-memory text buffer.
from io import StringIO
buffer = StringIO()
buffer.write('Header\n')
buffer.write('Body\n')
result = buffer.getvalue()
Common errors and edge cases
| Symptom or exception | Likely cause | Recommended correction |
|---|---|---|
SyntaxError for a literal | Unterminated text, mismatched quotes, or an unescaped internal quote | Close the literal, choose another delimiter, or escape the quote |
| Unexpected tab or newline in a path | Backslashes were interpreted as escape sequences | Use a valid raw literal, escape backslashes, or use path tools |
TypeError during concatenation | A non-string value was added to a string | Use an f-string, format, or deliberate conversion |
IndexError | A direct index does not exist | Check the length, guard the access, or use a slice |
replace appears ineffective | The returned string was ignored | Rebind or otherwise use the returned value |
join raises TypeError | An item is not a string | Convert or format each intended item first |
Unexpected empty fields from split | Consecutive explicit delimiters were present | Handle empty fields or validate the input format |
| Encoding or decoding exception | The selected encoding does not match the data | Identify the encoding; prefer UTF-8 for controlled formats |
| Visually identical strings differ | Different Unicode normalization forms | Normalize both consistently when required |
| Character count differs from file or network size | Unicode characters may use multiple UTF-8 bytes | Distinguish len(str_value) from len(str_value.encode('utf-8')) |
Practical checklist
- Use single or double quotes according to which delimiter makes the text clearest.
- Use triple quotes for intentional multiline text and documentation strings.
- Remember that indexing starts at zero and that slice stop positions are excluded.
- Never expect a string method to mutate its receiver; use its returned string.
- Prefer
findfor an optional match andindexwhen absence should raise an exception. - Use
startswithandendswithinstead of manual prefix and suffix slicing. - Use f-strings for readable mixed text and values.
- Use
joinfor many fragments and specify file encodings explicitly. - Keep Unicode text as
struntil a byte-oriented interface requires encoding.
For focused follow-up practice, see Python string functions, accessing individual characters, escape characters, and searching strings.