String Advanced

Lesson 06 covered the basics. Now let's dig deeper. What does immutability really mean? What lesser-known but powerful string operations exist? How are Chinese characters stored in computers? After this lesson, your understanding of strings will reach a new level.


1. String Immutability

We mentioned it briefly in Lesson 06's FAQ — once created, strings can't be modified. Let's see what this means concretely.

PYTHON
text = "Hello"

# ❌ Can't modify like this — this would error
# text[0] = "h"    # TypeError: 'str' object does not support item assignment

To change the first letter to lowercase, create a new string:

PYTHON
text = "Hello"
new_text = "h" + text[1:]   # Create a new string
print(new_text)              # hello
print(text)                  # Hello — original unchanged

Benefits of Immutability

PYTHON
# Safe shared references
a = "Python"
b = a       # b and a point to the same string

# No matter what you do to a, b is unaffected
a = a.upper()
print(a)    # PYTHON
print(b)    # Python — b still holds the original value
💡 Tip: Immutability brings thread safety and hashability. Strings can be dictionary keys (we'll cover this later); lists can't — because lists are mutable, and changing them would break the dictionary lookup.

The Cost of Immutability

Each "modification" creates a new object. Concatenating many strings in a loop is slow:

PYTHON
# ❌ Not recommended: string concatenation in a loop
result = ""
for i in range(10000):
    result += str(i) + ","
# This creates 10000 temporary string objects

# ✅ Recommended: collect in a list, then join()
parts = []
for i in range(10000):
    parts.append(str(i))
result = ",".join(parts)
⚠️ Note: In daily coding, concatenating a few dozen strings is never a performance concern. Watch out for concatenating thousands of strings in a loop. Under a few hundred operations, += is perfectly fine.

Example: Simulating String Methods (Difficulty ⭐⭐)

PYTHON
# Implement a "capitalize" function
def my_capitalize(text):
    if not text:
        return text
    first = text[0].upper()
    rest = text[1:].lower()
    return first + rest

print(my_capitalize("hello WORLD"))      # Hello world
print(my_capitalize("python"))           # Python

# Implement a "replace" function
def my_replace(text, old, new):
    parts = text.split(old)     # Split by old, get the parts
    return new.join(parts)      # Rejoin with new

print(my_replace("one, two, one", "one", "1"))    # 1, two, 1
▶ Try it Yourself

2. String Query Methods

Beyond find() and count() from Lesson 06, here are more useful methods.

startswith() and endswith()

PYTHON
# Check string start/end

url = "https://www.example.com"

print(url.startswith("https"))    # True — HTTPS protocol
print(url.endswith(".com"))       # True — .com domain
print(url.endswith(".org"))       # False

# Can check multiple at once
print(url.endswith((".com", ".org", ".cn")))   # True — matches .com

Practical Uses

PYTHON
# File extension check
filename = "report.pdf"

if filename.endswith((".pdf", ".doc", ".docx")):
    print("Document file")
elif filename.endswith((".jpg", ".png", ".gif")):
    print("Image file")
elif filename.endswith((".py", ".js", ".html")):
    print("Code file")
else:
    print("Other file")

# URL protocol check
link = "ftp://files.example.com"
if link.startswith(("http://", "https://")):
    print("Web link")
elif link.startswith("ftp://"):
    print("FTP link")

Output:

TEXT
Document file
FTP link

removeprefix() and removesuffix() (Python 3.9+)

These were added in Python 3.9 to cleanly remove prefixes or suffixes:

PYTHON
url = "https://www.example.com"

# Remove protocol prefix
domain = url.removeprefix("https://")
print(domain)               # www.example.com

# Remove domain suffix
site_name = url.removesuffix(".com")
print(site_name)             # https://www.example

# If the string doesn't start/end with the given text, return it unchanged
print(url.removeprefix("ftp://"))   # https://www.example.com (unchanged)
💡 Tip: Before Python 3.9, removing a prefix required url[len("https://"):] or url.split("://", 1)[1]. removeprefix() and removesuffix() make this more intuitive.


3. Advanced split() Usage

Lesson 06 covered basic splitting. Let's explore more powerful variants.

Limiting the Number of Splits

PYTHON
# Limit splits with the maxsplit parameter
text = "one,two,three,four,five"

print(text.split(",", 1))       # ['one', 'two,three,four,five']
print(text.split(",", 2))       # ['one', 'two', 'three,four,five']
print(text.split(",", 3))       # ['one', 'two', 'three', 'four,five']

rsplit(): Split from Right to Left

PYTHON
text = "one,two,three,four,five"

print(text.rsplit(",", 1))      # ['one,two,three,four', 'five']
print(text.rsplit(",", 2))      # ['one,two,three', 'four', 'five']

splitlines(): Split by Lines

PYTHON
multiline = """First line
Second line
Third line"""

lines = multiline.splitlines()
print(lines)                    # ['First line', 'Second line', 'Third line']

# Keep line breaks
lines = multiline.splitlines(True)
print(lines)                    # ['First line\n', 'Second line\n', 'Third line']

partition() and rpartition()

partition() not only splits but also retains the separator position:

PYTHON
text = "user@example.com"

# Split by @ into three parts: (left, separator, right)
parts = text.partition("@")
print(parts)                    # ('user', '@', 'example.com')

# If separator not found, returns (original_string, '', '')
print("hello".partition("@"))   # ('hello', '', '')

# rpartition searches from the right
path = "/home/user/documents/file.txt"
last_slash = path.rpartition("/")
print(last_slash)               # ('/home/user/documents', '/', 'file.txt')

partition() is better than split() for "extraction" scenarios — it tells you exactly what each of the three parts is.

Example: URL Parsing (Difficulty ⭐⭐)

PYTHON
# Parse a URL using partition
url = "https://www.example.com:8080/path/page.html?name=test&page=1"

# Extract protocol
proto, _, rest = url.partition("://")
print(f"Protocol: {proto}")        # https

# Extract host and path
host_part, _, path_and_query = rest.partition("/")
print(f"Host: {host_part}")    # www.example.com:8080

# Extract port
host, _, port = host_part.partition(":")
print(f"Domain: {host}")         # www.example.com
print(f"Port: {port}")         # 8080

# Extract path and query
path, _, query = path_and_query.partition("?")
print(f"Path: /{path}")        # /path/page.html
print(f"Query: {query}")        # name=test&page=1
▶ Try it Yourself

Output:

TEXT
Protocol: https
Host: www.example.com:8080
Domain: www.example.com
Port: 8080
Path: /path/page.html
Query: name=test&page=1

4. String Alignment and Padding

Python provides methods to align strings within a specified width — left, right, or center.

PYTHON
text = "Python"

print(text.ljust(10))           # 'Python    ' (left-aligned, padded with spaces on the right)
print(text.rjust(10))           # '    Python' (right-aligned, padded on the left)
print(text.center(10))          # '  Python  ' (centered, padded on both sides)

# Can specify a fill character
print(text.ljust(10, "-"))      # 'Python----'
print(text.rjust(10, "-"))      # '----Python'
print(text.center(10, "-"))     # '--Python--'

zfill(): Zero Padding

Commonly used for numeric IDs:

PYTHON
print("42".zfill(5))            # 00042
print("-42".zfill(5))           # -0042 — negative sign comes first
print("3.14".zfill(8))          # 00003.14

# Generate 3-digit IDs
for i in range(1, 11):
    filename = f"photo_{str(i).zfill(3)}.jpg"
    print(filename, end="  ")

Output:

TEXT
photo_001.jpg  photo_002.jpg  photo_003.jpg  photo_004.jpg  photo_005.jpg
photo_006.jpg  photo_007.jpg  photo_008.jpg  photo_009.jpg  photo_010.jpg

Example: Generating an Aligned Report (Difficulty ⭐⭐)

PYTHON
# Align a table using rjust and ljust
items = [
    ("Apple", 5.0, 3),
    ("Banana", 3.5, 5),
    ("Milk", 12.0, 2),
]

# Headers
header_name = "Item".ljust(8)
header_price = "Price".rjust(6)
header_qty = "Qty".rjust(4)
header_total = "Total".rjust(6)
print(f"{header_name}{header_price}{header_qty}{header_total}")
print("-" * 28)

# Data rows
for name, price, qty in items:
    total = price * qty
    name_col = name.ljust(8)
    price_col = f"{price:.1f}".rjust(6)
    qty_col = str(qty).rjust(4)
    total_col = f"{total:.1f}".rjust(6)
    print(f"{name_col}{price_col}{qty_col}{total_col}")
▶ Try it Yourself

Output:

TEXT
Item     Price  Qty Total
────────────────────────────
Apple      5.0    3  15.0
Banana     3.5    5  17.5
Milk      12.0    2  24.0

5. Character Encoding: ord() and chr()

Every character in a computer corresponds to a number (code point). ord() gets a character's numeric encoding; chr() does the reverse.

PYTHON
# View Unicode code points
print(ord("A"))          # 65
print(ord("中"))         # 20013
print(ord("❤"))          # 10084

# Get character from code point
print(chr(65))           # A
print(chr(20013))        # 中
print(chr(10084))        # ❤

# Generate all uppercase letters
for code in range(ord("A"), ord("Z") + 1):
    print(chr(code), end=" ")
# Output: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

What Is Unicode

Simply put, Unicode is a global character encoding standard — it assigns a unique number to every character from every writing system worldwide (Chinese, English, Arabic, Japanese, etc.). Python 3 stores all strings internally as Unicode, so it natively supports multiple languages.

PYTHON
# Python 3 strings natively support Unicode
text = "Hello, 你好, مرحبا, こんにちは"

print(text)                     # All displayed correctly
print(len(text))                # Character count

# You can also represent characters with \u escapes
print("\u0048")                 # H
print("\u4e2d\u6587")          # 中文 (Chinese)
⚠️ Note: In Python 2, strings were divided into str (bytes) and unicode (text), causing frequent encoding errors. Python 3 unified them — all strings are Unicode, treating Chinese and English equally. This is one of Python 3's best design decisions.

Encoding and Decoding

While Python internally uses Unicode, file storage and network transmission use bytes. So you need encoding (encode) and decoding (decode):

PYTHON
text = "Hello, 世界"

# Encoding: string → bytes
utf8_bytes = text.encode("utf-8")
print(utf8_bytes)               # b'\xe4\xbd\xa0\xe5\xa5\xbd...'
print(len(utf8_bytes))          # Number of bytes in UTF-8

gbk_bytes = text.encode("gbk")
print(len(gbk_bytes))           # Number of bytes in GBK

# Decoding: bytes → string
decoded = utf8_bytes.decode("utf-8")
print(decoded)                  # Hello, 世界

# If encode/decode don't match, error occurs
# gbk_bytes.decode("utf-8")    # UnicodeDecodeError!
💡 Tip: 99% of daily development is fine with UTF-8. UTF-8 is the internet standard encoding, compatible with ASCII and capable of representing all Unicode characters. If you encounter garbled text when reading files, the issue is almost certainly encoding — specify encoding="utf-8" when opening the file and it should work.


6. String Formatting Supplement

Beyond f-strings, Python has two older formatting methods. While f-strings are the modern standard, you may encounter these in older code.

format() Method (Python 3.0+)

PYTHON
# By position
print("My name is {}, I'm {} years old.".format("Alice", 18))
# My name is Alice, I'm 18 years old.

# By name
print("My name is {name}, I'm {age} years old.".format(name="Bob", age=22))
# My name is Bob, I'm 22 years old.

# Number formatting
print("Pi: {:.3f}".format(3.14159))
# Pi: 3.142

% Formatting (Python 2 Era)

PYTHON
# You may see this in older code
print("My name is %s, I'm %d years old." % ("Alice", 25))
# My name is Alice, I'm 25 years old.

print("Pi: %.2f" % 3.14159)
# Pi: 3.14
💡 Tip: f-strings (Python 3.6+) are the best choice — fastest, most readable, most concise. For new code, always use f-strings. format() is still useful for "delayed formatting" (define a template now, fill in values later). The % formatting is just for recognition when you encounter it; don't use it in new code.


Common Use Cases


❓ FAQ

Q Why are Python 3 strings Unicode but Python 2 strings weren't?
A The Python 2 era was plagued by a confusion of encodings (ASCII, Latin-1, GBK, Shift-JIS coexisting). String handling was split between "bytes" and "text" — two separate systems, very error-prone. Python 3 made a clean break: all strings are Unicode (text); byte data is handled separately by the bytes type. This makes string operations intuitive — len("中") is always 1 on any system. The trade-off is you need manual encode/decode for file or network I/O. ⚠️ Q: When should I use split() vs partition()? A: For simple splitting, use split(). When you need the separator itself, use partition(). partition() always returns three elements (left, separator, right), handy for tuple unpacking: left, sep, right = text.partition(":"). split() returns a list of variable length. If the delimiter appears only once and you need content on both sides, partition() is the best choice. Q: What's the difference between ljust() and f-string's {value:10}? A: They produce the same result — text.ljust(10) is equivalent to f"{text:<10}". The f-string version is more flexible (combines alignment with number formatting), while the method call is more intuitive. It's a matter of preference — pick one and stay consistent. Alignment symbols in f-strings: < left, > right, ^ center: f"{text:^10}". Q: What's the difference between UTF-8 and GBK? Why does my Python file sometimes save with garbled characters? A: UTF-8 is the international standard — 1-4 bytes per character, universal. GBK is a Chinese standard (simplified Chinese) — 1-2 bytes per character, only supports Chinese and English. If your Python file is saved as GBK but contains Japanese or Arabic text, it will error or display garbled. Solution: save all Python files as UTF-8 — Python 3 defaults to UTF-8.

📖 Summary

  • Strings are immutable: each "modification" creates a new object; use join() over += for large concatenations
  • Query methods: startswith() / endswith() check start/end; supports tuple arguments for multiple checks
  • Python 3.9+: removeprefix() / removesuffix() for clean prefix/suffix removal
  • Advanced splitting: split(maxsplit=N) limit, rsplit() from right, splitlines() by lines
  • partition() retains separator info, ideal for structured parsing
  • Alignment: ljust() / rjust() / center() / zfill(); custom fill characters supported
  • ord() gets character Unicode code point; chr() converts back
  • Python 3 strings use Unicode internally; encode/decode with encode() / decode(); UTF-8 recommended

📝 Exercises

  1. Basic (Difficulty ⭐): Given files = ["report.pdf", "photo.jpg", "script.py", "notes.txt", "index.html"], use endswith() to filter:

    • All image files (.jpg, .png, .gif)
    • All document files (.pdf, .doc, .docx, .txt)
    • All code files (.py, .js, .html, .css)
  2. Intermediate (Difficulty ⭐⭐): Write a "batch file renamer." Given photos = ["IMG_1.jpg", "IMG_2.jpg", ..., "IMG_12.jpg"], rename them to photo_001.jpg, photo_002.jpg, ..., photo_012.jpg. Hint: Use partition() to split filename and extension; use zfill() for zero-padding.

  3. Challenge (Difficulty ⭐⭐⭐): Write a "simple template engine." Given a template string template and a dictionary data, replace {variable} placeholders with actual values.

    PYTHON
    template = "Dear {name}, your order {order_id} has been {status}. Estimated delivery: {delivery_time}."
    
    data = {
        "name": "Alice",
        "order_id": "20260623001",
        "status": "shipped",
        "delivery_time": "3 business days"
    }
    
    # Your code: implement replace_template(template, data)
    # Output: Dear Alice, your order 20260623001 has been shipped. Estimated delivery: 3 business days.
    

    Extra requirement: If a template variable doesn't exist in data, leave {variable} as-is (don't replace). Hint: Iterate over key-value pairs in data, calling replace() on the template for each.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏