Python Regular Expressions
A regular expression (regex) is a small pattern that describes text, and Python's re module uses it to find, check and replace text.
Why Regex?
String methods like find() and replace() work with exact text. But what if you want "any mobile number" or "every price in this message"? A regex can describe a *kind* of text, not just one fixed word.
Example
import re
message = "Call Ali on 0300-1234567 or Sara on 0321-7654321 after 5 pm."
phones = re.findall(r"03\d{2}-\d{7}", message)
print(phones)['0300-1234567', '0321-7654321']
The pattern 03\d{2}-\d{7} means: "03, then two digits, then a dash, then seven digits".
Always write patterns as raw strings with an r in front, like r"\d+". Otherwise Python may treat the backslash as an escape character before the regex even sees it.
The Building Blocks
| Pattern | Matches |
|---|---|
. | Any one character (except a new line) |
\d | A digit 0-9 |
\w | A letter, digit or underscore |
\s | A space, tab or new line |
\D, \W, \S | The opposite of the three above |
[abc] | One of a, b or c |
[a-z] | Any small letter |
[^0-9] | Anything that is not a digit |
^ / $ | Start / end of the text |
\b | Edge of a word |
How Many Times?
| Pattern | Meaning |
|---|---|
* | 0 or more |
+ | 1 or more |
? | 0 or 1 (optional) |
{3} | Exactly 3 |
{2,5} | From 2 to 5 |
The Main Functions
| Function | What it does |
|---|---|
re.search(p, text) | Finds the first match anywhere, or None |
re.match(p, text) | Checks only at the start of the text |
re.fullmatch(p, text) | The whole text must match |
re.findall(p, text) | A list of all matches |
re.sub(p, new, text) | Replaces every match |
re.split(p, text) | Splits the text at every match |
Example
import re
text = "Order #4521: 3 shirts for Rs 2,400 and 1 cap for Rs 650"
m = re.search(r"#\d+", text)
print(m.group(), m.start())
print(re.findall(r"\d+", text))
print(re.findall(r"Rs [\d,]+", text))
print(re.split(r"\s*and\s*", text))
print(re.sub(r"\d", "*", "PIN 4821"))#4521 6 ['4521', '3', '2', '400', '1', '650'] ['Rs 2,400', 'Rs 650'] ['Order #4521: 3 shirts for Rs 2,400', '1 cap for Rs 650'] PIN ****
Checking Input With fullmatch()
fullmatch() is the right choice when you want to validate something, because it fails if there is any extra text.
Example
import re
def valid_mobile(number):
return re.fullmatch(r"(\+92|0)3\d{2}-?\d{7}", number) is not None
for n in ["03001234567", "0300-1234567", "+923001234567", "0300-12345", "03001234567abc"]:
print(n, valid_mobile(n))03001234567 True 0300-1234567 True +923001234567 True 0300-12345 False 03001234567abc False
(\+92|0) means "+92 or 0". The + needs a backslash because a plain + means "one or more". -? makes the dash optional.
Groups: Taking Parts Out
Round brackets make a group. You can read each group on its own. Named groups (?P<name>...) make the code easier to read.
Example
import re
email = "ayesha.khan@workownix.com"
m = re.fullmatch(r"([\w.]+)@([\w-]+\.\w+)", email)
print(m.group(1))
print(m.group(2))
line = "2026-10-15 Invoice KHI-0042 total 18500"
p = r"(?P<date>\d{4}-\d{2}-\d{2}) Invoice (?P<inv>[A-Z]+-\d+) total (?P<amount>\d+)"
m = re.search(p, line)
print(m["date"], m["inv"], int(m["amount"]) + 500)
print(m.groupdict())ayesha.khan
workownix.com
2026-10-15 KHI-0042 19000
{'date': '2026-10-15', 'inv': 'KHI-0042', 'amount': '18500'}Groups always return strings, so use int() before doing maths.
Cleaning Text With sub()
Example
import re
messy = " Hello Ali, your order is ready! "
print(re.sub(r"\s+", " ", messy).strip())
phone = "0300 123-45 67"
print(re.sub(r"\D", "", phone))
print(re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", "Due 2026-11-30"))Hello Ali, your order is ready! 03001234567 Due 30/11/2026
In the last line \3/\2/\1 puts the groups back in a new order, turning a date from year-month-day into day/month/year.
Flags and compile()
Flags change how a pattern behaves. If you use the same pattern many times, re.compile() saves it as an object.
Example
import re
print(re.findall(r"python", "Python, PYTHON and python", flags=re.IGNORECASE))
price = re.compile(r"Rs\.?\s?(\d+)")
for msg in ["Rs 500 only", "Now Rs.350!", "Free"]:
m = price.search(msg)
print(m.group(1) if m else "no price")['Python', 'PYTHON', 'python'] 500 350 no price
Regex is powerful but hard to read. Keep patterns short, add a comment that explains them, and test them with a few good and bad examples, just like we did above.
Exercise
Exercise
What does re.findall(r"\d+", "Room 12, floor 3") return?
