Tarkibga o'tish

16 β€” Muntazam ifodalar (regex, re moduli)

Tasavvur qil: foydalanuvchi formaga email kiritdi β€” aziz@mail deb. Bu to'g'ri emailmi? Yoki minglab qatorli log faylidan faqat xato (ERROR) qatorlarini, ulardagi sana va IP manzilni ajratib olish kerak. Bularni oddiy in, .split(), .find() bilan qilish mumkin, lekin tezda halokatga aylanadi β€” chunki bunday tekshiruvlar naqsh (pattern) ustida ishlaydi: "bir nechta harf, keyin @, keyin domen, keyin nuqta...". Mana shu naqshlarni qisqa, kuchli tilda yozish vositasi β€” muntazam ifodalar (regular expressions, qisqacha regex), Python'da esa re moduli orqali.

Regex avvaliga "begona alifbo" kabi ko'rinadi (r"^\d{3}-\d{2}$" nimaga o'xshaydi?), lekin u aslida juda mantiqiy. Bu bobda har bir belgini noldan, analogiya bilan tushuntiramiz va har birini real misolda β€” email, O'zbekiston telefoni, parol, sana, IP, log β€” ishlatib ko'ramiz.

Bu modulda: re modulining barcha asosiy funksiyalari (search, match, fullmatch, findall, finditer, sub, split), raw string r"..." nega zarurligi, naqsh sintaksisi to'liq (. \d \w \s, belgi sinflari, anchorlar, miqdorlar, guruhlar, nomli guruhlar, tanlov, ekranlash), bayroqlar (IGNORECASE, MULTILINE, DOTALL), re.compile, Match obyekti, hamda real validatsiya va parsing misollari.


16.1 Birinchi regex: naqsh bormi yoki yo'qmi?

Regex bilan ishlash uchun avval re modulini import qilamiz. Eng oddiy savol: "shu matnda shu naqsh bormi?" Buni re.search() hal qiladi β€” u matnni boshidan oxirigacha qidiradi va birinchi moslikni topadi.

import re

matn = "Mening yoshim 25 da"
natija = re.search(r"\d+", matn)    # \d+ β€” bir yoki ko'p raqam

print(natija)            # <re.Match object; span=(14, 16), match='25'>
print(natija.group())    # 25  β€” topilgan qism

re.search() ikkita narsa qaytaradi: - agar moslik topilsa β€” Match obyekti (uni if da True deb hisoblaydi); - agar topilmasa β€” None.

Shuning uchun natijani odatda if bilan tekshiramiz:

import re

def raqam_bormi(matn: str) -> bool:
    return re.search(r"\d", matn) is not None

print(raqam_bormi("salom123"))   # True
print(raqam_bormi("salom"))      # False

Bu yerda \d β€” "bitta raqam" degani. Naqsh nimani anglatishini keyingi bo'limlarda batafsil ko'ramiz; hozir asosiy oqimni tushunib ol: naqsh + matn β†’ Match yoki None.


16.2 Raw string r"..." β€” nega regex'da shart?

Yuqorida naqshni r"\d+" deb yozdik β€” boshida r harfi bilan. Bu raw string (xom satr). Nega kerak?

Muammo shundaki, Python'ning oddiy satrida \ (teskari chiziq) maxsus ma'noga ega: \n β€” yangi qator, \t β€” tabulyatsiya, \b β€” backspace. Lekin regex'ning o'zi ham \ dan keng foydalanadi (\d, \w, \b). Natijada chalkashlik chiqadi:

print("\d")     # Python ogohlantiradi: \d maxsus emas, lekin xavfli
print("\n")     # bu β€” yangi qator (bo'sh satr chiqadi)
print(r"\n")    # bu β€” ikki belgi: \ va n

Raw string r"..." ichida \ oddiy belgi sifatida qoladi, hech narsa "tarjima" qilinmaydi. Demak r"\d" β€” aynan \ va d ikki belgisi, va regex buni "raqam" deb to'g'ri o'qiydi.

import re

# r SIZ β€” \b ni Python "backspace" deb tarjima qiladi, regex buziladi
print(re.findall("\bsom", "1000 som"))    # []  β€” ishlamaydi

# r BILAN β€” \b regex'ga "so'z chegarasi" bo'lib yetadi
print(re.findall(r"\bsom", "1000 som"))   # ['som']  β€” to'g'ri

Qoida: regex naqshini doim r"..." bilan yoz. Bu odat seni ko'p tushunarsiz xatolardan qutqaradi.


16.3 search, match, fullmatch β€” uchta turli savol

Uchala funksiya ham naqshni qidiradi, lekin qayerdan qidirishi va qancha qismni talab qilishi bilan farq qiladi. Bu farqni tushunish juda muhim.

  • re.search β€” naqsh matnning istalgan joyida bormi?
  • re.match β€” naqsh matnning boshidan boshlanadimi? (oxirigacha shart emas)
  • re.fullmatch β€” naqsh butun matnga to'liq mos keladimi? (boshidan oxirigacha)
import re

matn = "abc123"

print(re.search(r"\d+", matn))      # match='123'  β€” istalgan joyda topdi
print(re.match(r"\d+", matn))       # None         β€” bosh '1' emas, 'a'
print(re.match(r"[a-z]+", matn))    # match='abc'  β€” bosh harflar bilan boshlandi
print(re.fullmatch(r"[a-z]+", matn))  # None       β€” butun matn faqat harf emas
print(re.fullmatch(r"[a-z]+\d+", matn))  # match='abc123'  β€” to'liq mos

Buni shunday eslab qol: - search β€” "ichidan top" (lupa bilan qidiradi), - match β€” "boshidan boshlanadimi", - fullmatch β€” "aynan shumi" (validatsiya uchun eng qulayi).

Email yoki telefonni tekshirishda odatda re.fullmatch ishlatamiz β€” chunki bizga "matn butunlay to'g'ri formatdami" kerak, "ichida bormi" emas.


16.4 Pattern asoslari: literal va . (har qanday belgi)

Naqshning eng oddiy turi β€” literal, ya'ni belgilarning o'zi. r"som" naqshi matndagi aynan som ketma-ketligini qidiradi.

import re
print(re.search(r"som", "1000 som").group())   # som

Maxsus belgi . (nuqta) β€” istalgan bitta belgini anglatadi (yangi qatordan tashqari):

import re

print(re.findall(r"k.t", "kot kit kut kt kart"))
# ['kot', 'kit', 'kut']
# k.t = k, istalgan bitta belgi, t
# 'kt' mos kelmadi (o'rtada belgi yo'q), 'kart' ham (ikki belgi)

Agar haqiqiy nuqtani qidirmoqchi bo'lsang (masalan 3.14 dagi nuqta), uni ekranlash kerak β€” \. deb yozasan. Bu haqda 16.13 da batafsil.

import re
print(re.findall(r"\.", "3.14 va 2.71"))   # ['.', '.']  β€” faqat nuqtalar

16.5 \d \w \s va ularning teskarilari

Regex'da eng ko'p ishlatiladigan maxsus belgi sinflari β€” bular qisqa yozuvlar:

Belgi Ma'nosi Misol
\d raqam (digit) 0-9 7, 0
\w "so'z belgisi": harf, raqam, _ a, 5, _
\s bo'sh joy (space, tab, yangi qator) , \t
\D raqam emas a, @
\W so'z belgisi emas @, , .
\S bo'sh joy emas a, 5

Katta harfli variant β€” kichik harflining teskarisi. Ular juda foydali:

import re

matn = "Buyurtma #42 narxi 1500 som"

print(re.findall(r"\d", matn))    # ['4', '2', '1', '5', '0', '0']  β€” har bir raqam
print(re.findall(r"\d+", matn))   # ['42', '1500']  β€” raqamlar guruhi
print(re.findall(r"\w+", matn))   # ['Buyurtma', '42', 'narxi', '1500', 'som']
print(re.findall(r"\S+", matn))   # ['Buyurtma', '#42', 'narxi', '1500', 'som']

\d bilan \d+ farqiga e'tibor ber: \d β€” bitta raqam, \d+ β€” ketma-ket bir yoki ko'p raqam (miqdorlar haqida 16.8 da).


16.6 Belgi sinflari [...] va inkor [^...]

Kvadrat qavs [...] β€” "shu belgilardan biri" degani. Bu o'zingning maxsus sinfingni yaratish usuli.

import re

# [aiu] β€” a, i yoki u harflaridan biri
print(re.findall(r"k[aiu]t", "kat kit kut ket kot"))
# ['kat', 'kit', 'kut']  β€” ket va kot mos emas (e, o yo'q)

Qavs ichida diapazon (-) yozish mumkin: - [a-z] β€” kichik lotin harflari, - [A-Z] β€” katta harflar, - [0-9] β€” raqamlar (\d bilan bir xil), - [a-zA-Z0-9] β€” harf yoki raqam.

import re

print(re.findall(r"[a-z]+", "Salom Dunyo 2026"))   # ['alom', 'unyo']  β€” faqat kichik
print(re.findall(r"[A-Za-z]+", "Salom Dunyo 2026")) # ['Salom', 'Dunyo']
print(re.findall(r"[0-9]+", "Salom Dunyo 2026"))    # ['2026']

^ belgisi qavs ichida va boshida β€” inkor ("bulardan tashqari hammasi"):

import re

# [^0-9] β€” raqam BO'LMAGAN har qanday belgi
print(re.sub(r"[^0-9]", "", "Tel: +998-90-123-45-67"))
# 998901234567  β€” faqat raqamlar qoldi

Diqqat: qavs ichida ko'p maxsus belgilar (., +, *) oddiy belgiga aylanadi. [.+] β€” aynan nuqta yoki plyus belgisi, miqdor emas.


16.7 Anchorlar: ^, $, \b

Anchorlar (langar) belgilar β€” ular hech narsaga "mos kelmaydi", balki pozitsiyani ko'rsatadi:

  • ^ β€” matn (yoki qator) boshi,
  • $ β€” matn (yoki qator) oxiri,
  • \b β€” so'z chegarasi (so'z boshi yoki oxiri).
import re

print(re.search(r"^Salom", "Salom dunyo"))     # topadi β€” boshida 'Salom'
print(re.search(r"^Salom", "Hey, Salom"))      # None β€” boshida emas
print(re.search(r"dunyo$", "Salom dunyo"))     # topadi β€” oxirida 'dunyo'

^...$ birgalikda butun matn shu naqshga to'liq mos kelishini talab qiladi β€” bu fullmatch ga juda o'xshaydi va validatsiyada ko'p ishlatiladi:

import re

def faqat_raqamlardanmi(s: str) -> bool:
    return re.search(r"^\d+$", s) is not None

print(faqat_raqamlardanmi("12345"))    # True
print(faqat_raqamlardanmi("123a5"))    # False β€” o'rtada harf bor

\b (so'z chegarasi) β€” so'zni "yopishgan" matndan ajratib oladi. Masalan som so'zini topish, lekin kosmos ichidagisini emas:

import re

print(re.findall(r"\bsom\b", "som va kosmos somon"))
# ['som']  β€” faqat alohida 'som' so'zi (kosmos, somon ichidagisi emas)

16.8 Miqdorlar: * + ? {n} {n,m}

Miqdorlar β€” naqshning bir qismi necha marta takrorlanishini ko'rsatadi. Bular o'zidan oldingi elementga tegishli.

Miqdor Ma'nosi
* 0 yoki ko'p marta
+ 1 yoki ko'p marta (kamida bitta)
? 0 yoki 1 marta (ixtiyoriy)
{n} aniq n marta
{n,m} n dan m gacha
{n,} n yoki ko'p marta
import re

print(re.findall(r"go*l", "gl gol goool"))   # ['gl', 'gol', 'goool']  β€” 'o' 0+ marta
print(re.findall(r"go+l", "gl gol goool"))   # ['gol', 'goool']         β€” 'o' 1+ marta
print(re.findall(r"go?l", "gl gol gool"))    # ['gl', 'gol']            β€” 'o' 0 yoki 1

{n} va {n,m} aniq sonni belgilaydi β€” masalan, telefon kodi yoki pochta indeksi uchun ideal:

import re

# aniq 5 ta raqam (pochta indeksi)
print(re.fullmatch(r"\d{5}", "10000"))    # match='10000'
print(re.fullmatch(r"\d{5}", "1000"))     # None β€” 4 ta raqam

# 2 dan 4 gacha raqam
print(re.findall(r"\d{2,4}", "1 22 333 44444"))   # ['22', '333', '4444']

Dangasa (lazy) miqdor *? va +?

Odatda miqdorlar ochko'z (greedy) β€” imkon qadar ko'proq belgi yutadi. Ba'zan bu xato natija beradi:

import re

matn = "<b>qalin</b> matn"

# ochko'z: birinchi < dan oxirgi > gacha hammasini oladi
print(re.findall(r"<.*>", matn))    # ['<b>qalin</b>']

# dangasa (*?): imkon qadar KAM oladi β€” har bir tegni alohida
print(re.findall(r"<.*?>", matn))   # ['<b>', '</b>']

? ni miqdordan keyin qo'ysang (*?, +?, {n,m}?), u dangasa bo'ladi β€” minimal mos kelishni oladi. HTML teglari, qo'shtirnoq ichidagi matnni ajratishda juda foydali.


16.9 findall va finditer β€” barcha mosliklar

re.search faqat birinchi moslikni topadi. Barchasini olish uchun ikki yo'l bor.

re.findall β€” barcha mosliklarni ro'yxat (list) sifatida qaytaradi (matn ko'rinishida):

import re

matn = "Narxlar: 1500, 23000 va 450 som"
print(re.findall(r"\d+", matn))   # ['1500', '23000', '450']

re.finditer β€” barcha mosliklarni Match obyektlari ko'rinishida, birma-bir (iterator) qaytaradi. Bu pozitsiya (.start(), .end()) ham kerak bo'lganda ishlatiladi:

import re

matn = "Narxlar: 1500, 23000 va 450 som"
for m in re.finditer(r"\d+", matn):
    print(f"{m.group()} β†’ pozitsiya {m.start()}-{m.end()}")
# 1500 β†’ pozitsiya 9-13
# 23000 β†’ pozitsiya 15-20
# 450 β†’ pozitsiya 24-27

Qaysi birini tanlash? Faqat topilgan matnlar kerak bo'lsa β€” findall (oddiy). Pozitsiya yoki guruhlar bilan murakkab ishlov kerak bo'lsa β€” finditer.

findall ning bir o'ziga xosligi: agar naqshda guruh ((...)) bo'lsa, u butun moslik o'rniga faqat guruhlarni qaytaradi (bu haqda 16.10 da).


16.10 Guruhlar (...) β€” qismni ajratib olish

Qavs (...) naqshning bir qismini guruhga oladi β€” keyin o'sha qismni alohida ajratib olish mumkin. Bu regex'ning eng kuchli imkoniyatlaridan biri.

import re

matn = "Sana: 2026-06-11"
m = re.search(r"(\d{4})-(\d{2})-(\d{2})", matn)

print(m.group())     # 2026-06-11  β€” butun moslik (group 0)
print(m.group(1))    # 2026        β€” birinchi guruh
print(m.group(2))    # 06          β€” ikkinchi guruh
print(m.group(3))    # 11          β€” uchinchi guruh
print(m.groups())    # ('2026', '06', '11')  β€” barcha guruhlar tuple

group(0) (yoki shunchaki group()) β€” butun moslik. group(1), group(2) ... β€” qavslar tartibida. groups() β€” barchasini birdan tuple qilib qaytaradi.

Guruhlarni findall bilan ishlatganda, u faqat guruhlarni qaytaradi:

import re

matn = "Aziz: 25, Malika: 30, Bobur: 19"
print(re.findall(r"(\w+): (\d+)", matn))
# [('Aziz', '25'), ('Malika', '30'), ('Bobur', '19')]
# har bir moslik uchun (ism, yosh) jufti

16.11 Nomli guruhlar (?P<ism>...)

Guruhlarni raqam (group(1)) bilan emas, nom bilan chaqirish ancha o'qiladigan kod beradi β€” ayniqsa guruh ko'p bo'lsa. Sintaksis: (?P<ism>...).

import re

matn = "2026-06-11"
m = re.search(r"(?P<yil>\d{4})-(?P<oy>\d{2})-(?P<kun>\d{2})", matn)

print(m.group("yil"))    # 2026
print(m.group("oy"))     # 06
print(m.group("kun"))    # 11
print(m.groupdict())     # {'yil': '2026', 'oy': '06', 'kun': '11'}

m.groupdict() β€” barcha nomli guruhlarni lug'at (dict) qilib qaytaradi. Bu juda qulay: natijani to'g'ridan-to'g'ri o'zgaruvchilarga yoki strukturaga solish mumkin.

import re

def sanani_ajrat(s: str) -> dict[str, int] | None:
    m = re.fullmatch(r"(?P<kun>\d{2})\.(?P<oy>\d{2})\.(?P<yil>\d{4})", s)
    if m is None:
        return None
    return {k: int(v) for k, v in m.groupdict().items()}

print(sanani_ajrat("11.06.2026"))   # {'kun': 11, 'oy': 6, 'yil': 2026}
print(sanani_ajrat("xato"))         # None

16.12 Tanlov | β€” "yoki"

Vertikal chiziq | β€” "yoki" degani: bir nechta variantdan birini tanlaydi.

import re

# 'olma' yoki 'anor' yoki 'uzum'
print(re.findall(r"olma|anor|uzum", "olma anor banan uzum"))
# ['olma', 'anor', 'uzum']

Tanlovni guruh bilan birlashtirib, qismga qo'llash mumkin:

import re

# fayl kengaytmasi: jpg, png yoki gif
matn = "rasm.jpg hujjat.pdf logo.png ikon.gif"
print(re.findall(r"\w+\.(jpg|png|gif)", matn))
# ['jpg', 'png', 'gif']  β€” faqat mos kengaytmalar (qavs guruh bo'lgani uchun)

Yana bir misol β€” salomlashishning turli shakllarini topish:

import re

matn = "Salom! Assalomu alaykum. Hayrli kun."
print(re.findall(r"Salom|Assalomu|Hayrli", matn))
# ['Salom', 'Assalomu', 'Hayrli']

16.13 Ekranlash \ β€” maxsus belgini oddiy qilish

. + * ? ( ) [ ] { } | ^ $ \ β€” bular regex'da maxsus ma'noga ega. Agar shu belgilarning o'zini qidirmoqchi bo'lsak, oldiga \ qo'yib ekranlash (escape) kerak.

import re

# . ni ekranlamasdan β€” har qanday belgini bildiradi (xato)
print(re.findall(r"3.14", "3.14 va 3X14"))   # ['3.14', '3X14']  β€” ikkalasi ham!

# \. bilan β€” faqat haqiqiy nuqta
print(re.findall(r"3\.14", "3.14 va 3X14"))  # ['3.14']  β€” to'g'ri

Boshqa maxsus belgilarni ekranlash misoli β€” narx va qavs:

import re

matn = "Narx: $5 (chegirma) va $10"
print(re.findall(r"\$\d+", matn))      # ['$5', '$10']  β€” \$ haqiqiy dollar belgisi
print(re.findall(r"\(\w+\)", matn))    # ['(chegirma)']  β€” \( va \) qavslar

Agar ko'p belgini ekranlash kerak bo'lsa, re.escape() foydalaniladi β€” u matndagi barcha maxsus belgilarni avtomatik ekranlaydi:

import re

foydalanuvchi_kiritdi = "narx (3.5$)"
xavfsiz = re.escape(foydalanuvchi_kiritdi)
print(xavfsiz)    # narx\ \(3\.5\$\)
# endi bu naqshni xavfsiz ishlatish mumkin
print(re.search(xavfsiz, "Yangi narx (3.5$) bugun").group())   # narx (3.5$)

16.14 re.sub β€” almashtirish (topib, o'zgartirish)

re.sub(naqsh, almashtiruvchi, matn) β€” naqshga mos kelgan barcha qismlarni boshqa matnga almashtiradi. Bu "topib-almashtirish" ning juda kuchli varianti.

import re

# Barcha raqamlarni # bilan yashirish
print(re.sub(r"\d", "#", "Karta: 1234 5678"))   # Karta: #### ####

# Ortiqcha bo'shliqlarni bittaga keltirish
print(re.sub(r"\s+", " ", "Salom    dunyo\t\tbugun"))   # Salom dunyo bugun

Almashtiruvchi matnda guruhga murojaat qilish mumkin β€” \1, \2 yoki nomli guruh \g<ism>. Bu format o'zgartirishda zo'r ishlaydi:

import re

# Sanani YYYY-MM-DD dan DD.MM.YYYY ga aylantirish
matn = "Bugun 2026-06-11"
natija = re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3.\2.\1", matn)
print(natija)   # Bugun 11.06.2026

Almashtiruvchi sifatida funksiya ham berish mumkin β€” har bir moslik shu funksiyaga Match obyekti bo'lib uzatiladi:

import re

# Har bir sonni ikki barobar oshirish
def ikki_barobar(m: re.Match) -> str:
    return str(int(m.group()) * 2)

print(re.sub(r"\d+", ikki_barobar, "5 olma, 10 nok"))   # 10 olma, 20 nok

re.subn β€” sub bilan bir xil, lekin (natija, nechta_almashtirildi) tuplini qaytaradi. Nechta o'zgarish bo'lganini bilish kerak bo'lsa foydali.


16.15 re.split β€” naqsh bo'yicha bo'lish

Oddiy str.split() faqat bitta ajratuvchi bilan ishlaydi. re.split() esa naqsh bo'yicha bo'ladi β€” bir nechta turli ajratuvchini birato'la qo'llash mumkin.

import re

# Vergul, nuqta-vergul yoki bo'shliq bilan ajratilgan
matn = "olma, anor; uzum  banan"
print(re.split(r"[,;\s]+", matn))   # ['olma', 'anor', 'uzum', 'banan']

[,;\s]+ naqshi β€” "bir yoki ko'p vergul, nuqta-vergul yoki bo'sh joy" β€” ketma-ket ajratuvchilarni ham to'g'ri yutadi. Yana bir misol:

import re

# Matnni gaplarga bo'lish (. ! ? bo'yicha)
matn = "Salom! Qalaysan? Yaxshi. Rahmat."
gaplar = re.split(r"[.!?]\s*", matn)
print([g for g in gaplar if g])   # ['Salom', 'Qalaysan', 'Yaxshi', 'Rahmat']

16.16 Bayroqlar (flags): IGNORECASE, MULTILINE, DOTALL

Bayroqlar regex'ning xatti-harakatini o'zgartiradi. Ular funksiyaga flags= argumenti orqali beriladi.

re.IGNORECASE (qisqa: re.I) β€” katta-kichik harfni e'tiborsiz qoldiradi:

import re

print(re.findall(r"salom", "Salom SALOM salom", flags=re.IGNORECASE))
# ['Salom', 'SALOM', 'salom']

re.MULTILINE (re.M) β€” ^ va $ ni har bir qatorning boshi/oxiriga moslaydi (butun matnniki emas):

import re

matn = """1-qator
2-qator
3-qator"""

# MULTILINE'siz: faqat butun matnning boshi
print(re.findall(r"^\d", matn))                      # ['1']
# MULTILINE bilan: har qatorning boshi
print(re.findall(r"^\d", matn, flags=re.MULTILINE))  # ['1', '2', '3']

re.DOTALL (re.S) β€” . belgisi yangi qatorni (\n) ham moslaydi (odatda moslamaydi):

import re

matn = "boshi\noxiri"
print(re.findall(r"boshi.oxiri", matn))                 # []  β€” . qatorni o'tmaydi
print(re.findall(r"boshi.oxiri", matn, flags=re.DOTALL)) # ['boshi\noxiri']

Bir nechta bayroqni birlashtirish uchun | (OR) ishlatiladi: flags=re.IGNORECASE | re.MULTILINE.


16.17 re.compile β€” naqshni qayta ishlatish

Agar bitta naqshni ko'p marta ishlatsang (masalan, siklda har qatorni tekshirish), uni har safar qaytadan "tarjima qilish" samarasiz. re.compile() naqshni bir marta tayyor obyektga aylantiradi, keyin uni qayta-qayta ishlatasan.

import re

# Naqshni bir marta tayyorlash
email_naqsh = re.compile(r"[\w.]+@[\w.]+\.\w+")

# Endi obyektning o'z metodlari bor (re. emas, naqsh.)
print(email_naqsh.search("aloqa: aziz@mail.uz").group())   # aziz@mail.uz
print(email_naqsh.findall("a@b.uz va c@d.com"))            # ['a@b.uz', 'c@d.com']

Compiled obyektda .search(), .match(), .fullmatch(), .findall(), .finditer(), .sub(), .split() β€” barcha metodlar mavjud, faqat naqshni qayta yozish shart emas. Bayroqni ham compile vaqtida berish mumkin:

import re

naqsh = re.compile(r"xato", flags=re.IGNORECASE)
print(naqsh.findall("XATO va Xato va xato"))   # ['XATO', 'Xato', 'xato']

Qoida: naqshni faqat bir-ikki marta ishlatsang β€” to'g'ridan-to'g'ri re.search(...) yetarli. Ko'p marta (siklda, funksiyada) ishlatsang β€” re.compile bilan tayyorla. Bu tezroq va o'qiladigan.


16.18 Match obyekti to'liq: .group, .start, .end, .span

Har bir muvaffaqiyatli moslik β€” Match obyekti. Uning foydali metodlari:

  • .group() / .group(0) β€” butun moslik,
  • .group(n) β€” n-guruh,
  • .groups() β€” barcha guruhlar (tuple),
  • .groupdict() β€” nomli guruhlar (dict),
  • .start(), .end() β€” moslik boshlanish/tugash indeksi,
  • .span() β€” (start, end) tuple.
import re

matn = "Mahsulot kodi: AB-1234 tayyor"
m = re.search(r"(?P<harf>[A-Z]{2})-(?P<son>\d{4})", matn)

print(m.group())          # AB-1234       β€” butun moslik
print(m.group("harf"))    # AB            β€” nomli guruh
print(m.group("son"))     # 1234
print(m.groups())         # ('AB', '1234')
print(m.groupdict())      # {'harf': 'AB', 'son': '1234'}
print(m.start(), m.end()) # 15 22
print(m.span())           # (15, 22)
print(matn[m.start():m.end()])   # AB-1234  β€” span orqali kesib olish

Bu metodlar, ayniqsa finditer bilan birga, matndagi mosliklarni aniq joylashuvi bilan qayta ishlashda zarur bo'ladi.


16.19 REAL: email validatsiyasi

Endi o'rganganlarni birlashtirib, amaliy validatorlar yozamiz. Email uchun soddalashtirilgan, lekin amalda ishlaydigan naqsh:

import re

EMAIL = re.compile(r"^[\w.+-]+@[\w-]+\.[\w.-]+$")

def email_togrimi(s: str) -> bool:
    return EMAIL.fullmatch(s) is not None

for e in ["aziz@mail.uz", "a.b+test@gmail.com", "xato@", "@yoq.uz", "oddiy matn"]:
    print(f"{e:20} β†’ {email_togrimi(e)}")
# aziz@mail.uz         β†’ True
# a.b+test@gmail.com   β†’ True
# xato@                β†’ False
# @yoq.uz              β†’ False
# oddiy matn           β†’ False

Naqshni qism-qismga ajratamiz: - ^[\w.+-]+ β€” boshida bir yoki ko'p so'z belgisi, nuqta, plyus yoki tire (foydalanuvchi nomi), - @ β€” majburiy "kuchukcha", - [\w-]+ β€” domen nomi, - \. β€” haqiqiy nuqta (ekranlangan), - [\w.-]+$ β€” domen kengaytmasi (uz, com, co.uk), oxirigacha.

Eslatma: "100% to'g'ri" email regex'i juda murakkab (RFC standarti). Amalda yuqoridagi kabi soddalashtirilgan naqsh aksar holatlarda yetarli. Mukammal tekshiruv kerak bo'lsa, email-validator kabi kutubxonadan foydalaniladi.


16.20 REAL: O'zbekiston telefon raqami (+998)

O'zbekiston raqami: +998 mamlakat kodi, keyin 2 raqamli operator kodi, keyin 7 raqam. Odamlar uni turlicha yozadi (+998 90 123-45-67, 998901234567). Avval tozalaymiz, keyin tekshiramiz.

import re

def telefon_normalla(s: str) -> str | None:
    # Faqat raqamlarni qoldiramiz (bo'shliq, tire, qavs, + ni olib tashlaymiz)
    raqamlar = re.sub(r"\D", "", s)
    # 998 bilan boshlanishi va jami 12 raqam bo'lishi kerak
    if re.fullmatch(r"998\d{9}", raqamlar):
        return "+" + raqamlar
    return None

for t in ["+998 90 123 45 67", "998901234567", "90-123-45-67", "+1 555 0100"]:
    print(f"{t:20} β†’ {telefon_normalla(t)}")
# +998 90 123 45 67    β†’ +998901234567
# 998901234567         β†’ +998901234567
# 90-123-45-67         β†’ None   (mamlakat kodi yo'q)
# +1 555 0100          β†’ None   (998 emas)

Bu yondashuv kuchli: avval re.sub(r"\D", "", s) bilan barcha ajratuvchilarni tozalaymiz, keyin toza raqamni fullmatch bilan tekshiramiz. Foydalanuvchi qanday yozishidan qat'i nazar ishlaydi.

Operator kodini ajratib olmoqchi bo'lsak, nomli guruh ishlatamiz:

import re

m = re.fullmatch(r"998(?P<operator>\d{2})(?P<raqam>\d{7})", "998901234567")
print(m.group("operator"))   # 90
print(m.group("raqam"))      # 1234567

16.21 REAL: parol mustahkamligini tekshirish

Yaxshi parol qoidasi: kamida 8 belgi, ichida katta harf, kichik harf, raqam va maxsus belgi bo'lsin. Buni bitta ulkan regex bilan yozish qiyin va o'qilmaydi β€” shuning uchun har shartni alohida kichik regex bilan tekshiramiz:

import re

def parol_tekshir(p: str) -> list[str]:
    xatolar = []
    if len(p) < 8:
        xatolar.append("kamida 8 belgi bo'lishi kerak")
    if not re.search(r"[A-Z]", p):
        xatolar.append("katta harf bo'lishi kerak")
    if not re.search(r"[a-z]", p):
        xatolar.append("kichik harf bo'lishi kerak")
    if not re.search(r"\d", p):
        xatolar.append("raqam bo'lishi kerak")
    if not re.search(r"[!@#$%^&*]", p):
        xatolar.append("maxsus belgi (!@#$...) bo'lishi kerak")
    return xatolar

print(parol_tekshir("Aziz123!"))   # []  β€” kuchli parol
print(parol_tekshir("aziz"))
# ['kamida 8 belgi...', 'katta harf...', 'raqam...', 'maxsus belgi...']

Bu usul β€” bitta murakkab naqshdan ko'ra ancha tushunarli va foydalanuvchiga aniq nima yetishmayotganini aytadi. Regex'ni qachon bo'lish kerakligiga yaxshi misol.


16.22 REAL: sana va IP manzil validatsiyasi

Sana (DD.MM.YYYY) ni ajratib, qism-qismlari mantiqan to'g'ri ekanini ham tekshiramiz:

import re

def sana_ajrat(s: str) -> tuple[int, int, int] | None:
    m = re.fullmatch(r"(?P<kun>\d{1,2})\.(?P<oy>\d{1,2})\.(?P<yil>\d{4})", s)
    if m is None:
        return None
    kun, oy, yil = int(m["kun"]), int(m["oy"]), int(m["yil"])
    if not (1 <= kun <= 31 and 1 <= oy <= 12):
        return None
    return kun, oy, yil

print(sana_ajrat("11.06.2026"))   # (11, 6, 2026)
print(sana_ajrat("45.13.2026"))   # None  β€” format to'g'ri, lekin qiymat noto'g'ri
print(sana_ajrat("2026/06/11"))   # None  β€” format noto'g'ri

Diqqat: m["kun"] β€” m.group("kun") ning qisqa shakli (Match obyektini lug'at kabi indekslash mumkin).

IP manzil (192.168.1.1) β€” to'rtta 0-255 oraliqdagi son, nuqta bilan ajratilgan:

import re

# Har bir oktet 0-255: 25[0-5] | 2[0-4]\d | 1\d\d | [1-9]?\d
OKTET = r"(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)"
IP = re.compile(rf"^{OKTET}\.{OKTET}\.{OKTET}\.{OKTET}$")

def ip_togrimi(s: str) -> bool:
    return IP.fullmatch(s) is not None

print(ip_togrimi("192.168.1.1"))     # True
print(ip_togrimi("255.255.255.0"))   # True
print(ip_togrimi("256.1.1.1"))       # False  β€” 256 > 255
print(ip_togrimi("1.2.3"))           # False  β€” 4 ta bo'lak emas

Bu yerda rf"..." β€” raw va f-string birga (naqsh ichida {OKTET} o'zgaruvchisini qo'yamiz). Murakkab naqshni kichik bo'laklardan qurish β€” professional usul.


16.23 REAL: log faylini tahlil qilish

Eng kuchli real misol β€” log parsing. Quyidagi formatdagi loglardan sana, daraja (level) va xabarni ajratib olamiz:

import re

log = """2026-06-11 09:15:23 INFO Server ishga tushdi
2026-06-11 09:16:01 ERROR Bazaga ulanib bo'lmadi
2026-06-11 09:16:05 WARNING Xotira kam qoldi
2026-06-11 09:17:30 ERROR Timeout: 30s"""

NAQSH = re.compile(
    r"(?P<sana>\d{4}-\d{2}-\d{2}) "
    r"(?P<vaqt>\d{2}:\d{2}:\d{2}) "
    r"(?P<level>\w+) "
    r"(?P<xabar>.+)"
)

# Faqat ERROR qatorlarini ajratamiz
for m in NAQSH.finditer(log):
    if m["level"] == "ERROR":
        print(f"[{m['vaqt']}] {m['xabar']}")
# [09:16:01] Bazaga ulanib bo'lmadi
# [09:17:30] Timeout: 30s

Naqsh bir nechta string sifatida yozilgan β€” Python ularni avtomatik birlashtiradi (qulay, har qism alohida o'qiladi). Nomli guruhlar har qatorni strukturaga aylantiradi: shu tarzda minglab qatorli logni soniyalarda tahlil qilish mumkin.


16.24 REAL: matndan hashtag, son va boshqa naqshlar

Ijtimoiy tarmoq matnidan hashtag larni ajratish:

import re

post = "Bugun #Python o'rgandim! #dasturlash #code2026 zo'r ekan"
print(re.findall(r"#\w+", post))
# ['#Python', '#dasturlash', '#code2026']

Matndan barcha sonlarni (butun va kasr) topish:

import re

matn = "Narx 1500.50 som, chegirma 10%, jami 1350.45"
print(re.findall(r"\d+\.?\d*", matn))   # ['1500.50', '10', '1350.45']
# \d+ butun qism, \.? ixtiyoriy nuqta, \d* kasr qism

URL havolalarni ajratish:

import re

matn = "Sayt https://ioqil.uz va https://github.com/iOqil yaxshi"
print(re.findall(r"https?://[\w./-]+", matn))
# ['https://ioqil.uz', 'https://github.com/iOqil']
# https? β€” 's' ixtiyoriy (http yoki https)

Bu uchala misol regex'ning kundalik ishdagi eng tez-tez uchraydigan vazifalarini ko'rsatadi: matndan kerakli naqshni toping va ajratib oling.


16.25 Ushlamaydigan guruh (?:...) va lookahead

Ba'zan guruh kerak (| yoki miqdor uchun), lekin uni ushlab qolish (capture) shart emas. (?:...) β€” ushlamaydigan guruh: guruhlaydi, lekin natijaga qo'shmaydi:

import re

matn = "2024-yil, 2025-yil"
print(re.findall(r"(\d{4})(?:-yil)", matn))   # ['2024', '2025'] β€” faqat yil
print(re.findall(r"(\d{4})(-yil)", matn))     # [('2024', '-yil'), ('2025', '-yil')] β€” ortiqcha

Lookahead β€” "oldinda nima borligini" guruhga olmasdan tekshirish. (?=...) ijobiy, (?!...) salbiy, (?<=...)/(?<!...) lookbehind:

import re

# Oldida "so'm" bo'lgan son ("so'm" natijaga kirmaydi):
print(re.findall(r"\d+(?= so'm)", "Olma 5000 so'm, Non 3000 so'm"))   # ['5000', '3000']
# Oldida "$" bo'lgan son (lookbehind):
print(re.findall(r"(?<=\$)\d+", "narx $100 va 200"))                 # ['100']

πŸ“Œ Lookahead/lookbehind naqshga shart qo'yadi, lekin o'sha qismni natijaga olmaydi. Kuchli parol qoidasida qulay: (?=.*\d) β€” "ichida raqam bo'lsin".

16.26 Orqa havola  β€” takrorlangan qismni topish

Naqsh ichida ilgari ushlangan guruhga , `` bilan murojaat qilish β€” "xuddi shu" qismni qayta talab qiladi:

import re

# Ketma-ket takrorlangan so'zni top:
print(re.findall(r"(\w+)\s+", "salom salom, xayr dunyo dunyo"))   # ['salom', 'dunyo']
# Nomli guruhga orqa havola (?P=ism):
print(bool(re.fullmatch(r"(?P<x>\w)(?P=x)", "aa")))   # True (ikki bir xil harf)

16.27 re.VERBOSE β€” o'qiladigan murakkab naqsh

Uzun naqsh tushunarsiz bo'ladi. re.VERBOSE (yoki re.X) naqsh ichida bo'sh joy va izoh yozishga ruxsat beradi:

import re

telefon = re.compile(r"""
    \+998        # davlat kodi
    \s?          # ixtiyoriy bo'sh joy
    \d{2}        # operator kodi
    \s?
    \d{7}        # raqam
""", re.VERBOSE)

print(bool(telefon.fullmatch("+998 90 1234567")))   # True

πŸ“Œ re.VERBOSEda haqiqiy bo'sh joyni \ (ekranlangan) yoki [ ] bilan yozing β€” oddiy bo'sh joy e'tibordan qoladi.

16.28 Amaliy maslahatlar va keng tarqalgan xatolar

Bobni yakunlab, regex bilan ishlashda esda tutadigan asosiy qoidalar:

  1. Doim r"..." ishlat β€” raw string regex naqshlari uchun standart.
  2. Validatsiyada fullmatch yoki ^...$ β€” "ichida bormi" emas, "to'liq to'g'rimi" kerak bo'lganda.
  3. Murakkab naqshni bo'lib tashla β€” parol kabi ko'p shartli tekshiruvni alohida kichik regex'larga ajrat. O'qiladigan va xatosi oson topiladigan bo'ladi.
  4. re.compile ni ko'p ishlatiladigan naqshlar uchun β€” siklda yoki funksiyada qayta-qayta chaqirilsa.
  5. Nomli guruh (?P<ism>...) afzal β€” raqamli guruhdan ko'ra o'qiladigan.
  6. Ochko'z/dangasa farqini eslab tur β€” HTML/teg ajratganda *? (dangasa) kerak bo'ladi.
  7. Regex har narsani yechmaydi β€” to'liq HTML/JSON parsing uchun maxsus kutubxona (html.parser, json) ishlat, regex emas.

Mashq qilish maslahati: regex101.com β€” naqshni real vaqtda sinab ko'rish, har bir belgini izohlash uchun ajoyib vosita. Yangi regex yozayotganda undan foydalan.

Regex β€” boshda murakkab, lekin amaliyot bilan kuchli qurolingga aylanadi. Endi matndagi istalgan naqshni topish, tekshirish va o'zgartirishni bilasan.


✍️ Masalalar (20 ta)

Bu masalalar shu modul (regex) mavzulariga asoslangan. import re ni unutma.

Oson (1–7):

  1. re.search bilan tekshir: berilgan matnda kamida bitta raqam bormi? True/False qaytaruvchi raqam_bormi(matn) funksiyasini yoz.
  2. re.findall bilan matndan barcha raqam guruhlarini (\d+) ro'yxat sifatida chiqar. Sinov: "uy 12, xona 305" β†’ ['12', '305'].
  3. Matnda Python so'zi (katta-kichik harfga qaramay) bormi? re.IGNORECASE ishlatib tekshir.
  4. re.sub bilan matndagi barcha raqamlarni * belgisiga almashtir. Sinov: "PIN 1234" β†’ "PIN ****".
  5. Berilgan satr faqat harflardan iboratmi? ^[a-zA-Z]+$ bilan tekshiruvchi funksiya yoz.
  6. re.findall bilan matndan barcha hashtag (#soz) larni ajrat.
  7. re.split bilan vergul yoki nuqta-vergul bo'yicha matnni bo'laklarga ajrat. Sinov: "a,b;c,d" β†’ ['a', 'b', 'c', 'd'].

O'rta (8–14):

  1. Email validatori yoz: ^[\w.+-]+@[\w-]+\.[\w.-]+$ naqshi bilan fullmatch qilib True/False qaytar.
  2. O'zbekiston telefon raqamini tekshir: avval re.sub(r"\D", "", s) bilan tozalab, keyin 998\d{9} ga mosligini tekshir.
  3. re.sub bilan matndagi ortiqcha bo'shliqlarni (ketma-ket) bitta probelga keltir (\s+ β†’ " ").
  4. Matndan barcha email manzillarni re.findall bilan ro'yxat qilib ajrat.
  5. re.findall bilan matndagi barcha sonlarni (butun va kasr, masalan 3.14) top.
  6. Berilgan parolda kamida bitta katta harf, bitta raqam va uzunligi β‰₯8 ekanini tekshir (uchta alohida re.search).
  7. re.sub va guruh bilan sanani YYYY-MM-DD dan DD.MM.YYYY ga aylantir.

Murakkab (15–20):

  1. Nomli guruh (?P<...>) bilan DD.MM.YYYY sanasini ajratib, groupdict() orqali lug'at qaytar.
  2. Log qatoridan (2026-06-11 09:15 ERROR xabar) nomli guruhlar bilan sana, daraja va xabarni ajrat.
  3. IP manzil validatori yoz: har bir oktet 0-255 oralig'ida bo'lsin (192.168.1.1 β†’ True, 256.1.1.1 β†’ False).
  4. Berilgan loglar ro'yxatidan faqat ERROR darajali qatorlarni filtrlab chiqar (finditer + nomli guruh).
  5. re.sub ga funksiya berib, matndagi har bir sonni 10% oshirib qaytar (100 β†’ 110).
  6. To'liq parol qoidasi: kamida 8 belgi, katta harf, kichik harf, raqam, maxsus belgi (!@#$%^&*). Yetishmagan shartlar ro'yxatini qaytar.

βœ… Yechimlar

Masala 1
import re

def raqam_bormi(matn: str) -> bool:
    return re.search(r"\d", matn) is not None

print(raqam_bormi("salom123"))   # True
print(raqam_bormi("salom"))      # False
Masala 2
import re

print(re.findall(r"\d+", "uy 12, xona 305"))   # ['12', '305']
Masala 3
import re

def python_bormi(matn: str) -> bool:
    return re.search(r"python", matn, flags=re.IGNORECASE) is not None

print(python_bormi("men PYTHON yaxshi ko'raman"))   # True
print(python_bormi("java haqida"))                   # False
Masala 4
import re

print(re.sub(r"\d", "*", "PIN 1234"))   # PIN ****
Masala 5
import re

def faqat_harfmi(s: str) -> bool:
    return re.fullmatch(r"[a-zA-Z]+", s) is not None

print(faqat_harfmi("Salom"))    # True
print(faqat_harfmi("Salom1"))   # False
Masala 6
import re

post = "#Python va #code2026 zo'r"
print(re.findall(r"#\w+", post))   # ['#Python', '#code2026']
Masala 7
import re

print(re.split(r"[,;]", "a,b;c,d"))   # ['a', 'b', 'c', 'd']
Masala 8
import re

EMAIL = re.compile(r"^[\w.+-]+@[\w-]+\.[\w.-]+$")

def email_togrimi(s: str) -> bool:
    return EMAIL.fullmatch(s) is not None

print(email_togrimi("aziz@mail.uz"))   # True
print(email_togrimi("xato@"))          # False
Masala 9
import re

def telefon_togrimi(s: str) -> bool:
    raqamlar = re.sub(r"\D", "", s)
    return re.fullmatch(r"998\d{9}", raqamlar) is not None

print(telefon_togrimi("+998 90 123 45 67"))   # True
print(telefon_togrimi("90-123-45-67"))        # False
Masala 10
import re

print(re.sub(r"\s+", " ", "Salom    dunyo\t\tbugun"))   # Salom dunyo bugun
Masala 11
import re

matn = "Aloqa: aziz@mail.uz yoki admin@site.com"
print(re.findall(r"[\w.+-]+@[\w.-]+\.\w+", matn))
# ['aziz@mail.uz', 'admin@site.com']
Masala 12
import re

matn = "Narx 1500.50, soni 3, jami 4501.50"
print(re.findall(r"\d+\.?\d*", matn))   # ['1500.50', '3', '4501.50']
Masala 13
import re

def parol_oddiy_tekshir(p: str) -> bool:
    return (
        len(p) >= 8
        and re.search(r"[A-Z]", p) is not None
        and re.search(r"\d", p) is not None
    )

print(parol_oddiy_tekshir("Parol123"))   # True
print(parol_oddiy_tekshir("parol"))      # False
Masala 14
import re

matn = "Sana 2026-06-11"
print(re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3.\2.\1", matn))
# Sana 11.06.2026
Masala 15
import re

def sana_dict(s: str) -> dict[str, str] | None:
    m = re.fullmatch(r"(?P<kun>\d{2})\.(?P<oy>\d{2})\.(?P<yil>\d{4})", s)
    return m.groupdict() if m else None

print(sana_dict("11.06.2026"))   # {'kun': '11', 'oy': '06', 'yil': '2026'}
print(sana_dict("xato"))         # None
Masala 16
import re

qator = "2026-06-11 09:15 ERROR Bazaga ulanib bo'lmadi"
m = re.search(
    r"(?P<sana>\d{4}-\d{2}-\d{2}) (?P<vaqt>\d{2}:\d{2}) "
    r"(?P<level>\w+) (?P<xabar>.+)",
    qator,
)
print(m["sana"], m["level"], "β†’", m["xabar"])
# 2026-06-11 ERROR β†’ Bazaga ulanib bo'lmadi
Masala 17
import re

OKTET = r"(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)"
IP = re.compile(rf"^{OKTET}\.{OKTET}\.{OKTET}\.{OKTET}$")

def ip_togrimi(s: str) -> bool:
    return IP.fullmatch(s) is not None

print(ip_togrimi("192.168.1.1"))   # True
print(ip_togrimi("256.1.1.1"))     # False
Masala 18
import re

log = """2026-06-11 09:15 INFO Boshlandi
2026-06-11 09:16 ERROR Xato 1
2026-06-11 09:17 WARNING Ogohlantirish
2026-06-11 09:18 ERROR Xato 2"""

NAQSH = re.compile(r"(?P<vaqt>\d{2}:\d{2}) (?P<level>\w+) (?P<xabar>.+)")
for m in NAQSH.finditer(log):
    if m["level"] == "ERROR":
        print(f"[{m['vaqt']}] {m['xabar']}")
# [09:16] Xato 1
# [09:18] Xato 2
Masala 19
import re

def oshir(m: re.Match) -> str:
    return str(int(int(m.group()) * 1.1))

print(re.sub(r"\d+", oshir, "narx 100 va 200"))   # narx 110 va 220
Masala 20
import re

def parol_tekshir(p: str) -> list[str]:
    xatolar = []
    if len(p) < 8:
        xatolar.append("kamida 8 belgi")
    if not re.search(r"[A-Z]", p):
        xatolar.append("katta harf")
    if not re.search(r"[a-z]", p):
        xatolar.append("kichik harf")
    if not re.search(r"\d", p):
        xatolar.append("raqam")
    if not re.search(r"[!@#$%^&*]", p):
        xatolar.append("maxsus belgi")
    return xatolar

print(parol_tekshir("Aziz123!"))   # []
print(parol_tekshir("aziz"))
# ['kamida 8 belgi', 'katta harf', 'raqam', 'maxsus belgi']

← Ma'lumotlar bazasi va SQL | Boshlovchilar README ↑ | Keyingi: OOP β€” ilg'or β†’