Python online course

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

FormPurposeKey behaviorCaveats
'text'Ordinary textSingle-quoted literalEscape an internal apostrophe or use double quotes
"text"Ordinary textDouble-quoted literalEscape an internal double quote or use single quotes
'''text''' or """text"""Multiline text and docstringsPreserves line breaks in the valueThe indentation and final newline may become part of the string
r'text'Backslash-heavy textBackslashes are generally literalCannot end with one backslash; quotes still matter
'a' 'b'Long fixed literalsAdjacent literals concatenateOnly 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 patternMeaningExample outcomeOut-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 stringMay 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

MethodPurposeReturnsImportant behavior
find, rfindFind first or last substring positionIntegerReturns -1 if absent
index, rindexFind first or last substring positionIntegerRaises ValueError if absent
countCount non-overlapping occurrencesIntegerDoes not count overlapping matches
startswith, endswithCheck a prefix or suffixBooleanCan accept a tuple of alternatives
replace(old, new, count)Replace textNew stringcount limits replacements
split, rsplitSeparate fieldsList of stringsWhitespace splitting differs from explicit delimiters
splitlinesSeparate linesList of stringsRecognizes common line boundaries
partition, rpartitionSplit around one delimiterThree-item tupleRetains delimiter position as the middle item
joinCombine stringsNew stringEvery item must be a string
strip, lstrip, rstripRemove surrounding charactersNew stringRemoves from the relevant edge, not arbitrary internal text
center, ljust, rjust, zfillAlign or pad textNew stringUseful for columns and numeric-looking output
removeprefix, removesuffixRemove an exact prefix or suffixNew stringLeaves the value unchanged when it does not match
translateApply character substitutionsNew stringUses a table made by maketrans
encodeConvert text to bytesbytesUses 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

ApproachTypical useStrengthsLimitations
f-stringNew Python codeReadable and evaluates expressions directlyRequires a modern Python version
str.formatReusable templates and older codeSupports named, positional, attribute, and item fieldsMore verbose than f-strings
Percent formattingLegacy codeStill widely recognizableLess flexible and less readable for complex formatting
Manual concatenationSmall, all-string combinationsSimple for a few text fragmentsNeeds 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

Aspectstrbytes
MeaningUnicode textBinary data
ElementOne-character stringInteger from 0 through 255
ConversionUse encode to produce bytesUse decode to produce text
Typical useUser-visible text and parsed contentFiles, network protocols, and encoded data
LengthNumber of code-point elementsNumber 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 exceptionLikely causeRecommended correction
SyntaxError for a literalUnterminated text, mismatched quotes, or an unescaped internal quoteClose the literal, choose another delimiter, or escape the quote
Unexpected tab or newline in a pathBackslashes were interpreted as escape sequencesUse a valid raw literal, escape backslashes, or use path tools
TypeError during concatenationA non-string value was added to a stringUse an f-string, format, or deliberate conversion
IndexErrorA direct index does not existCheck the length, guard the access, or use a slice
replace appears ineffectiveThe returned string was ignoredRebind or otherwise use the returned value
join raises TypeErrorAn item is not a stringConvert or format each intended item first
Unexpected empty fields from splitConsecutive explicit delimiters were presentHandle empty fields or validate the input format
Encoding or decoding exceptionThe selected encoding does not match the dataIdentify the encoding; prefer UTF-8 for controlled formats
Visually identical strings differDifferent Unicode normalization formsNormalize both consistently when required
Character count differs from file or network sizeUnicode characters may use multiple UTF-8 bytesDistinguish 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 find for an optional match and index when absence should raise an exception.
  • Use startswith and endswith instead of manual prefix and suffix slicing.
  • Use f-strings for readable mixed text and values.
  • Use join for many fragments and specify file encodings explicitly.
  • Keep Unicode text as str until a byte-oriented interface requires encoding.

For focused follow-up practice, see Python string functions, accessing individual characters, escape characters, and searching strings.