Replacing Text in Strings

Concepts covered: String Methods

Strings are immutable: once a string exists, none of its characters can change. Reading by index works, and assigning to an index is refused.

status = "pending"
print(status[0])
status[0] = "P"

Every edit therefore produces a new string. You can assemble it yourself from slices, as "P" + status[1:] gives Pending, or call a method that assembles it for you. replace(old, new) is the method for substituting one piece of text with another.

replace hands back a new string

sku = "sku-0042-blue"
sku.replace("-", "_")
print(sku)
clean = sku.replace("-", "_")
print(clean)
print(sku.replace("-", "", 1))
print(sku.replace("red", "green"))

Output:

sku-0042-blue
sku_0042_blue
sku0042-blue
sku-0042-blue

Line 2 calls replace and discards the result, so sku prints unchanged; storing the result in clean keeps it. The optional third argument caps how many replacements happen, counting from the left, so replace("-", "", 1) removes only the first dash, and an empty new deletes the text. When old is absent, replace returns an unchanged copy and raises nothing, which hides typos: the red call above did nothing and reported nothing.

Because a missed match is silent, a cleanup that must change something can count first. sku.count("-") is 2, the number of substitutions replace("-", "_") will make, and a count of 0 warns that the call would hand back its input untouched.

How replace scans the text

print("aaaa".replace("aa", "b"))
print("banana".replace("ana", "X"))
print("a-b".replace("-", "--"))
print("Cat cat CAT".replace("cat", "dog"))

Output:

bb
bXna
a--b
Cat dog CAT

replace scans left to right. After substituting a match, it resumes past the text it consumed. "aaaa" holds 2 non-overlapping aa runs, so it becomes bb. In banana the first ana covers positions 1 to 3, the scan resumes at 4, and the ana that would start at 3 is never seen. Inserted text is never rescanned, so replacing each dash with 2 dashes turns a-b into a--b and stops there. Matching is case-sensitive, so only the lowercase cat changes.

Chaining replace calls

Because replace returns a string, another method can run on its result in the same expression. Python evaluates the chain left to right, and each call works on the string the call before it produced:

header = "Order Date (UTC)"
col = header.lower().replace(" ", "_").replace("(", "").replace(")", "")
print(col)

Output:

order_date_utc

Each call here removes or swaps a character that no later call looks for, so the calls could run in any order and still give order_date_utc. A chain goes wrong when a later call matches text an earlier call inserted, because a string keeps no record of which characters were original.

Running the same cleanup twice

Pipelines get re-run: a failed job retries, a backfill replays last month. A cleanup step that damages input it already cleaned will corrupt data on the second run.

address = "12 Main St"
once = address.replace("St", "Street")
twice = once.replace("St", "Street")
print(once)
print(twice)

def expand_street(a):
    if a.endswith(" St"):
        return a[:-3] + " Street"
    return a

print(expand_street(expand_street(address)))

Output:

12 Main Street
12 Main Streetreet
12 Main Street

Street begins with St, so the second pass turns it into Streetreet. The function checks for the suffix with endswith and rewrites it with a slice, so a second call finds no St ending and returns its input untouched. Calling it once or 5 times produces the same address.

Editing at a position you found

email = "ana.lopez@example.com"
at = email.find("@")
masked = email[0] + "*" * (at - 1) + email[at:]
print(at)
print(masked)

Output:

9
a********@example.com

replace works by content: it changes every copy of some text wherever it appears. Masking by position needs find for the position and slices for the edit. With at at 9, the result keeps email[0], adds "*" * (at - 1), which repeats the star 8 times to cover positions 1 to 8, and keeps everything from the @ onward.

Do
  • Assign the result: s = s.replace(a, b)
  • Pass a count when only the first match should change
  • Test a cleanup by running it twice on the same value
  • Use find plus slices for edits tied to a position
Avoid
  • Call replace and expect the original string to change
  • Chain replaces where a later call can match text an earlier call inserted
  • Replace a short token that also appears inside longer words
  • Assign to s[i]; build a new string instead

This line is meant to swap every Y and N. What does it print?

row = "N,Y,N"
print(row.replace("Y", "N").replace("N", "Y"))

About this lesson

This section is part of the Strings: Intermediate lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.