Python

String methods and slicing — the operations you'll reach for constantly

Splitting, joining, stripping, and slicing aren't separate topics — they're the small set of operations underneath almost every piece of real text processing, and slicing's [start:stop:step] syntax is the one part that actually trips people up.

Beginner

3 min read

Strings are immutable — every "modifying" method returns a new string

s = "hello"
s.upper()        # 'HELLO' — a NEW string
s                # 'hello' — s itself is completely unchanged
 
s = s.upper()    # this is the only way to actually "keep" the change — reassign it

No string method ever changes the string it's called on — strings are immutable in Python, the same way tuples are (covered in the collections lesson). .upper(), .replace(), .strip(), and every other string method return a new string, leaving the original exactly as it was. Forgetting to reassign the result (s = s.upper(), not just s.upper()) is one of the most common first mistakes with strings.

Splitting and joining: text to a list, and back

"a,b,,c".split(",")          # ['a', 'b', '', 'c'] — empty strings between consecutive delimiters are kept
"  a  b  c  ".split()          # ['a', 'b', 'c'] — split() with NO argument also collapses extra whitespace
 
",".join(["a", "b", "c"])       # 'a,b,c' — the REVERSE of split: a list back into one string

.split(sep) with an explicit separator keeps every piece, including empty strings from consecutive delimiters. Calling .split() with no argument is a different, special mode: it splits on any run of whitespace and automatically discards empty results — the tool of choice for "break this into words" rather than "break this on this exact character." .join() runs the other direction: separator.join(list_of_strings) — note it's called on the separator, not the list, which is the opposite of what people expect the first time.

Stripping, replacing, and case

"  hello  ".strip()      # 'hello' — removes whitespace from BOTH ends only
"  hello  ".lstrip()       # 'hello  ' — left end only
"xxhelloxx".strip("x")      # 'hello' — strips the given characters, not whitespace, from both ends
 
"hello world".replace("world", "there")   # 'hello there' — every occurrence, unless count= is given
 
"Hello".lower()    # 'hello'
"Hello".upper()     # 'HELLO'

.strip() only removes characters from the ends of a string — " a b ".strip() is "a b", not "ab"; whitespace in the middle is untouched. Passing an argument (.strip("x")) strips that specific set of characters instead of whitespace. .replace(old, new) replaces every occurrence by default; passing a third integer argument caps how many replacements happen, left to right.

Checking content: the .is*() and .startswith()/.endswith() family

"42".isdigit()          # True
"abc".isalpha()          # True
"user_input".startswith("user_")    # True — checks the actual start, safer than [:5] == "user_"
"report.csv".endswith((".csv", ".json"))   # True — a TUPLE checks any of several suffixes at once

.isdigit(), .isalpha(), .isalnum(), and similar methods check a string's content without needing a regular expression (covered in the regex lesson) for simple cases. .startswith()/.endswith() accept a tuple of options, not just one string — .endswith((".csv", ".json")) checks against both in one call, which is both clearer and safer than manual slicing (s[:4] == ".csv" breaks silently if the string is shorter than the slice).

Slicing: s[start:stop:step]

s = "abcdefgh"
 
s[2:5]      # 'cde'   — index 2 up to (NOT including) index 5
s[:3]        # 'abc'   — from the start
s[3:]         # 'defgh' — to the end
s[-3:]         # 'fgh'   — the last 3 characters, via a NEGATIVE index
s[::2]          # 'aceg'  — every 2nd character
s[::-1]          # 'hgfedcba' — step -1 REVERSES the string entirely

s[start:stop:step] never raises an error for an out-of-range index the way s[10] on a shorter string would — it just clips to whatever's actually there, which is why slicing is so often used for "the last N characters" or "up to this point, however long that turns out to be" without extra bounds checking. stop is always exclusive, the same convention range() uses. A negative step walks backward — [::-1] is the standard, idiomatic Python way to reverse a string or list, not a special "reverse" method.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. After running `s = 'hello'; s.upper()` (without reassigning), what is s?

2. What does ' a b c '.split() (no arguments) return?

3. What does 'report.csv'.endswith(('.csv', '.json')) check?

4. What does s[::-1] do?