Free lesson · Python Basic to Advance
String Basics
String Basics
What a String Is in Python
A string in Python is an immutable sequence of Unicode characters. The string type is str. Strings are one of the most-used types in Python and have a rich set of methods, slicing semantics, and formatting options.
Two properties matter most for understanding everything else about strings:
Strings are Unicode. Every character in a Python str is a Unicode code point. There is no separate "byte string" type for general text - Python 3 made the decision to treat all strings as Unicode, with bytes handled by a separate bytes type. This is different from Python 2, where strings were byte sequences by default.
Strings are immutable. A string cannot be modified after creation. Operations that appear to modify a string actually create a new one. This is the same behavior as String in Java or str in Rust.
s = "hello"
upper = s.upper()
print(s, upper)
# hello HELLO
This code assigns the string hello to s, then calls the upper method which returns a new uppercase string. The original s is unchanged. The print shows both: hello in lowercase and HELLO in uppercase.
The original string s is unchanged. The upper() method returned a new string. Reassigning s = s.upper() would replace the binding but leave the original string object intact (until garbage collected).
Creating Strings
Strings can be created with single, double, or triple quotes. All three produce str objects.
single = 'hello'
double = "hello"
triple_single = '''hello'''
triple_double = """hello"""
print(single == double == triple_single == triple_double)
# True
This code creates the same string hello using four different quote styles: single quotes, double quotes, triple single quotes, and triple double quotes. The chained equality comparison confirms all four produce equal strings.
Single and double quotes are interchangeable. The convention is to use one consistently in a file. PEP 8 has no strong preference; many style guides (including those of Google and Black) prefer double quotes.
The practical reason to choose one or the other is escaping. A string containing a single quote is cleaner with double-quote delimiters, and vice versa:
print("It's a test")
# It's a test
print('She said "hi"')
# She said "hi"
This code prints two strings, choosing quote style to avoid escaping. The first string contains an apostrophe, so it uses double quotes as delimiters. The second contains double quotes, so it uses single quotes as delimiters.
When both kinds of quote appear, escape the delimiter:
print("It's \"complicated\"")
# It's "complicated"
This code prints a string containing both an apostrophe and double quotes. Because the delimiter is double quotes, the inner double quotes are escaped with backslashes.
Interactive animation: Single, double and triple quotes all make the same string - pick the quote the content does not contain.
Triple-Quoted Strings
Triple-quoted strings can span multiple lines. The newlines in the source are preserved in the string:
message = """First line
Second line
Third line"""
print(message)
# First line
# Second line
# Third line
This code creates a multi-line string using triple double-quotes. The newlines in the source are preserved in the string. Printing shows three lines: First line, Second line, Third line.
Triple-quoted strings are used for:
- Multi-line text content
- Docstrings (covered in Topic 2.7)
- Embedded SQL queries, JSON, HTML, or other multi-line content
sql = """
SELECT id, name, email
FROM users
WHERE active = true
ORDER BY created_at DESC
"""
print(sql.strip())
# SELECT id, name, email
# FROM users
# WHERE active = true
# ORDER BY created_at DESC
This code shows a SQL query embedded as a triple-quoted string. The strip method removes leading and trailing whitespace including the surrounding newlines. The output is the four-line SELECT statement.
The .strip() call removes the leading and trailing newlines that come from the triple-quoted form.
Interactive animation: Triple quotes keep the line breaks you type, so a three-line message prints exactly as written.
Escape Sequences
Backslash followed by certain characters produces special characters in strings:
| Sequence | Meaning |
|---|---|
\n |
Newline |
\t |
Tab |
\r |
Carriage return |
\\ |
Literal backslash |
\' |
Literal single quote |
\" |
Literal double quote |
\0 |
Null character |
\xNN |
Hex character (2 digits) |
\uNNNN |
Unicode character (4 hex digits) |
\UNNNNNNNN |
Unicode character (8 hex digits) |
\N{name} |
Unicode character by name |
Examples:
print("Line 1\nLine 2")
# Line 1
# Line 2
print("Column 1\tColumn 2")
# Column 1 Column 2
print("\u03c0") # Greek letter pi
# π
print("\N{GREEK SMALL LETTER PI}")
# π
This code shows four escape sequences. Backslash n produces a newline, splitting the string across two output lines. Backslash t produces a tab between Column 1 and Column 2. Backslash u zero three c zero is the Unicode code point for the Greek letter pi. Backslash N with the name GREEK SMALL LETTER PI also produces pi.
When an escape sequence is not recognized, Python preserves the backslash literal but produces a SyntaxWarning. Treat unknown escapes as bugs.
For strings containing many backslashes (Windows file paths, regular expressions), use raw strings (Topic 3.6).
Unicode and Encoding
A Python string holds Unicode code points, not bytes. The same string represents the same characters regardless of source encoding.
greeting = "Hello, 世界"
print(greeting)
# Hello, 世界
print(len(greeting))
# 9
This code creates a string containing the ASCII text Hello comma space and the Chinese characters meaning world. The length is nine, counting code points: seven ASCII characters and two Chinese characters.
len() returns the number of code points (9), not the number of bytes. The actual byte count depends on the encoding used to serialize the string.
To convert between strings and bytes, use encode() and decode():
text = "Hello, 世界"
data = text.encode("utf-8")
print(data)
# b'Hello, \xe4\xb8\x96\xe7\x95\x8c'
print(len(data))
# 13
decoded = data.decode("utf-8")
print(decoded == text)
# True
This code encodes the mixed-script string to UTF-8 bytes. The result starts with ASCII bytes followed by six hex-escape bytes representing the two Chinese characters. The byte length is thirteen. Decoding the bytes back to a string returns the original; the equality check confirms it.
UTF-8 is the default encoding and the recommended choice for almost all text I/O. Other encodings (UTF-16, Latin-1, etc.) exist for specific compatibility needs but should be avoided when possible.
Bytes and encoding are covered in detail in Module 18 (File Handling).
Strings as Sequences
A string is a sequence of characters, which means it supports the same operations as other sequences (lists, tuples):
s = "hello"
# Length
print(len(s))
# 5
# Indexing
print(s[0])
# h
print(s[-1])
# o
# Iteration
for ch in s:
print(ch)
# h
# e
# l
# l
# o
# Membership
print("ell" in s)
# True
print("xyz" in s)
# False
# Concatenation
print("hello" + " " + "world")
# hello world
# Repetition
print("ab" * 3)
# ababab
This code demonstrates several sequence operations on the string hello. len returns five. Indexing by zero returns h, by negative one returns o. Iteration yields the characters one at a time. The substring ell is in hello; xyz is not. Concatenation joins three strings into hello world. Multiplying ab by three repeats it three times.
Indexing and slicing are covered in Topic 3.2. Sequence operations and methods are explored throughout the module.
Interactive animation: What sequence means for a string - len, indexing from either end, substring tests with in, and iteration.
Immutability in Practice
Because strings are immutable, attempting to modify them in place raises an error:
s = "hello"
try:
s[0] = "H" # error
except TypeError as e:
print(f"TypeError: {e}")
# TypeError: 'str' object does not support item assignment
This code tries to modify the first character of a string by assignment, which fails because strings are immutable. The except clause catches the TypeError and prints its message.
The pattern for "modifying" a string is to build a new one:
s = "hello"
s = "H" + s[1:]
print(s)
# Hello
This code builds a new string by concatenating an uppercase H with the slice of s from index one onward. The result is Hello with capital H. The variable s is rebound to the new string; the original hello is unchanged.
This creates a new string and rebinds s to it. The original "hello" is unchanged (and eventually garbage collected if no other references exist).
For repeated modification of text, building a new string each time is inefficient. The standard alternatives:
# String concatenation in a loop is O(n²) - slow for large strings
parts = ["apple", "banana", "cherry"]
result = ""
for part in parts:
result += part + ", "
print(result.rstrip(", "))
# apple, banana, cherry
# Use join for O(n) - preferred
parts = ["apple", "banana", "cherry"]
result = ", ".join(parts)
print(result)
# apple, banana, cherry
This code shows two ways to build a comma-joined list of fruits. The first uses plus-equals in a loop, which is order n squared because each concatenation copies the previous result. After the loop, rstrip removes the trailing comma and space. The second uses join, which is order n and is the preferred approach. Both produce the string apple comma banana comma cherry.
The performance difference matters at scale. A 10,000-element join takes milliseconds; equivalent concatenation can take seconds.
For building strings character by character or with complex logic, use io.StringIO:
import io
buffer = io.StringIO()
for i in range(5):
buffer.write(f"Line {i}\n")
print(buffer.getvalue())
# Line 0
# Line 1
# Line 2
# Line 3
# Line 4
This code uses io dot StringIO as a file-like buffer for efficient string building. The loop writes five lines with an embedded index and a newline. getvalue returns the full accumulated string, showing Line zero through Line four.
StringIO provides a file-like interface for building strings efficiently.
Interactive animation: Methods return rather than change - s[0] = "H" raises TypeError, so build a new string and rebind the name.
String Comparison
Strings compare lexicographically, character by character, using Unicode code points:
print("apple" < "banana")
# True
print("apple" < "apply")
# True
print("Apple" < "apple")
# True (uppercase letters have lower code points than lowercase)
This code shows three lexicographic comparisons. apple is less than banana because a comes before b. apple is less than apply because they share the first four letters and e comes before y. Capital Apple is less than lowercase apple because uppercase letters have lower Unicode code points than lowercase letters.
The last comparison is a common source of bugs. 'A' == 65 and 'a' == 97 in ASCII, so 'A' < 'a'. For case-insensitive comparison, normalize both sides:
print("Apple".lower() == "APPLE".lower())
# True
This code normalizes both strings to lowercase before comparing, so capital Apple and uppercase APPLE compare equal.
== compares values; is compares identity. For strings:
a = "hello"
b = "hello"
print(a == b) # True
print(a is b) # True (often - see below)
This code creates two variables a and b both bound to the string hello. a equals b is True because the contents are equal. a is b is often True due to Python's string interning, but this is an implementation detail and should not be relied upon. Always use double-equals for string comparison.
Python may intern short strings, making identical string literals refer to the same object. This is an implementation detail and should not be relied on. Always use == for string comparison, not is.
Empty Strings
An empty string is "" or ''. It is falsy in boolean contexts:
s = ""
if s:
print("non-empty")
else:
print("empty")
# empty
print(bool(""))
# False
print(bool("x"))
# True
This code shows that the empty string is falsy. The if branch tests s, which is empty, so the else branch runs and prints empty. bool of the empty string returns False; bool of a non-empty string like x returns True.
This is useful for checking whether input is present:
name = input("Name: ")
if not name:
print("Name is required")
This code prompts for a name. If the user enters an empty string, the not check is True and the message Name is required prints.
The pattern works for any container - empty lists, dicts, sets are all falsy.
Interview Relevance
Interview: String questions in interviews probe immutability ("Why is
s[0] = 'H'an error?") and Unicode ("How many bytes is the string'café'?"). Senior interviews touch on performance ("Why is+=in a loop slow for strings?") and interning ("Is'hello' is 'hello'guaranteed to be True?").Module 30 covers Python interview preparation.
Common Pitfalls
Interactive animation: Concatenating with += in a loop is O(n squared), while collecting the pieces and joining once is O(n).
Warning: Trying to modify strings in place. Strings are immutable. Build a new string instead.
String concatenation in tight loops. O(n²) behavior. Use
"".join(...)orio.StringIO.Using
isfor string comparison. Python may intern short strings; results are implementation-dependent. Use==.Confusing characters and bytes.
len(s)is code points;len(s.encode())is bytes. Different for non-ASCII.Case-sensitive comparisons assuming case-insensitive.
'Apple' == 'apple'is False. Normalize with.lower()or.casefold().Forgetting escape sequences.
"C:\new"contains a newline. Use raw strings (r"...") for paths.
Try It
Exercise: Build string fluency:
- Create the same string using single quotes, double quotes, and triple quotes. Verify they are equal.
- Write a string containing both a single and double quote. Use escaping or alternating quote styles.
- Measure the length of
"café"and compare to the byte length when encoded as UTF-8.- Try to modify a character in a string by assignment. Note the error.
- Build a string by concatenation in a loop and compare its performance to
"".join(...)for 10,000 iterations.- Demonstrate that
"Apple" < "apple"and explain why.