Strings and text · about 40 min

Essential string methods

Why this matters

Raw text rarely arrives in the shape you need. User input has stray spaces and random capitalization, CSV lines must be broken into fields, paths must be checked for extensions, and output must be assembled from pieces. Python strings come with a toolbox of methods that do these jobs in one call each, implemented in C and therefore fast. Knowing a dozen of them well covers most everyday text handling.

What a method is

A method is a function that belongs to an object's type and is called on an object with a dot:

name = "ada lovelace"
print(name.upper())
print(name.title())
print(name.count("a"))
ADA LOVELACE
Ada Lovelace
3

name.upper() means: look up the upper method on the type of name (which is str), and call it with name as the object to work on. Every string has the same methods because they all share the type str. You can list them with dir(str) and read about one with help(str.upper).

Note: Strings are immutable, so no string method changes the string. Methods that "modify" text return a new string. If you want to keep the result, assign it: name = name.strip().

Because each method returns a string, you can chain calls; they run left to right:

raw = "   Hello World   "
print(raw.strip().lower().replace(" ", "_"))
hello_world

Changing case

MethodResult for "hello World"
upper()"HELLO WORLD"
lower()"hello world"
title()"Hello World"
capitalize()"Hello world"
swapcase()"HELLO wORLD"

To compare text without caring about case, normalize both sides: answer.lower() == "yes". (For fully correct case-insensitive comparison of international text, casefold() is stronger than lower(): "Straße".casefold() becomes "strasse".)

Removing whitespace: strip

strip() returns a copy without leading and trailing whitespace (spaces, tabs, newlines). lstrip() and rstrip() do only the left or right side:

s = "\t  padded  \n"
print(repr(s.strip()))
print(repr(s.lstrip()))
print(repr(s.rstrip()))
'padded'
'padded  \n'
'\t  padded'

repr() shows the string with quotes and escapes, so you can see invisible characters. Use it whenever whitespace is a suspect.

With an argument, strip removes any of the given characters (not a substring) from the ends: "xxhixx".strip("x") is "hi", and "...hello!!".strip(".!") is "hello".

Searching

path = "/home/ada/notes.txt"
print(path.startswith("/home"))
print(path.endswith(".txt"))
print(path.find("ada"))
print(path.find("bob"))
print(path.rfind("/"))
print(path.count("/"))
True
True
6
-1
9
3

Combine rfind with slicing to split at the last separator, the classic file-extension recipe:

filename = "archive.tar.gz"
dot = filename.rfind(".")
print(filename[:dot])
print(filename[dot + 1:])
archive.tar
gz

Replacing

replace(old, new) returns a copy with every occurrence of old replaced; an optional third argument limits how many:

s = "a-b-c-d"
print(s.replace("-", " "))
print(s.replace("-", "", 2))
a b c d
abc-d

Splitting into pieces: split

split breaks a string into a list of strings. A list is an ordered collection written in square brackets; you will study lists properly in the collections module. For now you need two facts: you can index it just like a string (parts[0] is the first piece), and len(parts) tells you how many pieces there are.

line = "ada,36,London"
parts = line.split(",")
print(parts)
print(parts[0])
print(parts[2])
print(len(parts))
['ada', '36', 'London']
ada
London
3

Called with no argument, split() is special: it splits on any run of whitespace and ignores whitespace at the ends. That makes it ideal for words typed by humans:

print("  the   quick brown\tfox ".split())
print("a,,b".split(","))
['the', 'quick', 'brown', 'fox']
['a', '', 'b']

With an explicit separator, every occurrence counts, so adjacent separators produce empty strings (as in "a,,b"). split(sep, 1) stops after the first split, handy for key=value lines whose values may contain =. splitlines() splits text into lines.

Joining pieces: join

join is the reverse of split. It is called on the separator and takes a collection of strings:

words = ["2026", "11", "01"]
print("-".join(words))
print("".join(["a", "b", "c"]))
print(", ".join("xyz"))
2026-11-01
abc
x, y, z

The last example works because a string is itself a sequence of one-character strings. The items must all be strings: "-".join([1, 2]) raises TypeError: sequence item 0: expected str instance, int found.

Why sep.join(items) instead of adding strings with + in a loop? Every + creates a new string and copies both parts, because strings are immutable. Joining n pieces with repeated + can copy the text over and over; join first measures the total length, allocates the result once, and copies each piece once.

Checking what a string contains

A family of is... methods test every character:

print("2026".isdigit())
print("abc".isalpha())
print("abc123".isalnum())
print("   ".isspace())
print("Hello".isupper())
True
True
True
True
False

"12".isdigit() is a good guard before int(...), though it says False for "-12", so it is not a complete number check. All of these return False for the empty string.

Common mistakes

Recap

Going further: run help(str) in the REPL and skim the method list. Try partition: "key=value=x".partition("=") returns three parts, and is often the neatest way to split at the first separator.

Exercises

Exercise 1 · quiz

Do methods change the string?

What does this print?

name = "ada"
name.upper()
print(name)
Answer
  • ada

String methods never modify the string (strings are immutable); they return a new string. Here the new string "ADA" is computed and immediately thrown away because nothing stores it. Write name = name.upper() to keep it.

Exercise 2 · write code

Tidy a name

Read one line containing a person's name, possibly with extra spaces around it and in any mix of upper and lower case. Print it with the surrounding whitespace removed and each word capitalized (first letter uppercase, the rest lowercase), followed on a second line by its number of characters after stripping.

For input aDA lovelace the output must be exactly:

Ada   Lovelace
14

(Spaces between words are kept as they are.)

File: main.py

Starting point

name = input()
print(name)

Example

input

   aDA   lovelace

expected output

Ada   Lovelace
14

Example

input

GRACE

expected output

Grace
5
Hint 1

strip() removes whitespace from both ends.

Hint 2

title() capitalizes each word.

Hint 3

Methods can be chained: input().strip().title().

Reference solution
name = input().strip().title()
print(name)
print(len(name))

Exercise 3 · write code

Reformat a date

Read a date written as YYYY-MM-DD and print it as DD/MM/YYYY. For input 2026-11-01 the output must be exactly:

01/11/2026

Use split to break the date into parts and join (or string concatenation) to put them back together.

File: main.py

Starting point

date = input()
print(date.replace("-", "/"))

Example

input

2026-11-01

expected output

01/11/2026

Example

input

1815-12-10

expected output

10/12/1815
Hint 1

"2026-11-01".split("-") gives ['2026', '11', '01'], and parts[0] is the year.

Hint 2

"/".join([a, b, c]) puts / between the pieces.

Reference solution
date = input().strip()
parts = date.split("-")
print("/".join([parts[2], parts[1], parts[0]]))

Exercise 4 · write code

Inspect a file name

Read a file name (one line) and print four lines:

  1. name: followed by the part before the last dot
  2. extension: followed by the part after the last dot, in lowercase
  3. image: yes if that lowercase extension is png, jpg or gif, else image: no
  4. dots: followed by how many dots the file name contains

If the file name contains no dot at all, the name is the whole input and the extension is empty (so line 2 is extension: with nothing after the space, and line 3 is image: no).

For input holiday.photo.JPG:

name: holiday.photo
extension: jpg
image: yes
dots: 2

rfind finds the last occurrence of a substring and returns -1 if there is none. You may use if/else (taught in the next module) or a conditional expression.

File: main.py

Starting point

filename = input()
dot = filename.find(".")

Example

input

holiday.photo.JPG

expected output

name: holiday.photo
extension: jpg
image: yes
dots: 2

Example

input

notes.txt

expected output

name: notes
extension: txt
image: no
dots: 1
Hint 1

dot = filename.rfind(".") gives the index of the last dot, so filename[:dot] and filename[dot + 1:] are the two parts.

Hint 2

Handle dot == -1 separately, or the slices will give wrong pieces.

Hint 3

ext in ("png", "jpg", "gif") tests membership in a small group of values; filename.count(".") counts dots.

Reference solution
filename = input().strip()
dot = filename.rfind(".")
if dot == -1:
    name = filename
    ext = ""
else:
    name = filename[:dot]
    ext = filename[dot + 1:].lower()
print("name: " + name)
print("extension: " + ext)
print("image: " + ("yes" if ext in ("png", "jpg", "gif") else "no"))
print("dots: " + str(filename.count(".")))

Exercise 5 · quiz

split with and without an argument

How many items does each call return? Answer with the two numbers separated by a space, first a then b.

s = "  one  two   three "
a = s.split()
b = s.split(" ")
Answer

With no argument, split() splits on any run of whitespace and ignores leading and trailing whitespace, so you get ['one', 'two', 'three']. With an explicit separator, every single space is a split point, and empty strings appear at the ends and between adjacent separators. The string contains 8 spaces, so there are 9 pieces: ['', '', 'one', '', 'two', '', '', 'three', '']. For parsing human-typed text, plain split() is almost always what you want.