Skip to main content
Advertisement

Ch 3.3 Advanced String Usage

Python strings go beyond simple text containers, offering powerful formatting and manipulation capabilities. In modern Python development, f-strings are essential.

1. Mastering f-strings (Python 3.6+)

An f-string uses the format f"..." or f'...' — just prefix the string with f. You can place any Python expression inside the curly braces {}.

Basic Usage

name = "Alice"
age = 30
score = 95.6789

# Basic variable insertion
print(f"Name: {name}, Age: {age}")
# Name: Alice, Age: 30

# Expressions are supported
print(f"Next year's age: {age + 1}")
print(f"Pass/Fail: {'Pass' if score >= 60 else 'Fail'}")
print(f"Name length: {len(name)}")
print(f"Uppercase: {name.upper()}")

Format Specification

pi = 3.14159265358979
price = 29900
text = "python"
number = 42

# Decimal places
print(f"{pi:.2f}") # 3.14 (2 decimal places)
print(f"{pi:.5f}") # 3.14159
print(f"{pi:10.2f}") # ' 3.14' (total width 10)

# Thousands separator
print(f"{price:,}") # 29,900
print(f"{price:,.2f}") # 29,900.00

# Alignment
print(f"{text:<10}") # 'python ' (left-aligned, width 10)
print(f"{text:>10}") # ' python' (right-aligned)
print(f"{text:^10}") # ' python ' (center-aligned)
print(f"{text:*^10}") # '**python**' (fill character *)

# Integer formatting
print(f"{number:d}") # 42 (decimal)
print(f"{number:b}") # 101010 (binary)
print(f"{number:o}") # 52 (octal)
print(f"{number:x}") # 2a (hexadecimal)
print(f"{number:#x}") # 0x2a (with prefix)
print(f"{number:08b}") # 00101010 (8-digit binary)

# Sign display
print(f"{42:+d}") # +42
print(f"{-42:+d}") # -42
print(f"{42: d}") # ' 42' (space for positive numbers)

f-string Debugging (f"{x=}") Python 3.8+

x = 10
y = 20
result = x + y

# Print variable name and value together — very useful for debugging!
print(f"{x=}") # x=10
print(f"{y=}") # y=20
print(f"{result=}") # result=30
print(f"{x+y=}") # x+y=30
print(f"{x * 2 + 1=}") # x * 2 + 1=21

# Use with formatting
pi = 3.14159
print(f"{pi=:.3f}") # pi=3.142

# Real-world example: debugging a function
def calculate(a: float, b: float) -> float:
result = a * b + a / b
print(f"DEBUG: {a=}, {b=}, {result=:.4f}")
return result

calculate(3.0, 4.0)
# DEBUG: a=3.0, b=4.0, result=12.7500

Nested f-strings

width = 10
fill_char = "*"
text = "hello"

# Use variables inside the braces
print(f"{text:{fill_char}^{width}}") # **hello***

# Dynamic formatting
for i in range(1, 4):
decimals = i
print(f"{3.14159:.{decimals}f}")
# 3.1
# 3.14
# 3.142

2. str.format() and % Formatting (Legacy)

Modern code should use f-strings, but you need to recognize these when reading older code.

# str.format()
name, age = "Bob", 25
print("Name: {}, Age: {}".format(name, age))
print("Name: {0}, Age: {1}, Name again: {0}".format(name, age))
print("Name: {name}, Age: {age}".format(name=name, age=age))
print("π = {:.4f}".format(3.14159))
print("{:>10}".format("right")) # ' right'

# % formatting (oldest style)
print("Name: %s, Age: %d" % (name, age))
print("π = %.4f" % 3.14159)
print("%10s" % "right") # ' right'

# Comparison
value = 42
pi = 3.14159

# f-string (Python 3.6+) — recommended
print(f"{value:05d} | {pi:.3f}") # 00042 | 3.142

# str.format()
print("{:05d} | {:.3f}".format(value, pi))

# % formatting
print("%05d | %.3f" % (value, pi))

3. Key String Methods

text = "  Hello, Python World!  "

# Case
print(text.upper()) # ' HELLO, PYTHON WORLD! '
print(text.lower()) # ' hello, python world! '
print(text.title()) # ' Hello, Python World! '
print(text.capitalize()) # ' Hello, python world! '
print(text.swapcase()) # swap upper and lower case

# Stripping whitespace
print(text.strip()) # 'Hello, Python World!'
print(text.lstrip()) # 'Hello, Python World! '
print(text.rstrip()) # ' Hello, Python World!'
print(text.strip("! ")) # 'Hello, Python World'

# Splitting and joining
sentence = "Hello,World,Python"
words = sentence.split(",")
print(words) # ['Hello', 'World', 'Python']

lines = "Line1\nLine2\nLine3"
print(lines.splitlines()) # ['Line1', 'Line2', 'Line3']

# Maximum number of splits
print("a-b-c-d".split("-", 2)) # ['a', 'b', 'c-d']

# join — combine a list into a string
parts = ["apple", "banana", "strawberry"]
print(", ".join(parts)) # 'apple, banana, strawberry'
print("-".join(["2024", "01", "15"])) # '2024-01-15'
print("".join(["H", "e", "l", "l", "o"])) # 'Hello'

# Searching
s = "Hello, World!"
print(s.find("World")) # 7 (index, returns -1 if not found)
print(s.find("Java")) # -1
print(s.index("World")) # 7 (raises ValueError if not found!)
print(s.count("l")) # 3 (count occurrences)

# Checking
print(s.startswith("Hello")) # True
print(s.endswith("!")) # True
print("12345".isdigit()) # True
print("hello".isalpha()) # True
print("hello123".isalnum()) # True
print(" ".isspace()) # True

# Replacing
print(s.replace("World", "Python")) # 'Hello, Python!'
print("aaa".replace("a", "b", 2)) # 'bba' (at most 2 replacements)

# Padding
print("42".zfill(5)) # '00042' (pad with zeros)
print("hi".center(10, "-")) # '----hi----'
print("hi".ljust(10, ".")) # 'hi........'
print("hi".rjust(10, ".")) # '........hi'

4. Advanced String Slicing

s = "Hello, Python!"

# Basic slicing [start:stop:step]
print(s[0:5]) # 'Hello'
print(s[7:]) # 'Python!'
print(s[:5]) # 'Hello'
print(s[-7:]) # 'Python!'
print(s[:-1]) # 'Hello, Python'

# Using step
print(s[::2]) # 'Hlo yhn' (every other character)
print(s[1::2]) # 'el,Pto!' (starting at 1, every other)
print(s[::-1]) # '!nohtyP ,olleH' (reversed)

# Palindrome check
def is_palindrome(text: str) -> bool:
cleaned = "".join(c.lower() for c in text if c.isalnum())
return cleaned == cleaned[::-1]

print(is_palindrome("racecar")) # True
print(is_palindrome("A man a plan a canal Panama")) # True
print(is_palindrome("hello")) # False

# Extracting substrings via slicing
log_line = "2024-01-15 10:30:45 INFO User logged in"
date = log_line[:10]
time = log_line[11:19]
level = log_line[20:24]
message = log_line[25:]

print(f"Date: {date}, Time: {time}, Level: {level}, Message: {message}")

5. String Immutability — Why Can't Strings Be Modified?

text = "hello"
# text[0] = "H" # TypeError: 'str' object does not support item assignment

# Modifying a string always creates a new string
text = "H" + text[1:] # "Hello"

# Advantage of immutability: can be used as dictionary keys or set elements
word_count = {"hello": 5, "world": 3}
word_set = {"apple", "banana", "apple"} # duplicates removed

# Performance consideration: when many modifications are needed, convert to list first
def reverse_string(s: str) -> str:
chars = list(s) # convert to a mutable list
chars.reverse()
return "".join(chars) # convert back to a string

# Or simply
def reverse_string_v2(s: str) -> str:
return s[::-1]

6. str.encode() / bytes.decode() — Encoding Basics

# string → bytes (encoding)
text = "Hello, Python!"

utf8_bytes = text.encode("utf-8")
print(utf8_bytes)
print(type(utf8_bytes)) # <class 'bytes'>
print(len(utf8_bytes)) # number of bytes

# bytes → string (decoding)
decoded = utf8_bytes.decode("utf-8")
print(decoded) # Hello, Python!

# Various encodings
latin1_bytes = text.encode("latin-1") # Latin-1 encoding
ascii_bytes = text.encode("ascii") # ASCII encoding

# Encoding when reading/writing files
# open("file.txt", "r", encoding="utf-8") # specifying encoding is recommended

# bytes literal
raw_bytes = b"Hello, World!"
print(raw_bytes[0]) # 72 (ASCII code for 'H')
print(raw_bytes.decode()) # 'Hello, World!'

# Handling encoding errors
faulty = b"\xff\xfe Test"
print(faulty.decode("utf-8", errors="ignore")) # ignore invalid characters
print(faulty.decode("utf-8", errors="replace")) # replace invalid characters with ?

Pro Tips: str.join() vs + Performance, textwrap.dedent, String Interning

Performance difference between join() and +:

import timeit

words = ["python"] * 10000

# + operator: O(n²) — creates a new string every iteration
def concat_plus(words):
result = ""
for word in words:
result += word + " " # creates a new string every loop!
return result

# join(): O(n) — processes all at once
def concat_join(words):
return " ".join(words)

time_plus = timeit.timeit(lambda: concat_plus(words), number=100)
time_join = timeit.timeit(lambda: concat_join(words), number=100)

print(f"+ operator: {time_plus:.4f}s")
print(f"join(): {time_join:.4f}s")
# join() is tens to hundreds of times faster!

textwrap.dedent to remove leading indentation:

import textwrap

def get_sql_query() -> str:
query = """
SELECT user_id, user_name, email
FROM users
WHERE is_active = true
AND created_at > '2024-01-01'
ORDER BY created_at DESC
LIMIT 100
"""
return textwrap.dedent(query).strip()

print(get_sql_query())
# SELECT user_id, user_name, email
# FROM users
# ... (indentation removed)

# textwrap.wrap — wrap long text
long_text = "Python is a high-level programming language first released in 1991 by Guido van Rossum. It is known for its readable and concise syntax and is used across many domains."
wrapped = textwrap.fill(long_text, width=60)
print(wrapped)

String Interning:

import sys

# Python automatically interns short identifier-like strings
a = "hello"
b = "hello"
print(a is b) # True (same object!)

# Strings with spaces are not interned
c = "hello world"
d = "hello world"
print(c is d) # False (not guaranteed)

# Force interning with sys.intern()
e = sys.intern("hello world")
f = sys.intern("hello world")
print(e is f) # True

# Interning improves memory usage and comparison speed when dealing
# with large numbers of repeated strings (e.g., hundreds of thousands)

You have explored all the powerful features of Python strings. The next chapter covers conditional expressions and the Walrus operator for writing even more concise Python code.

Advertisement