Intermediate Python 📂 Modules · 4 of 6 238 min read

Python Regular Expressions

Master Python regex from definition to advanced patterns. Learn what a regular expression is, every function in the re module (search, match, findall, sub, compile), and every special character (\d, \w, \s, anchors, quantifiers, groups, lookaheads). Includes two visual diagrams — search vs match vs findall behavior and email regex anatomy — a common pattern library, and six exercises from simple extraction to strong password validation.

🌐
SectionSection第 01 节

The Story That Explains Regular Expressions 🔍Regular Expressions समझाने वाली कहानी 🔍一个讲清正则表达式的故事 🔍

The Search Warrant That Describes a Shapeआकार बताने वाला तलाशी वारंट描述"形状"的搜查令
Imagine you are a detective with a warehouse full of a million documents. Your boss says: "Find every phone number in there."

You could hire a hundred people to read every page. Or you could hand each of them one small card that describes a shape: "an optional +91, then ten digits, the first one between 6 and 9." Everyone scans their pile for anything with that shape. Ten minutes later, you have every phone number in the building — even ones you have never seen before.

That card is a regular expression: a tiny language for describing patterns of text instead of exact text. Python's re module is the team that reads the card and does the search.

It is the difference between "find the word error" and "find every line that starts with a date, contains ERROR or WARNING, and ends with a number." The first is a search box. The second is a superpower.
सोचिए आप एक जासूस हैं और आपके पास दस लाख दस्तावेज़ों से भरा गोदाम है। आपका boss कहता है: "इसमें हर phone number ढूँढो।"

आप हर पन्ना पढ़ने के लिए सौ लोग रख सकते हैं। या आप हर एक को एक छोटा card दे सकते हैं जो एक आकार बताता है: "पहले चाहें तो +91, फिर दस अंक, पहला अंक 6 से 9 के बीच।" हर कोई अपने ढेर में उस आकार वाली चीज़ खोजता है। दस मिनट बाद आपके पास building का हर phone number है — वे भी जो आपने पहले कभी नहीं देखे।

वह card एक regular expression है: सटीक text के बजाय text के patterns बताने की एक छोटी भाषा। Python का re module वह टीम है जो card पढ़ती है और खोज करती है।

यही फ़र्क़ है "error शब्द ढूँढो" और "हर वह line ढूँढो जो तारीख़ से शुरू हो, जिसमें ERROR या WARNING हो, और जो एक नंबर पर ख़त्म हो" में। पहला एक search box है। दूसरा एक superpower।
想象你是一名侦探,面前是装满一百万份文件的仓库。上司说:"把里面所有的电话号码都找出来。"

你可以雇一百个人逐页阅读。也可以给每人一张小卡片,上面描述一种 形状:"可选的 +91,然后是十位数字,第一位在 6 到 9 之间。"每个人在自己那堆里寻找 符合这个形状 的东西。十分钟后,楼里所有电话号码都找到了 — 包括你从没见过的。

这张卡片就是 正则表达式:一种描述文本 模式 而非精确文本的小语言。Python 的 re 模块就是读卡片、执行搜索的团队。

这就是 "查找 error 这个词" 和 "查找所有以日期开头、包含 ERROR 或 WARNING、并以数字结尾的行" 的区别。前者是搜索框,后者是超能力。
💡
The Core Insightमूल बात核心要点

A regex describes three things: which characters are allowed, where they may appear, and how many times. Learn the small set of symbols for each, and you can read almost any pattern — they are just short sentences built from small pieces.

एक regex तीन बातें बताता है: कौन-से अक्षर चलेंगे, वे कहाँ आ सकते हैं, और कितनी बार। हर एक के छोटे-से चिह्न सीख लें, और आप लगभग कोई भी pattern पढ़ सकेंगे — ये छोटे टुकड़ों से बने छोटे वाक्य ही हैं।

一个正则表达式描述 三件事:允许 哪些 字符、它们可以出现在 哪里、出现 几次。学会每类的一小组符号,你就能读懂几乎所有模式 — 它们只是由小片段组成的短句。

▶️
This Tutorial Has Four Simulators and a Quizइस Tutorial में चार Simulators और एक Quiz है本教程有四个模拟器和一个小测验

Look for the interactive boxes: the Regex Lab (Section 05) where you can type any pattern, the Character Class Explorer (08), the Engine Step-Through (10) and the Backtracking Race (14). The Regex Lab follows Python's rules and was checked against Python 3.13 on 250 test cases.

Interactive डिब्बे खोजें: Regex Lab (Section 05) जहाँ आप कोई भी pattern लिख सकते हैं, Character Class Explorer (08), Engine Step-Through (10) और Backtracking Race (14)। Regex Lab Python के नियम मानता है और 250 test cases पर Python 3.13 से जाँचा गया है।

请留意这些互动框:可以输入任意模式的 正则实验室(第 05 节)、字符类探索器(08)、引擎单步演示(10)和 回溯赛跑(14)。正则实验室遵循 Python 的规则,并在 250 个测试用例上与 Python 3.13 核对过。


SectionSection第 02 节

What Is a Regular Expression? 🧩Regular Expression क्या है? 🧩什么是正则表达式?🧩

A regular expression (regex) is a string that describes a search pattern. Most characters, like letters and digits, match themselves. Others — . ^ $ * + ? { } [ ] \ | ( ) — are metacharacters: instructions for the engine.

Regular expression (regex) एक string है जो search pattern बताती है। ज़्यादातर अक्षर, जैसे letters और अंक, ख़ुद से match करते हैं। बाक़ी — . ^ $ * + ? { } [ ] \ | ( ) — metacharacters हैं: engine के लिए निर्देश।

正则表达式(regex)是描述 搜索模式 的字符串。大多数字符(如字母和数字)匹配它们自己。另一些 — . ^ $ * + ? { } [ ] \ | ( ) — 是 元字符:给引擎的指令。

🧠 A Regex Says Three ThingsRegex तीन बातें कहता है正则表达式说三件事
Whatक्या什么
Which characters: \d a digit, [aeiou] a vowel, . anything. कौन-से अक्षर: \d एक अंक, [aeiou] एक स्वर, . कुछ भी। 哪些字符:\d 一个数字,[aeiou] 一个元音,. 任意字符。
Whereकहाँ哪里
Where in the text: ^ start, $ end, \b word edge. Text में कहाँ: ^ शुरुआत, $ अंत, \b शब्द का किनारा। 文本中的位置:^ 开头,$ 结尾,\b 单词边界。
How manyकितने几次
How many times: ? 0–1, * 0+, + 1+, {4} exactly 4. कितनी बार: ? 0–1, * 0+, + 1+, {4} ठीक 4। 重复次数:? 0–1,* 0+,+ 1+,{4} 恰好 4 次。
import re

# The simplest regex: literal text
print(re.search("hello", "say hello world"))

# A pattern: one or more digits
print(re.findall(r"\d+", "order 42 shipped 17 items"))

# The shape of a UK postcode
print(re.search(r"[A-Z]{1,2}\d[A-Z\d]? \d[A-Z]{2}", "Send to SW1A 1AA today"))
OUTPUT
<re.Match object; span=(4, 9), match='hello'> ['42', '17'] <re.Match object; span=(8, 16), match='SW1A 1AA'>

Raw Strings: Always Write r"..."Raw Strings: हमेशा r"..." लिखें原始字符串:始终写 r"..."

Python reads the backslashes in a normal string before re sees the pattern. "\b" becomes a backspace character, and the search quietly finds nothing. In a raw string, r"\b" stays as the two characters \ and b.

Python सामान्य string के backslashes को re के pattern देखने से पहले ही पढ़ लेता है। "\b" एक backspace अक्षर बन जाता है, और खोज चुपचाप कुछ नहीं पाती। Raw string में r"\b" दो अक्षरों \ और b के रूप में ही रहता है।

Python 会在 re 看到模式 之前 先处理普通字符串中的反斜杠。"\b" 变成退格字符,搜索会悄悄地什么也找不到。在 原始字符串 中,r"\b" 保持为 \ 和 b 两个字符。

import re

# Without r"...", Python turns "\b" into a BACKSPACE character before re sees it
print(repr("\bcat\b"))
print(re.findall("\bcat\b", "a cat sat"))      # [] - looks for backspaces!

# With a raw string, re receives the two characters \ and b
print(repr(r"\bcat\b"))
print(re.findall(r"\bcat\b", "a cat sat"))     # ['cat']
OUTPUT
'\x08cat\x08' [] '\\bcat\\b' ['cat']
⚠️
Since Python 3.12: a SyntaxWarningPython 3.12 से: एक SyntaxWarning自 Python 3.12 起:SyntaxWarning

Writing "\d+" without r still works by luck (\d is not a Python escape), but since Python 3.12 it prints SyntaxWarning: invalid escape sequence '\d'. The r prefix removes the warning and the risk.

r के बिना "\d+" लिखना किस्मत से चल जाता है (\d Python escape नहीं है), पर Python 3.12 से यह SyntaxWarning: invalid escape sequence '\d' छापता है। r prefix warning और ख़तरा दोनों हटा देता है।

不加 r 写 "\d+" 凑巧能用(\d 不是 Python 转义),但从 Python 3.12 起会打印 SyntaxWarning: invalid escape sequence '\d'。加上 r 前缀,警告和风险都消失。


SectionSection第 03 节

The Search Functions 🔎खोजने वाले Functions 🔎搜索函数 🔎

Five functions find matches. They differ in where they look and what they return.

पाँच functions matches खोजते हैं। वे इसमें अलग हैं कि कहाँ देखते हैं और क्या लौटाते हैं।

有五个函数用于 查找 匹配。它们的区别在于 在哪里 找和 返回什么。

FunctionFunction函数Looksकहाँ देखता है查找位置Returnsलौटाता है返回
re.search(p, s)anywhere — the first hitकहीं भी — पहला मिलान任意位置 — 第一个Match or NoneMatch या NoneMatch 或 None
re.match(p, s)only at position 0सिर्फ़ position 0 पर仅位置 0Match or NoneMatch या NoneMatch 或 None
re.fullmatch(p, s)the whole stringपूरी string整个字符串Match or NoneMatch या NoneMatch 或 None
re.findall(p, s)everywhereहर जगह所有位置list of strings (or tuples)strings (या tuples) की list字符串(或元组)列表
re.finditer(p, s)everywhereहर जगह所有位置iterator of Match objectsMatch objects का iteratorMatch 对象迭代器

search() — The First Match Anywheresearch() — कहीं भी पहला Matchsearch() — 任意位置的第一个匹配

import re

text = "The rain 42 falls 17 on 8 plains"

result = re.search(r"\d+", text)
print(result)
print(result.group(), result.start(), result.end())   # end is exclusive

# None when nothing matches - always check!
if re.search(r"xyz", text):
    print("found")
else:
    print("not found")
OUTPUT
<re.Match object; span=(9, 11), match='42'> 42 9 11 not found

match() and fullmatch() — Start Only, or the Whole Thingmatch() और fullmatch() — सिर्फ़ शुरुआत, या पूराmatch() 与 fullmatch() — 只看开头,或整体

import re

print(re.match(r"\d+", "42 items"))      # starts with digits
print(re.match(r"\d+", "order 42"))      # None - starts with 'o'

print(re.fullmatch(r"\d+", "42"))        # the WHOLE string must match
print(re.fullmatch(r"\d+", "42 items"))  # None - extra text

user_input = "560034"
if re.fullmatch(r"\d{6}", user_input):   # an Indian PIN code
    print("valid 6-digit code")
OUTPUT
<re.Match object; span=(0, 2), match='42'> None <re.Match object; span=(0, 2), match='42'> None valid 6-digit code
🆕
Python 3.15: prefixmatch()Python 3.15: prefixmatch()Python 3.15:prefixmatch()

Many people expect match() to check the whole string. Python 3.15 adds re.prefixmatch() — the same function with a clearer name — and soft-deprecates match(). Nothing breaks: match() stays, so use it when your code must run on older Pythons.

बहुत लोग मानते हैं कि match() पूरी string जाँचता है। Python 3.15 re.prefixmatch() जोड़ता है — वही function, साफ़ नाम के साथ — और match() को soft-deprecate करता है। कुछ नहीं टूटता: match() बना रहता है, इसलिए पुराने Python पर चलने वाले code में उसे ही इस्तेमाल करें।

很多人以为 match() 会检查整个字符串。Python 3.15 新增了 re.prefixmatch() — 同一个函数,名字更清楚 — 并对 match() 做了 软弃用。不会有任何东西坏掉:match() 会保留,需要兼容旧版 Python 的代码请继续用它。

findall() and finditer() — Every Matchfindall() और finditer() — हर Matchfindall() 与 finditer() — 所有匹配

import re

text = "The rain 42 falls 17 on 8 plains"
print(re.findall(r"\d+", text))

# With ONE group, findall returns just that group
print(re.findall(r"(\w+): \d+", "John: 25, Alice: 30, Bob: 22"))

# With TWO or more groups, it returns tuples
print(re.findall(r"(\w+): (\d+)", "John: 25, Alice: 30, Bob: 22"))
OUTPUT
['42', '17', '8'] ['John', 'Alice', 'Bob'] [('John', '25'), ('Alice', '30'), ('Bob', '22')]
👀
Groups Change What findall() ReturnsGroups बदलते हैं कि findall() क्या लौटाता है分组会改变 findall() 的返回值

No groups → the whole matches. One group → only that group. Two or more → tuples. This surprises everyone once: add a group "just for structure" and the result changes. Use a non-capturing group (?:...) when you only need brackets.

कोई group नहीं → पूरे matches। एक group → सिर्फ़ वह group। दो या ज़्यादा → tuples। यह हर किसी को एक बार चौंकाता है: "बस ढाँचे के लिए" एक group जोड़ें और नतीजा बदल जाता है। जब सिर्फ़ brackets चाहिए हों तो non-capturing group (?:...) इस्तेमाल करें।

没有分组 → 返回整个匹配。一个 分组 → 只返回该分组。两个及以上 → 返回元组。这会让每个人都吃一次惊:仅仅"为了结构"加个分组,结果就变了。只需要括号时,请用 非捕获 分组 (?:...)。

import re

text = "The rain 42 falls 17 on 8 plains"

# finditer yields Match objects one at a time - memory friendly
for m in re.finditer(r"\d+", text):
    print(f"{m.group()!r:5} at {m.start():2}..{m.end()}")
OUTPUT
'42' at 9..11 '17' at 18..20 '8' at 24..25

SectionSection第 04 节

Picture It — search vs match vs findall 🖼️तस्वीर में — search बनाम match बनाम findall 🖼️图解 — search、match 与 findall 🖼️

Same pattern \d+, same text, three answersवही pattern \d+, वही text, तीन जवाब同样的模式 \d+、同样的文本,三种答案
search match findallT0he rain 492 falls 1187 on 824 plainsre.match(r"\d+", text)Nonere.search(r"\d+", text)<Match span=(9, 11) '42'>re.findall(r"\d+", text)['42', '17', '8']

match: "does it start here?" · search: "is it anywhere?" · findall: "give me every one."

match: "क्या यहीं से शुरू होता है?" · search: "क्या कहीं है?" · findall: "मुझे हर एक दो।"

match:"是从这里开始吗?" · search:"在某处吗?" · findall:"全都给我。"


SectionSection第 05 节

Try It — The Regex Lab 🧪करके देखें — Regex Lab 🧪动手试试 — 正则实验室 🧪

Type any pattern and any text. The Lab shows exactly what Python would print, colours each match (and underlines each group), and lists every Match with its span. Start with a preset, then change one symbol at a time. The last preset is a trap — try it.

कोई भी pattern और कोई भी text लिखें। Lab ठीक वही दिखाता है जो Python छापता, हर match को रंगता है (और हर group को रेखांकित करता है), और हर Match को उसके span के साथ सूचीबद्ध करता है। किसी preset से शुरू करें, फिर एक बार में एक चिह्न बदलें। आख़िरी preset एक जाल है — आज़माएँ।

输入任意模式和任意文本。实验室会准确显示 Python 将打印的内容,为每个匹配上色(并给每个分组加下划线),列出每个 Match 及其 span。先从预设开始,然后一次改一个符号。最后一个预设是个陷阱 — 试试看。

🧪 Regex LabPython rules, live in your browser.Python के नियम, आपके browser में।Python 的规则,在你的浏览器里实时运行。
flags
presetspresets预设:
🔬
How the Lab Stays HonestLab कैसे सच्चा रहता है实验室如何保证准确

Your browser has its own regex engine with slightly different rules. The Lab translates each Python pattern first — Unicode \d and \w, Python's $, \A/\Z, (?P<name>...), atomic groups — and copies Python's rules for empty matches, findall groups, sub templates and split. It runs in a background worker, so a runaway pattern cannot freeze the page.

आपके browser का अपना regex engine है जिसके नियम थोड़े अलग हैं। Lab पहले हर Python pattern का अनुवाद करता है — Unicode \d और \w, Python का $, \A/\Z, (?P<name>...), atomic groups — और ख़ाली matches, findall groups, sub templates और split के लिए Python के नियम अपनाता है। यह background worker में चलता है, इसलिए बेक़ाबू pattern page को जमा नहीं सकता।

你的浏览器有自己的正则引擎,规则略有不同。实验室会先翻译每个 Python 模式 — Unicode 的 \d 和 \w、Python 的 $、\A/\Z、(?P<name>...)、原子分组 — 并照搬 Python 对空匹配、findall 分组、sub 模板和 split 的规则。它在后台 worker 中运行,所以失控的模式不会卡死页面。


SectionSection第 06 节

The Editing Functions — sub, subn, split, compile ✂️बदलने वाले Functions — sub, subn, split, compile ✂️编辑函数 — sub、subn、split、compile ✂️

sub() and subn() — Search and Replacesub() और subn() — खोजो और बदलोsub() 与 subn() — 查找并替换

In the replacement, \1 or \g<1> inserts group 1, and \g<name> inserts a named group. The replacement can also be a function that receives each Match and returns a string.

Replacement में \1 या \g<1> group 1 डालता है, और \g<name> named group डालता है। Replacement एक function भी हो सकता है जो हर Match पाकर एक string लौटाए।

在替换串中,\1 或 \g<1> 插入第 1 组,\g<name> 插入命名分组。替换也可以是一个 函数,它接收每个 Match 并返回字符串。

import re

print(re.sub(r"\d", "#", "Order 42 for ₹17.50"))
print(re.sub(r"(\w+) (\w+)", r"\2 \1", "Alice Smith"))          # swap with \1 \2
print(re.sub(r"(?P<first>\w+) (?P<last>\w+)", r"\g<last>, \g<first>", "Alice Smith"))
print(re.sub(r"\d", "#", "1-2-3-4-5", count=2))                 # count= as a keyword!

def double(m):
    return str(int(m.group()) * 2)

print(re.sub(r"\d+", double, "a 3 b 5 c 7"))                    # replacement function

new_text, n = re.subn(r"\d", "#", "1-2-3-4-5")
print(new_text, n)
OUTPUT
Order ## for ₹##.## Smith Alice Smith, Alice #-#-3-4-5 a 6 b 10 c 14 #-#-#-#-# 5

split() — Split by a Patternsplit() — Pattern से तोड़नाsplit() — 按模式分割

import re

print(re.split(r"\s+", "one    two\tthree\nfour"))
print(re.split(r"[,;|]", "apple,banana;cherry|kiwi"))
print(re.split(r"-", "a-b-c-d-e", maxsplit=2))      # maxsplit= as a keyword!

# A capturing group KEEPS the separators in the result
print(re.split(r"([,;])", "a,b;c"))
OUTPUT
['one', 'two', 'three', 'four'] ['apple', 'banana', 'cherry', 'kiwi'] ['a', 'b', 'c-d-e'] ['a', ',', 'b', ';', 'c']
🆕
Python 3.13: Pass count, maxsplit and flags by NamePython 3.13: count, maxsplit और flags नाम से देंPython 3.13:count、maxsplit、flags 请按名字传

re.sub(p, r, s, 2) and re.split(p, s, 2) still work, but since Python 3.13 they raise a DeprecationWarning, and a future version will reject them. A classic bug hides here too: re.sub(p, r, s, re.I) passes the flag as count! Always write count=, maxsplit= and flags=.

re.sub(p, r, s, 2) और re.split(p, s, 2) अब भी चलते हैं, पर Python 3.13 से ये DeprecationWarning देते हैं, और कोई भविष्य का version इन्हें मना कर देगा। यहाँ एक पुराना bug भी छिपा है: re.sub(p, r, s, re.I) flag को count के रूप में देता है! हमेशा count=, maxsplit= और flags= लिखें।

re.sub(p, r, s, 2) 和 re.split(p, s, 2) 仍能运行,但从 Python 3.13 起会发出 DeprecationWarning,未来版本将拒绝这种写法。这里还藏着一个经典 bug:re.sub(p, r, s, re.I) 会把标志当成 count 传进去!请始终写 count=、maxsplit= 和 flags=。

compile() — Name a Pattern and Reuse Itcompile() — Pattern को नाम दें और दोबारा इस्तेमाल करेंcompile() — 给模式命名并复用

import re

digits = re.compile(r"\d+")            # compile once ...
print(digits.findall("a 12 b 34"))     # ... use many times
print(digits.search("only 99 here"))
print(digits.sub("#", "1 to 100"))
print(digits.pattern, digits.flags)

email_re = re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+")
log_lines = ["mail alice@example.com now", "nothing here", "cc bob.k+news@my-site.co.in"]
for line in log_lines:
    for email in email_re.findall(line):
        print(email)
OUTPUT
['12', '34'] <re.Match object; span=(5, 7), match='99'> # to # \d+ 32 alice@example.com bob.k+news@my-site.co.in

Speed is a small reason: the re module already keeps the last 512 patterns you used in a cache. The bigger reasons are readability (a well-named constant like EMAIL_RE) and keeping flags in one place.

रफ़्तार छोटी वजह है: re module पहले से ही आपके इस्तेमाल किए आख़िरी 512 patterns cache में रखता है। बड़ी वजहें हैं पढ़ने में आसानी (EMAIL_RE जैसा अच्छे नाम वाला constant) और flags को एक जगह रखना।

速度只是次要原因:re 模块本来就会缓存你最近用过的 512 个模式。更重要的原因是 可读性(像 EMAIL_RE 这样命名良好的常量),以及把标志集中在一处。


SectionSection第 07 节

The Match Object — What You Get Back 🎁Match Object — आपको क्या मिलता है 🎁Match 对象 — 你得到了什么 🎁

import re

m = re.search(r"(\w+)@(\w+\.\w+)", "Contact: alice@example.com today")

print(m.group())            # the whole match = group(0)
print(m.group(1), m.group(2))
print(m[1], m[2])           # m[n] is a short form of m.group(n)
print(m.groups())
print(m.start(), m.end(), m.span())
print(m.span(2))            # span of group 2 only

m2 = re.search(r"(?P<user>\w+)@(?P<domain>\w+\.\w+)", "alice@example.com")
print(m2.group("user"), m2["domain"])
print(m2.groupdict())

if m3 := re.search(r"\d+", "no digits here"):   # walrus: assign and test
    print(m3.group())
else:
    print("no match - and no AttributeError")
OUTPUT
alice@example.com alice example.com alice example.com ('alice', 'example.com') 9 26 (9, 26) (15, 26) alice example.com {'user': 'alice', 'domain': 'example.com'} no match - and no AttributeError
🔑
Always Check for None Before .group().group() से पहले हमेशा None जाँचें调用 .group() 前务必检查 None

When nothing matches, search returns None, and None.group() raises AttributeError. Use if m := re.search(...): (the walrus operator, Python 3.8+) to test and keep the match in one line.

कुछ match न हो तो search None लौटाता है, और None.group() AttributeError देता है। एक ही line में जाँचने और match रखने के लिए if m := re.search(...): (walrus operator, Python 3.8+) इस्तेमाल करें।

没有匹配时,search 返回 None,而 None.group() 会抛出 AttributeError。用 if m := re.search(...):(海象运算符,Python 3.8+)在一行里同时判断并保存匹配。


SectionSection第 08 节

Character Classes — Which Characters? 🔤Character Classes — कौन-से अक्षर? 🔤字符类 — 哪些字符?🔤

PatternPattern模式Meaning (Python 3, str patterns)मतलब (Python 3, str patterns)含义(Python 3,str 模式)Exampleउदाहरण示例
.any character except newlinenewline को छोड़कर कोई भी अक्षर除换行外的任意字符a 5 @ ␣
\dany Unicode decimal digitकोई भी Unicode दशमलव अंक任意 Unicode 十进制数字7 ५ ٣
\Dany non-digitकोई भी ग़ैर-अंक任意非数字a @ ␣
\wa letter or digit of any language, or _किसी भी भाषा का अक्षर या अंक, या _任何语言 的字母或数字,或 _a é 中 _
\Wanything that is not \wजो \w नहीं है非 \w 的字符@ ␣ !
\swhitespace: space, tab, newline, …whitespace: space, tab, newline, …空白:空格、制表、换行……␣ ⇥ ⏎
\Sanything that is not whitespaceजो whitespace नहीं है非空白字符a 5 @
[abc]one of a, b or ca, b या c में से एकa、b、c 之一a b c
[a-z]a rangeएक range一个范围a … z
[^abc]anything except a, b, ca, b, c को छोड़कर कुछ भीa、b、c 以外 的任意字符d @ 1
import re

print(re.findall(r"c.t", "cat cot cut c@t c t"))
print(re.findall(r"\d\d\d", "call 999 or 112"))
print(re.findall(r"[aeiou]", "hello world"))
print(re.findall(r"[^aeiou\s]", "hello world"))

# Surprise: in Python 3, \d and \w understand ALL languages
print(re.findall(r"\d+", "Price: ४५० or 450"))
print(re.findall(r"\d+", "Price: ४५० or 450", flags=re.ASCII))
print(re.findall(r"\w+", "café 中文 नमस्ते"))
OUTPUT
['cat', 'cot', 'cut', 'c@t', 'c t'] ['999', '112'] ['e', 'o', 'o'] ['h', 'l', 'l', 'w', 'r', 'l', 'd'] ['४५०', '450'] ['450'] ['café', '中文', 'नमस', 'त']
🌏
\d and \w Speak Every Language — Mostly\d और \w हर भाषा बोलते हैं — ज़्यादातर\d 和 \w 懂所有语言 — 大体上

Many tutorials say \d means 0-9. In Python 3 that is only true with re.ASCII: by default \d also matches ४५०. If you validate an Indian PIN or an amount, add re.ASCII or write [0-9]. And \w skips Indian combining marks (vowel signs and the virama ्), so \w+ splits नमस्ते into नमस and त. For Hindi words, the third-party regex module gets it right: regex.findall(r"\w+", "नमस्ते") → ['नमस्ते'].

बहुत से tutorials कहते हैं कि \d का मतलब 0-9 है। Python 3 में यह सिर्फ़ re.ASCII के साथ सच है: default रूप से \d ४५० से भी match करता है। भारतीय PIN या रक़म जाँचनी हो तो re.ASCII जोड़ें या [0-9] लिखें। और \w भारतीय मात्राओं (स्वर-चिह्न और हलंत ्) को छोड़ देता है, इसलिए \w+ नमस्ते को नमस और त में बाँटता है। हिंदी शब्दों के लिए third-party regex module सही काम करता है: regex.findall(r"\w+", "नमस्ते") → ['नमस्ते']।

很多教程说 \d 表示 0-9。在 Python 3 中,这只在加了 re.ASCII 时成立:默认情况下 \d 也会匹配 ४५०。校验印度 PIN 码或金额时,请加 re.ASCII 或写 [0-9]。另外,\w 会跳过印度文字的 组合符号(元音符号和 virama ्),所以 \w+ 会把 नमस्ते 拆成 नमस 和 त。处理印地语单词时,第三方 regex 模块能正确处理:regex.findall(r"\w+", "नमस्ते") → ['नमस्ते']。

Try It — Character Class Explorerकरके देखें — Character Class Explorer动手试试 — 字符类探索器

Pick a class and watch which of these 20 characters it accepts. Toggle re.ASCII and re.IGNORECASE. Every tile was decided by real Python with re.fullmatch.

एक class चुनें और देखें कि इन 20 अक्षरों में से कौन-से वह स्वीकार करती है। re.ASCII और re.IGNORECASE बदलें। हर tile का फ़ैसला असली Python ने re.fullmatch से किया है।

选一个字符类,看看这 20 个字符中它接受哪些。切换 re.ASCII 和 re.IGNORECASE。每个方块都是由真实 Python 用 re.fullmatch 判定的。

🔤 Character Class ExplorerWhich characters does it accept?यह कौन-से अक्षर स्वीकार करता है?它接受哪些字符?
re.ASCIIre.IGNORECASE

SectionSection第 09 节

Anchors — Where in the Text? ⚓Anchors — Text में कहाँ? ⚓锚点 — 文本中的位置 ⚓

Anchors match positions, not characters. They take up zero width.

Anchors अक्षरों से नहीं, positions से match करते हैं। उनकी चौड़ाई शून्य होती है।

锚点匹配的是 位置 而不是字符,宽度为零。

AnchorAnchor锚点Matches atकहाँ match करता है匹配位置
^start of the string (start of every line with re.M)string की शुरुआत (re.M के साथ हर line की शुरुआत)字符串开头(加 re.M 时为每行开头)
$end of the string or just before a final newline (end of every line with re.M)string का अंत या आख़िरी newline से ठीक पहले (re.M के साथ हर line का अंत)字符串结尾 或最后一个换行符之前(加 re.M 时为每行结尾)
\Astart of the string only, alwaysसिर्फ़ string की शुरुआत, हमेशा始终只匹配字符串开头
\Z / \zthe very end of the string only (\z is new in 3.14)सिर्फ़ string का बिल्कुल अंत (\z 3.14 में नया)只匹配字符串的最末尾(\z 为 3.14 新增)
\ba word edge: between \w and \W (or the string edge)शब्द का किनारा: \w और \W के बीच (या string का किनारा)单词边界:\w 与 \W 之间(或字符串边缘)
\Banywhere that is not a word edgeजहाँ शब्द का किनारा नहीं है非单词边界的位置
import re

print(re.search(r"^Hello", "Hello world"))
print(re.search(r"^Hello", "Say Hello"))
print(re.search(r"world$", "Hello world"))

print(re.findall(r"\bcat\b", "cat cats concatenate"))
print(re.findall(r"cat", "cat cats concatenate"))
print(re.findall(r"\Bcat\B", "cat cats concatenate"))

# $ also matches just BEFORE a final newline; \Z only at the very end
print(re.search(r"\d+$", "42\n"))
print(re.search(r"\d+\Z", "42\n"))
OUTPUT
<re.Match object; span=(0, 5), match='Hello'> None <re.Match object; span=(6, 11), match='world'> ['cat'] ['cat', 'cat', 'cat'] ['cat'] <re.Match object; span=(0, 2), match='42'> None
⏎
The $ Trap$ का जाल$ 陷阱

$ happily matches before a trailing \n, so re.match(r"\d+$", "42\n") says yes. When you validate input, use re.fullmatch() or end with \Z.

$ आख़िरी \n से पहले भी match कर लेता है, इसलिए re.match(r"\d+$", "42\n") हाँ कहता है। Input जाँचते समय re.fullmatch() इस्तेमाल करें या \Z पर ख़त्म करें।

$ 会在末尾的 \n 之前照样匹配,所以 re.match(r"\d+$", "42\n") 会返回匹配。校验输入时,请用 re.fullmatch() 或以 \Z 结尾。


SectionSection第 10 节

Quantifiers — How Many? 🔁Quantifiers — कितने? 🔁量词 — 几次?🔁

QuantifierQuantifier量词Meaningमतलब含义Exampleउदाहरण示例
*0 or more0 या ज़्यादा0 次或多次a* → "", a, aaa
+1 or more1 या ज़्यादा1 次或多次\d+ → 7, 2026
?0 or 1 (optional)0 या 1 (वैकल्पिक)0 或 1 次(可选)colou?r → color, colour
{n}exactly nठीक n恰好 n 次\d{6} → 560034
{n,} / {,m}at least n / at most mकम से कम n / ज़्यादा से ज़्यादा m至少 n 次 / 至多 m 次\d{3,}
{n,m}between n and mn और m के बीचn 到 m 次\d{2,4}
*? +? ?? {n,m}?lazy: as few as possiblelazy: जितना कम हो सके懒惰:尽可能少<.+?>
*+ ++ ?+possessive (3.11+): never give backpossessive (3.11+): कभी वापस नहीं देता占有(3.11+):绝不回吐a++b
import re

print(re.findall(r"\d+", "a1 b22 c333"))
print(re.findall(r"\d{2,3}", "1 22 333 4444"))
print(re.findall(r"colou?r", "color colour colouur"))

text = "<b>bold</b> and <i>italic</i>"
print(re.findall(r"<.+>", text))       # GREEDY: as much as possible
print(re.findall(r"<.+?>", text))      # LAZY: as little as possible
print(re.findall(r"<[^>]+>", text))    # a negated class: often the best choice
OUTPUT
['1', '22', '333'] ['22', '333', '444'] ['color', 'colour'] ['<b>bold</b> and <i>italic</i>'] ['<b>', '</b>', '<i>', '</i>'] ['<b>', '</b>', '<i>', '</i>']

Try It — Engine Step-Throughकरके देखें — Engine Step-Through动手试试 — 引擎单步演示

How does the engine decide? It tries one start position at a time and walks along the pattern. A greedy .+ eats everything, then backtracks one character at a time. A lazy .+? takes one character, then checks whether the rest can already match. Compare the step counts.

Engine कैसे तय करता है? यह एक बार में एक शुरुआती position आज़माता है और pattern के साथ चलता है। Greedy .+ सब कुछ खा जाता है, फिर एक-एक अक्षर backtrack करता है। Lazy .+? एक अक्षर लेता है, फिर जाँचता है कि क्या बाक़ी हिस्सा अभी match हो सकता है। कदमों की गिनती की तुलना करें।

引擎如何做决定?它一次尝试一个起始位置,沿着模式前进。贪婪 的 .+ 先吃掉一切,再逐个字符 回溯。懒惰 的 .+? 先只拿一个字符,然后检查其余部分能否匹配。比较一下步数。

🐢 Engine Step-ThroughWatch the engine try, eat, and back up.Engine को आज़माते, खाते और पीछे हटते देखें।看引擎尝试、吞吃、回退。
texttext文本
pattern tokenspattern tokens模式记号
⚠️
The Greedy TrapGreedy जाल贪婪陷阱

Quantifiers are greedy by default. Between delimiters (tags, quotes, brackets), .+ runs to the last closing mark. Use lazy .+? — or even better a negated class like [^>]+, which cannot cross the closing mark at all and needs fewer steps.

Quantifiers default रूप से greedy होते हैं। Delimiters (tags, quotes, brackets) के बीच .+ आख़िरी closing चिह्न तक चला जाता है। Lazy .+? इस्तेमाल करें — या उससे भी बेहतर [^>]+ जैसी negated class, जो closing चिह्न को पार ही नहीं कर सकती और कम कदम लेती है।

量词默认是 贪婪 的。在分隔符(标签、引号、括号)之间,.+ 会一直跑到 最后一个 结束符。请用懒惰的 .+? — 更好的是用 [^>]+ 这样的否定类,它根本无法越过结束符,步数也更少。


SectionSection第 11 节

Groups, Alternation and Lookarounds 🧷Groups, Alternation और Lookarounds 🧷分组、选择与环视 🧷

PatternPattern模式Meaningमतलब含义Exampleउदाहरण示例
(...)capturing group — remember what matchedcapturing group — जो match हुआ उसे याद रखो捕获分组 — 记住匹配内容(\d+) → group(1)
(?:...)group without capturingबिना capture का group非捕获分组(?:https?|ftp)
(?P<name>...)named groupनाम वाला group命名分组(?P<year>\d{4})
a|ba OR ba या ba 或 bcat|dog
\1, (?P=name)the same text again (backreference)वही text फिर से (backreference)再次出现相同文本(反向引用)(\w)\1 → oo
(?=...) / (?!...)followed by / NOT followed by (lookahead)आगे है / आगे नहीं है (lookahead)后面是 / 后面不是(先行断言)\d+(?=px)
(?<=...) / (?<!...)preceded by / NOT preceded by (lookbehind; fixed width)पहले है / पहले नहीं है (lookbehind; तय चौड़ाई)前面是 / 前面不是(后行断言;固定宽度)(?<=₹)\d+
(?>...)atomic group (3.11+): no backtracking insideatomic group (3.11+): अंदर backtracking नहीं原子分组(3.11+):内部不回溯(?>a+)b
import re

m = re.search(r"(\d{4})-\d{2}-\d{2}", "Born: 1990-03-15")
print(m.group(1))

print(re.findall(r"(?:https?|ftp)://\S+", "visit https://a.com or ftp://b.com"))
print(re.findall(r"(https?|ftp)://\S+", "visit https://a.com or ftp://b.com"))  # capturing!

print(re.findall(r"\b(cat|dog|fish)\b", "I have a cat, a dog, and a fish."))

m = re.search(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})", "Date: 2026-07-12")
print(m["year"], m["month"], m["day"])

print(re.findall(r"\d+(?=px)", "width: 100px, height: 200em"))    # lookahead
print(re.findall(r"\d+(?!\d|px)", "width: 100px, height: 200em"))  # negative lookahead
print(re.findall(r"(?<=₹)\d+", "pay ₹500 not $300"))               # lookbehind
print(re.search(r"(\w)\1", "bookkeeper"))                           # backreference
OUTPUT
1990 ['https://a.com', 'ftp://b.com'] ['https', 'ftp'] ['cat', 'dog', 'fish'] 2026 07 12 ['100'] ['200'] ['500'] <re.Match object; span=(1, 3), match='oo'>

Look at \d+(?!\d|px): a plain \d+(?!px) would still return '10' from 100px, because the engine backtracks to a shorter number that is not followed by px. Lookarounds check, but the quantifier before them can still move.

\d+(?!\d|px) देखें: सादा \d+(?!px) 100px से भी '10' लौटा देता, क्योंकि engine एक छोटे नंबर तक backtrack करता है जिसके आगे px नहीं है। Lookarounds जाँचते हैं, पर उनसे पहले का quantifier फिर भी खिसक सकता है।

看 \d+(?!\d|px):单纯的 \d+(?!px) 仍会从 100px 中返回 '10',因为引擎会回溯到一个后面不是 px 的更短数字。环视只负责检查,但它前面的量词仍然可以移动。


SectionSection第 12 节

Anatomy of an Email Regex 🧬Email Regex की बनावट 🧬邮箱正则的解剖 🧬

A real-world pattern is a short sentence made of small pieces. Here is a simple email matcher, one piece at a time.

असली pattern छोटे टुकड़ों से बना एक छोटा वाक्य होता है। यहाँ एक सादा email matcher है, एक-एक टुकड़ा करके।

真实世界的模式是由小片段组成的短句。下面是一个简单的邮箱匹配器,逐段拆解。

\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b
email regex anatomy①\b①word edgeशब्द का किनारा单词边界②[A-Za-z0-9._%+-]+②user name: 1+ of theseuser name: इनमें से 1+用户名:1 个以上这些字符③@③a literal @सादा @字面 @④[A-Za-z0-9.-]+④domain: letters, digits, . -domain: अक्षर, अंक, . -域名:字母、数字、. -⑤\.⑤an escaped, literal dotescape किया सादा बिंदु转义后的字面点⑥[A-Za-z]{2,}⑥top-level domain: 2+ letterstop-level domain: 2+ अक्षर顶级域名:2 个以上字母⑦\b⑦word edgeशब्द का किनारा单词边界matches:match करता है:可匹配:priya.s@iit.ac.in bob.k+news@my-site.co.in

Purple: anchors · blue: user · red: literal separators · green: domain · amber: top-level domain.

बैंगनी: anchors · नीला: user · लाल: सादे separators · हरा: domain · पीला: top-level domain।

紫色:锚点 · 蓝色:用户名 · 红色:字面分隔符 · 绿色:域名 · 琥珀色:顶级域名。

📧
"Simple" Is the Honest Word"सादा" ही सही शब्द है"简单"才是诚实的说法

This pattern catches ordinary addresses, but real email rules are far more complex, and no short regex follows them all. To check an address you will really use, do a light regex check and then send a confirmation email.

यह pattern सामान्य addresses पकड़ता है, पर असली email नियम कहीं ज़्यादा जटिल हैं, और कोई छोटा regex उन सबको नहीं मानता। जिस address को सच में इस्तेमाल करना है, उसके लिए हल्की regex जाँच करें और फिर confirmation email भेजें।

这个模式能抓住普通地址,但真实的邮箱规则复杂得多,没有哪个短正则能全部遵守。要验证真正会用的地址,先做轻量的正则检查,再发送确认邮件。


SectionSection第 13 节

Flags — Change How Matching Works 🚩Flags — Matching का तरीका बदलें 🚩标志 — 改变匹配方式 🚩

FlagShortछोटा简写Effectअसर作用
re.IGNORECASEre.I · (?i)ignore upper/lower caseबड़े/छोटे अक्षर का फ़र्क़ भूलो忽略大小写
re.MULTILINEre.M · (?m)^ and $ work at every line^ और $ हर line पर काम करते हैं^ 和 $ 作用于每一行
re.DOTALLre.S · (?s). also matches newline. newline से भी match करता है. 也匹配换行
re.VERBOSEre.X · (?x)spaces and # comments allowed in the patternPattern में spaces और # comments की अनुमति模式中允许空格和 # 注释
re.ASCIIre.A · (?a)\d \w \s \b mean ASCII only\d \w \s \b सिर्फ़ ASCII\d \w \s \b 仅限 ASCII
import re

text = "Hello World\nGoodbye World"

print(re.findall(r"hello", text, flags=re.IGNORECASE))
print(re.findall(r"^\w+", text))
print(re.findall(r"^\w+", text, flags=re.MULTILINE))
print(re.findall(r"Hello.+World", text))
print(re.findall(r"Hello.+World", text, flags=re.DOTALL))

email = re.compile(r"""
    [A-Za-z0-9._%+-]+     # user name
    @                     # the at sign
    [A-Za-z0-9.-]+        # domain
    \.[A-Za-z]{2,}        # dot + top-level domain
""", re.VERBOSE)
print(email.findall("write to priya.s@iit.ac.in or info@example.com"))

log = "ERROR disk full\nok\nerror retry"
print(re.findall(r"^error.*", log, flags=re.IGNORECASE | re.MULTILINE))
print(re.findall(r"(?i)^error.*", log))          # inline flag at the start
OUTPUT
['Hello'] ['Hello'] ['Hello', 'Goodbye'] ['Hello World'] ['Hello World\nGoodbye World'] ['priya.s@iit.ac.in', 'info@example.com'] ['ERROR disk full', 'error retry'] ['ERROR disk full']

Combine flags with |. An inline flag like (?i) must be at the very start of the pattern (an error since Python 3.11 otherwise). To change a flag for one part only, use (?i:...).

Flags को | से जोड़ें। (?i) जैसा inline flag pattern की बिल्कुल शुरुआत में होना चाहिए (वरना Python 3.11 से error)। सिर्फ़ एक हिस्से के लिए flag बदलना हो तो (?i:...) इस्तेमाल करें।

用 | 组合多个标志。像 (?i) 这样的内联标志必须放在模式的最 开头(否则从 Python 3.11 起会报错)。只想对一部分生效,请用 (?i:...)。


SectionSection第 14 节

Catastrophic Backtracking 💣Catastrophic Backtracking 💣灾难性回溯 💣

A pattern like (a+)+b has a quantifier inside a quantifier. When the text almost matches — many as but no b — the engine tries every possible way to divide the as between the inner and outer + before giving up. The number of ways doubles with each extra a.

(a+)+b जैसे pattern में quantifier के अंदर quantifier है। जब text लगभग match करता है — बहुत से a पर कोई b नहीं — तो engine हार मानने से पहले a को अंदर और बाहर के + में बाँटने का हर तरीका आज़माता है। हर अतिरिक्त a के साथ तरीकों की संख्या दोगुनी होती है।

像 (a+)+b 这样的模式,量词里面套着量词。当文本 几乎 匹配时 — 很多 a 但没有 b — 引擎会在放弃前尝试把这些 a 分配给内外两个 + 的每一种方式。每多一个 a,方式数就翻倍。

import re, time

for n in (16, 18, 20, 22):
    text = "a" * n + "c"                     # almost matches - but no 'b'
    t = time.perf_counter()
    re.match(r"(a+)+b", text)                # nested quantifiers
    slow = time.perf_counter() - t
    t = time.perf_counter()
    re.match(r"(?>a+)+b", text)              # atomic group (Python 3.11+)
    fast = time.perf_counter() - t
    print(f"n={n}: nested {slow * 1000:8.2f} ms   atomic {fast * 1000:.3f} ms")
OUTPUT (times from the build machine)OUTPUT (build machine का समय)输出(构建机器上的耗时)
n=16: nested 5.41 ms atomic 0.252 ms n=18: nested 21.45 ms atomic 0.022 ms n=20: nested 86.56 ms atomic 0.021 ms n=22: nested 253.58 ms atomic 0.042 ms

Try It — Backtracking Raceकरके देखें — Backtracking Race动手试试 — 回溯赛跑

Slide the number of as and compare four patterns that all mean "some a's then b". The times were measured with real Python on our build machine (on a log scale — each grid step is ×10). Yours will differ, but the doubling will not.

a की संख्या slide करें और चार patterns की तुलना करें जिनका मतलब एक ही है: "कुछ a फिर b"। समय हमारी build machine पर असली Python से नापे गए (log scale पर — हर कदम ×10)। आपके समय अलग होंगे, पर दोगुना होना नहीं बदलेगा।

拖动 a 的数量,比较四个都表示"若干 a 再接 b"的模式。这些耗时是在我们的构建机器上用真实 Python 测得的(对数刻度 — 每格 ×10)。你的数值会不同,但翻倍规律不会变。

💣 Backtracking RaceSame meaning, wildly different cost.मतलब वही, क़ीमत बिल्कुल अलग।含义相同,代价天差地别。
n =
re.match(pattern, )
(a+)+b
a+b
(?>a+)+b
a++b
🛡️
The Cureइलाज解决办法

① Remove the nesting: (a+)+b means the same as a+b. ② Since Python 3.11, use an atomic group (?>...) or a possessive quantifier a++: once they match, they never give characters back, so there is nothing to retry. ③ Prefer specific classes like [^"]* over .*. ④ Never run an untrusted user's pattern on your server.

① Nesting हटाएँ: (a+)+b का मतलब वही है जो a+b का। ② Python 3.11 से atomic group (?>...) या possessive quantifier a++ इस्तेमाल करें: एक बार match करने के बाद ये अक्षर कभी वापस नहीं देते, इसलिए दोबारा आज़माने को कुछ नहीं बचता। ③ .* के बजाय [^"]* जैसी ख़ास classes चुनें। ④ किसी अनजान user का pattern अपने server पर कभी न चलाएँ।

① 去掉嵌套:(a+)+b 与 a+b 含义相同。② 从 Python 3.11 起,使用 原子分组 (?>...) 或 占有量词 a++:一旦匹配,它们绝不回吐字符,也就没有什么可重试的。③ 优先用 [^"]* 这样具体的字符类,而不是 .*。④ 绝不要在服务器上运行不可信用户提供的模式。


SectionSection第 15 节

A Tested Pattern Library 📚जाँची हुई Pattern Library 📚经过测试的模式库 📚

Each pattern below was run by Python on the sample text; the last column is the real findall result. Notice the limits — they are part of the lesson.

नीचे का हर pattern Python ने नमूना text पर चलाया; आख़िरी column असली findall नतीजा है। सीमाओं पर ध्यान दें — वे भी सबक़ का हिस्सा हैं।

下面每个模式都由 Python 在示例文本上运行过;最后一列是真实的 findall 结果。注意它们的局限 — 那也是课程的一部分。

Whatक्या用途PatternSampleनमूना示例findall
Email (simple)Email (सादा)邮箱(简单版)[\w.+-]+@[\w-]+\.[\w.-]+mail a.b+x@my-site.co.in now['a.b+x@my-site.co.in']
URLURLURLhttps?://[^\s,]+see https://x.org/a?b=1, ok['https://x.org/a?b=1']
Indian mobileभारतीय mobile印度手机号(?:\+91[\s-]?)?\b[6-9]\d{9}\b+91 9876543210, 5123456789['+91 9876543210']
Indian PIN codeभारतीय PIN code印度邮编\b[1-9][0-9]{5}\bBaddi 173205, Delhi 110001, 012345['173205', '110001']
IPv4 (shape only!)IPv4 (सिर्फ़ आकार!)IPv4(仅形状!)\b(?:\d{1,3}\.){3}\d{1,3}\bip 192.168.1.1 and 999.1.1.1['192.168.1.1', '999.1.1.1']
ISO dateISO तारीख़ISO 日期\b\d{4}-\d{2}-\d{2}\bon 2026-07-12 or 2026-13-45['2026-07-12', '2026-13-45']
Hex colourHex रंग十六进制颜色#[0-9A-Fa-f]{6}\b#1a2B3c and #12345g['#1a2B3c']
HashtagHashtag话题标签#\w+#Python #100DaysOfCode['#Python', '#100DaysOfCode']
Signed decimalSigned decimal带符号小数[-+]?\d*\.?\d+-3.5 +2 .7 10['-3.5', '+2', '.7', '10']
Repeated wordदोहराया शब्द重复单词\b(\w+) \1\bthis is is a test test['is', 'test']
HTML tag (flat text only)HTML tag (सिर्फ़ सादा text)HTML 标签(仅限扁平文本)<[^>]+><p>Hi <b>there</b></p>['<p>', '<b>', '</b>', '</p>']
🧱
Shape Is Not Meaningआकार मतलब नहीं है形状不等于含义

The IPv4 pattern accepts 999.1.1.1, and the date pattern accepts 2026-13-45. A regex checks the shape; check the meaning with real tools: ipaddress.ip_address(), datetime.strptime().

IPv4 pattern 999.1.1.1 को स्वीकार करता है, और तारीख़ वाला pattern 2026-13-45 को। Regex आकार जाँचता है; मतलब असली औज़ारों से जाँचें: ipaddress.ip_address(), datetime.strptime()।

IPv4 模式会接受 999.1.1.1,日期模式会接受 2026-13-45。正则只检查 形状;含义 要用真正的工具检查:ipaddress.ip_address()、datetime.strptime()。


SectionSection第 16 节

Exercises 🏋️अभ्यास 🏋️练习 🏋️

Six problems, from easy to advanced. Try each one in the Regex Lab before reading the solution.

छह सवाल, आसान से कठिन तक। हल पढ़ने से पहले हर एक को Regex Lab में आज़माएँ।

六道题,由易到难。看答案之前,先在正则实验室里试一试。

1 · Extract All Numbers1 · सारे नंबर निकालें1 · 提取所有数字

📝
Problemसवाल题目

From "Order 42 for ₹17.50 delivered on day 3", get every number, including decimals. Then see what one capturing group does to findall.

"Order 42 for ₹17.50 delivered on day 3" से हर नंबर निकालें, दशमलव समेत। फिर देखें कि एक capturing group findall के साथ क्या करता है।

从 "Order 42 for ₹17.50 delivered on day 3" 中取出所有数字(包括小数)。然后看看一个捕获分组会对 findall 造成什么影响。

import re

text = "Order 42 for ₹17.50 delivered on day 3"
print(re.findall(r"\d+(?:\.\d+)?", text))
print(re.findall(r"\d+(\.\d+)?", text))      # capturing group: a different result!
OUTPUT
['42', '17.50', '3'] ['', '.50', '']

2 · Validate a UK Postcode2 · UK Postcode जाँचें2 · 校验英国邮编

📝
Problemसवाल题目

Return True for shapes like SW1A 1AA or M1 1AE: 1–2 letters, a digit, an optional letter or digit, a space, a digit, two letters.

SW1A 1AA या M1 1AE जैसे आकारों के लिए True लौटाएँ: 1–2 अक्षर, एक अंक, वैकल्पिक अक्षर या अंक, एक space, एक अंक, दो अक्षर।

对 SW1A 1AA 或 M1 1AE 这样的形状返回 True:1–2 个字母、一个数字、可选的字母或数字、一个空格、一个数字、两个字母。

import re

def is_uk_postcode(text):
    return re.fullmatch(r"[A-Z]{1,2}\d[A-Z\d]? \d[A-Z]{2}", text) is not None

for code in ["SW1A 1AA", "M1 1AE", "invalid", "SW1A 1AAA", "sw1a 1aa"]:
    print(f"{code!r:12} {is_uk_postcode(code)}")
OUTPUT
'SW1A 1AA' True 'M1 1AE' True 'invalid' False 'SW1A 1AAA' False 'sw1a 1aa' False

3 · Mentions and Hashtags3 · Mentions और Hashtags3 · 提及与话题标签

📝
Problemसवाल题目

Return the names after @ and #, without the symbols — but do not treat an email address as a mention.

@ और # के बाद के नाम लौटाएँ, चिह्नों के बिना — पर email address को mention न मानें।

返回 @ 和 # 后面的名字(不含符号)— 但不要把邮箱地址当成提及。

import re

post = "Loving @python on #Sunday with @friends #100DaysOfCode. Mail me@x.com"

mentions = re.findall(r"(?<!\w)@(\w+)", post)   # not after a letter: skips me@x.com
hashtags = re.findall(r"#(\w+)", post)
print("Mentions:", mentions)
print("Hashtags:", hashtags)
OUTPUT
Mentions: ['python', 'friends'] Hashtags: ['Sunday', '100DaysOfCode']

4 · Parse Log Lines4 · Log Lines parse करें4 · 解析日志行

📝
Problemसवाल题目

Split "[2026-07-12 14:30:15] INFO User logged in" into time, level and message with named groups, and skip lines that do not fit.

Named groups से "[2026-07-12 14:30:15] INFO User logged in" को time, level और message में बाँटें, और जो lines फ़िट न हों उन्हें छोड़ें।

用命名分组把 "[2026-07-12 14:30:15] INFO User logged in" 拆成时间、级别和消息,并跳过不符合格式的行。

import re

pattern = re.compile(r"""
    \[(?P<time>\d{4}-\d{2}-\d{2}\ \d{2}:\d{2}:\d{2})\]   # [timestamp]
    \s+(?P<level>[A-Z]+)                                  # level
    \s+(?P<msg>.+)                                        # the rest
""", re.VERBOSE)

for line in ["[2026-07-12 14:30:15] INFO User logged in",
             "[2026-07-12 14:31:02] ERROR Disk full",
             "garbage line"]:
    if m := pattern.match(line):
        print(m.groupdict())
    else:
        print("no match:", repr(line))
OUTPUT
{'time': '2026-07-12 14:30:15', 'level': 'INFO', 'msg': 'User logged in'} {'time': '2026-07-12 14:31:02', 'level': 'ERROR', 'msg': 'Disk full'} no match: 'garbage line'

5 · Redact Sensitive Data5 · संवेदनशील Data छिपाएँ5 · 脱敏敏感数据

📝
Problemसवाल题目

Replace emails with [EMAIL] and phone numbers with [PHONE] — first UK numbers, then Indian mobiles with an optional +91.

Emails को [EMAIL] और phone numbers को [PHONE] से बदलें — पहले UK नंबर, फिर वैकल्पिक +91 वाले भारतीय mobile।

把邮箱替换为 [EMAIL],电话号码替换为 [PHONE] — 先处理英国号码,再处理带可选 +91 的印度手机号。

import re

text = "Contact alice@x.com or call 07123456789. My email is bob@y.com"

redacted = re.sub(r"[\w.+-]+@[\w-]+\.[\w.-]+", "[EMAIL]", text)
redacted = re.sub(r"\b07\d{9}\b", "[PHONE]", redacted)
print(redacted)

# Indian mobile numbers: optional +91, then 10 digits starting 6-9
print(re.sub(r"(?:\+91[\s-]?)?\b[6-9]\d{9}\b", "[MOBILE]", "Call +91 9876543210 or 8123456789"))
OUTPUT
Contact [EMAIL] or call [PHONE]. My email is [EMAIL] Call [MOBILE] or [MOBILE]

6 · Strong Password Check6 · मज़बूत Password जाँच6 · 强密码校验

📝
Problemसवाल题目

At least 8 characters, with a lowercase letter, an uppercase letter, a digit and one of !@#$%^&* — in one pattern, using lookaheads.

कम से कम 8 अक्षर, जिनमें एक छोटा अक्षर, एक बड़ा अक्षर, एक अंक और !@#$%^&* में से एक हो — एक ही pattern में, lookaheads से।

至少 8 个字符,包含一个小写字母、一个大写字母、一个数字和 !@#$%^&* 之一 — 用前瞻断言在一个模式里完成。

import re

STRONG = re.compile(r"""
    (?=.*[a-z])          # at least one lowercase letter
    (?=.*[A-Z])          # at least one uppercase letter
    (?=.*\d)             # at least one digit
    (?=.*[!@#$%^&*])     # at least one special character
    .{8,}                # 8 or more characters in total
""", re.VERBOSE)

def is_strong(pw):
    return STRONG.fullmatch(pw) is not None

for pw in ["Passw0rd!", "weakpass", "Ab1!", "ALLCAPS123!", "Passw0rd!\n"]:
    print(f"{pw!r:14} {is_strong(pw)}")

# The original version used match + ^...$ - it lets a trailing newline slip in:
old = re.compile(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$")
print(bool(old.match("Passw0rd!\n")))
OUTPUT
'Passw0rd!' True 'weakpass' False 'Ab1!' False 'ALLCAPS123!' False 'Passw0rd!\n' False True
✅
Why the Password Pattern Works — and the Bug We FixedPassword Pattern क्यों चलता है — और कौन-सा Bug ठीक किया密码模式为何有效 — 以及我们修复的 bug

Each (?=.*x) peeks ahead for one condition without moving, so all four checks start from the same place; then .{8,} checks the length. The common version uses match with ^...$ — and the last line shows it accepts "Passw0rd!\n", because $ allows a final newline. fullmatch closes that gap.

हर (?=.*x) बिना हिले एक शर्त के लिए आगे झाँकता है, इसलिए चारों जाँचें एक ही जगह से शुरू होती हैं; फिर .{8,} लंबाई जाँचता है। आम version ^...$ के साथ match इस्तेमाल करता है — और आख़िरी line दिखाती है कि वह "Passw0rd!\n" को मान लेता है, क्योंकि $ आख़िरी newline की अनुमति देता है। fullmatch यह छेद बंद करता है।

每个 (?=.*x) 都 原地不动 地向前查看一个条件,所以四项检查都从同一位置开始;然后 .{8,} 检查长度。常见写法用 match 加 ^...$ — 最后一行显示它会接受 "Passw0rd!\n",因为 $ 允许末尾换行。fullmatch 堵住了这个漏洞。


SectionSection第 17 节

Common Pitfalls 🕳️आम ग़लतियाँ 🕳️常见陷阱 🕳️

⚠️
Forgetting r"..."r"..." भूलना忘记 r"..."
"\b" ≠ r"\b"
"\b" is a backspace and the search silently finds nothing. Write every pattern as r"...".
"\b" एक backspace है और खोज चुपचाप कुछ नहीं पाती। हर pattern r"..." के रूप में लिखें।
"\b" 是退格符,搜索会悄悄地什么也找不到。所有模式都写成 r"..."。
🚫
The greedy trapGreedy जाल贪婪陷阱
.+
<.+> runs to the last >. Use <.+?> or, better, <[^>]+>.
<.+> आख़िरी > तक जाता है। <.+?> या, बेहतर, <[^>]+> इस्तेमाल करें।
<.+> 会跑到最后一个 >。请用 <.+?>,更好的是 <[^>]+>。
🔑
Unescaped dotबिना escape का dot未转义的点
. = any
\d+.\d+ also matches 3x5. Write \d+\.\d+ for a real decimal point.
\d+.\d+ 3x5 से भी match करता है। असली दशमलव बिंदु के लिए \d+\.\d+ लिखें।
\d+.\d+ 也会匹配 3x5。真正的小数点请写 \d+\.\d+。
🌏
Assuming \d means 0-9\d को 0-9 मान लेना以为 \d 就是 0-9
Unicode
In Python 3 \d also matches ५ and ٣. Use [0-9] or re.ASCII for validation.
Python 3 में \d ५ और ٣ से भी match करता है। जाँच के लिए [0-9] या re.ASCII इस्तेमाल करें।
在 Python 3 中 \d 也匹配 ५ 和 ٣。校验时请用 [0-9] 或 re.ASCII。
🔥
Catastrophic backtrackingCatastrophic backtracking灾难性回溯
(a+)+
Nested quantifiers can take exponential time. Remove the nesting or use (?>...) / a++ (3.11+).
Nested quantifiers exponential समय ले सकते हैं। Nesting हटाएँ या (?>...) / a++ (3.11+) इस्तेमाल करें।
嵌套量词可能耗费指数级时间。去掉嵌套,或使用 (?>...) / a++(3.11+)。
🧰
Regex for the wrong jobग़लत काम के लिए regex用错地方
HTML · JSON · CSV
Nested formats need real parsers: html.parser or BeautifulSoup, json, csv. Regex shines on flat text.
Nested formats को असली parsers चाहिए: html.parser या BeautifulSoup, json, csv। Regex सादे text पर चमकता है।
嵌套格式需要真正的解析器:html.parser 或 BeautifulSoup、json、csv。正则擅长处理扁平文本。

SectionSection第 18 节

Golden Rules and Quiz 🏆सुनहरे नियम और Quiz 🏆黄金法则与小测验 🏆

🔑 Regular Expressions — Rules to RememberRegular Expressions — याद रखने वाले नियम正则表达式 — 必须记住的规则
1
Always write patterns as raw strings: r"\d+".
Patterns हमेशा raw strings में लिखें: r"\d+"।
模式始终写成原始字符串:r"\d+"。
2
fullmatch to validate, search to find one, findall/finditer to find all.
जाँचने के लिए fullmatch, एक ढूँढने के लिए search, सब ढूँढने के लिए findall/finditer।
校验用 fullmatch,找一个用 search,找全部用 findall/finditer。
3
Escape a dot you mean literally: \. or [.].
जिस dot का मतलब सादा बिंदु है उसे escape करें: \. या [.]।
要表示字面的点就转义:\. 或 [.]。
4
Use [0-9] or re.ASCII when only English digits are allowed.
जब सिर्फ़ अंग्रेज़ी अंक मान्य हों तो [0-9] या re.ASCII इस्तेमाल करें।
只允许英文数字时,用 [0-9] 或 re.ASCII。
5
Prefer named groups (?P<year>...), and (?:...) when you do not need the text.
Named groups चुनें (?P<year>...), और जब text न चाहिए तो (?:...)।
优先用命名分组 (?P<year>...),不需要文本时用 (?:...)。
6
Between delimiters, use a negated class or a lazy quantifier; never nest quantifiers.
Delimiters के बीच negated class या lazy quantifier इस्तेमाल करें; quantifiers कभी nest न करें।
在分隔符之间,用否定类或懒惰量词;绝不嵌套量词。
7
Name and comment complex patterns with re.compile(..., re.VERBOSE), and pass count=, maxsplit=, flags= by name.
जटिल patterns को नाम और comments दें re.compile(..., re.VERBOSE) से, और count=, maxsplit=, flags= नाम से दें।
给复杂模式命名并写注释(re.compile(..., re.VERBOSE)),并按名字传 count=、maxsplit=、flags=。

Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题

🏆 QuizPick an answer to see why.जवाब चुनें और कारण देखें।选一个答案,看看原因。
🎯
Key Takeawayमुख्य सीख关键要点

A regex is a short sentence about which characters, where, and how many. Write it as a raw string, choose the right function (fullmatch to validate, search / findall to find, sub to replace), remember that \d and \w are Unicode, stop greedy runs with [^...] or ?, and never nest quantifiers. Test every pattern on real data — the Regex Lab is right above.

Regex कौन-से अक्षर, कहाँ और कितने के बारे में एक छोटा वाक्य है। इसे raw string में लिखें, सही function चुनें (जाँचने के लिए fullmatch, ढूँढने के लिए search / findall, बदलने के लिए sub), याद रखें कि \d और \w Unicode हैं, greedy दौड़ को [^...] या ? से रोकें, और quantifiers कभी nest न करें। हर pattern को असली data पर जाँचें — Regex Lab ऊपर ही है।

正则表达式是关于 哪些 字符、在哪里、几次 的一句短句。用原始字符串书写,选对函数(校验用 fullmatch,查找用 search / findall,替换用 sub),记住 \d 和 \w 支持 Unicode,用 [^...] 或 ? 阻止贪婪狂奔,绝不嵌套量词。每个模式都要用真实数据测试 — 正则实验室就在上面。

🐍 Try Python Yourself

Write, run and visualize Python step by step. No install needed.