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.
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 itNo 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 entirelys[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?