Repetition and groups
\d\d\d\d matches four digits, but writing the same part again and again gets tiring, and often you don't even know how many there will be. A quantifier right after a part of a pattern says how many times that part repeats:
*zero or more times+one or more times?zero times or once: the part is optional{m}exactly m times{m,n}from m to n times
A quantifier repeats only the one thing right before it: a character, a class or a short form such as \d. So r"ha+" is an h followed by one or more a's, and r"[0-9]+" is one or more digits:
>>> import re
>>> re.findall(r"ha+", "ha haaa h hah")
['ha', 'haaa', 'ha']
>>> re.findall(r"colou?r", "color or colour?")
['color', 'colour']
>>> re.findall(r"\d{4}", "1984, 42, 2026")
['1984', '2026']
>>> re.findall(r"[A-Z][a-z]*", "Ada met Bo and X")
['Ada', 'Bo', 'X']The last pattern is a capital letter followed by any number of small letters, none at all included, so it finds capitalized words.
Groups
Round brackets make a group. They collect part of a pattern into one unit: a quantifier after a group repeats the whole group, and a | inside a group chooses only within it:
import re
for laugh in ["haha", "hahaha", "hah"]:
print(laugh, re.search(r"^(ha)+quot;, laugh) is not None)
for word in ["gray", "grey", "groy"]:
print(word, re.search(r"^gr(a|e)yquot;, word) is not None)
haha True
hahaha True
hah False
gray True
grey True
groy FalseWithout the brackets, r"^ha+quot; would be an h followed by a's, which matches haaa but not haha. And r"^gra|eyquot; would mean "starts with gra, or ends with ey". One thing to know: when a pattern has a group, re.findall returns only the part that the group matched, not the whole match. So use groups with re.search, or with the function below.
The whole text: re.fullmatch
^ and $ around a pattern make it describe the whole text. re.fullmatch(pattern, text) does the same more simply: it matches only when the pattern covers the text from its first character to its last. That makes it the natural tool for checking a whole value, such as a year:
import re
def is_year(text):
return re.fullmatch(r"\d{4}", text) is not None
for text in ["2026", "20266", "year 2026", "99"]:
print(text, is_year(text))
2026 True
20266 False
year 2026 False
99 Falsere.search(r"\d{4}", "20266") would find a match, the first four digits. re.fullmatch doesn't, because a fifth digit is left over.