OzbekPython / Bob 17

Iteratorlar va generatorlar

Iterable va iterator farqi, iter() va next(), öz iteratoringizni yoziş, yield bilan generatorlar va itertools yordamida çeksiz ketma-ketliklar.

Yuqori daraja20 daqiqa19 kod misoli17 / 24

for tsikli aslida qanday işlaydi?

Siz for tsiklini röyxat, satr, luğat, fayl bilan köp marta işlatgansiz. Lekin for bu turli xil obyektlarni qanday qilib bir xil usulda aylanib çiqadi? Javob — iteratsiya protokoli. Bu bobda şu "parda ortidagi" mexanizmni oçamiz va undan foydalanib, xotirani tejaydigan kuçli vositalar — generatorlar yozişni örganamiz.

Iterable va iterator

Ikki tuşunçani farqlaş muhim:

  • Iterable (aylanib çiqiladigan) — elementlarini birma-bir berişi mumkin bölgan har qanday obyekt: list, tuple, str, dict, set, range, fayl. Uni for bilan işlatiş mumkin.
  • Iterator — elementlarni aynan birma-bir beradigan obyekt. U qayerda töxtaganini eslab qoladi va har safar next() çaqirilganda keyingi elementni qaytaradi.

Oddiy öxşatiş: kitob — bu iterable, kitob içidagi xatçöp esa — iterator. Bitta kitobga bir neçta xatçöp qöyiş mumkin va har biri öz sahifasini eslab qoladi.

iter() va next()

iter() funksiyasi iterable dan iterator yasaydi, next() esa undan navbatdagi elementni oladi.

Python
mevalar = ["olma", "anor", "uzum"]

xatchop = iter(mevalar)
print(type(xatchop))
print(next(xatchop))
print(next(xatchop))
print(next(xatchop))

Natija:

Natija
<class 'list_iterator'>
olma
anor
uzum

StopIteration

Elementlar tugagaç, next() yana çaqirilsa, iterator StopIteration istisnosini qözğatadi. Bu xato emas, balki "boşqa element yöq" degan signal.

Python
xatchop = iter([10, 20])
print(next(xatchop))
print(next(xatchop))
try:
    next(xatchop)
except StopIteration:
    print("Elementlar tugadi!")

# next() ga standart qiymat beriş ham mumkin:
print(next(xatchop, "böş"))

Natija:

Natija
10
20
Elementlar tugadi!
böş

Endi for tsiklining sirini bilasiz. Quyidagi ikki kod bir xil işni bajaradi:

Python
shaharlar = ["Toşkent", "Andijon", "Termiz"]

# 1. Odatiy for
for shahar in shaharlar:
    print(shahar)

print("---")

# 2. for "parda ortida" aynan şunday işlaydi
it = iter(shaharlar)
while True:
    try:
        shahar = next(it)
    except StopIteration:
        break
    print(shahar)

Natija:

Natija
Toşkent
Andijon
Termiz
---
Toşkent
Andijon
Termiz
Python
sonlar = [1, 2, 3]
it = iter(sonlar)
print(list(it))   # hammasini oldi
print(list(it))   # iterator allaqaçon tugagan
print(list(sonlar))  # röyxatning özi esa joyida

Natija:

Natija
[1, 2, 3]
[]
[1, 2, 3]

Öz iteratoringizni yoziş

Har qanday sinfni iterator qiliş uçun ikkita maxsus metod yoziş kifoya:

  • __iter__() — iteratorning özini (self) qaytaradi.
  • __next__() — navbatdagi qiymatni qaytaradi yoki elementlar tugasa StopIteration qözğatadi.

Misol: raketa uçirişdan oldingi teskari sanoq.

Python
class TeskariSanoq:
    def __init__(self, boshlanish: int):
        self.joriy = boshlanish

    def __iter__(self):
        return self

    def __next__(self):
        if self.joriy < 1:
            raise StopIteration
        qiymat = self.joriy
        self.joriy -= 1
        return qiymat


for son in TeskariSanoq(5):
    print(son, end=" ")
print("🚀 Start!")

Natija:

Natija
5 4 3 2 1 🚀 Start!

Yana bir foydali misol — oylik tölov jadvali. Iterator har bir oy uçun qolgan qarzni hisoblab beradi:

Python
class TolovJadvali:
    def __init__(self, qarz: int, oylik_tolov: int):
        self.qarz = qarz
        self.oylik = oylik_tolov
        self.oy = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.qarz <= 0:
            raise StopIteration
        self.oy += 1
        tolov = min(self.oylik, self.qarz)
        self.qarz -= tolov
        return self.oy, tolov, self.qarz


for oy, tolov, qoldiq in TolovJadvali(2_500_000, 800_000):
    print(f"{oy}-oy: {tolov:>9,} tölandi, qoldiq {qoldiq:>9,}")

Natija:

Natija
1-oy:   800,000 tölandi, qoldiq 1,700,000
2-oy:   800,000 tölandi, qoldiq   900,000
3-oy:   800,000 tölandi, qoldiq   100,000
4-oy:   100,000 tölandi, qoldiq         0

Bu usul işlaydi, lekin biroz uzun: sinf, ikkita metod, holatni qölda saqlaş... Python bundan ança soddaroq yölni taklif qiladi.

Generatorlar va yield

Generator — bu return örniga yield işlatadigan funksiya. Oddiy funksiya return ga yetganda butunlay tugaydi. Generator esa yield ga yetganda qiymatni beradi va töxtab turadi: keyingi next() çaqiruvida aynan töxtagan joyidan, barça özgaruvçilari bilan davom etadi.

Python
def teskari_sanoq(boshlanish: int):
    while boshlanish >= 1:
        yield boshlanish
        boshlanish -= 1


for son in teskari_sanoq(5):
    print(son, end=" ")
print("🚀 Start!")

Natija:

Natija
5 4 3 2 1 🚀 Start!

Natija yuqoridagi TeskariSanoq sinfi bilan bir xil, lekin kod ikki barobar qisqa. __iter__, __next__ va StopIteration ni Python özi yaratdi.

Generatorning "töxtab-davom etişini" öz közingiz bilan köring:

Python
def kun_tartibi():
    print("  [uyğondim]")
    yield "nonuşta"
    print("  [işga bordim]")
    yield "iş"
    print("  [uyga qaytdim]")
    yield "dam oliş"


gen = kun_tartibi()
print(type(gen))
print("Hali heç narsa bajarilmadi.")
print(next(gen))
print(next(gen))
print(next(gen))

Natija:

Natija
<class 'generator'>
Hali heç narsa bajarilmadi.
  [uyğondim]
nonuşta
  [işga bordim]
iş
  [uyga qaytdim]
dam oliş

Eʼtibor bering: kun_tartibi() çaqirilganda funksiya tanasi umuman işlamadi — faqat generator obyekti yaratildi. Kod har bir next() da navbatdagi yield gaça bajariladi.

Generator ifodalari

Röyxat hosil qiliş ifodasini (list comprehension) eslaysizmi? Kvadrat qavslar [...] örniga oddiy qavs (...) yozsangiz, generator ifodasi hosil böladi. U barça elementlarni darhol yaratmaydi — kerak bölganda bittadan beradi.

Python
narxlar = [12000, 45000, 8000, 30000]

# QQS (12%) qöşilgan narxlar
royxat = [n * 112 // 100 for n in narxlar]   # hammasi darhol hisoblanadi
gen = (n * 112 // 100 for n in narxlar)      # hali heç narsa hisoblanmadi

print(royxat)
print(type(gen))
print(sum(gen))

# Funksiyaga yagona argument bölsa, qöşimça qavs şart emas:
print(max(len(soz) for soz in ["kitob", "daftar", "qalam"]))

Natija:

Natija
[13440, 50400, 8960, 33600]
<class 'generator'>
106400
6

Xotira foydasi: sys.getsizeof bilan soliştiriş

Generatorlarning eng katta afzalligi — xotirani tejaş. Röyxat barça elementlarini bir vaqtda xotirada saqlaydi. Generator esa faqat "keyingi elementni qanday hisoblaş" haqidagi maʼlumotni saqlaydi.

Python
import sys

royxat = [x * x for x in range(1_000_000)]
gen = (x * x for x in range(1_000_000))

print(f"Röyxat:   {sys.getsizeof(royxat):>10,} bayt")
print(f"Generator: {sys.getsizeof(gen):>10,} bayt")
print("Natijalar bir xilmi?", sum(royxat) == sum(gen))

Natija:

Natija
Röyxat:    8,448,728 bayt
Generator:        200 bayt
Natijalar bir xilmi? True

Bir million elementli röyxat megabaytlab joy egallaydi, generator esa bir neça yuz bayt — elementlar soni qança bölişidan qatʼi nazar. (Aniq raqamlar Python versiyasi va kompyuterga qarab biroz farq qilişi mumkin.)

Qaçon nimani tanlaş kerak?

HolatTanlov
Maʼlumotni bir marta aylanib çiqişGenerator
Juda katta yoki çeksiz ketma-ketlikGenerator
Elementlarga indeks orqali murojaat (x[5])Röyxat
Maʼlumotni bir neça marta işlatiş, len() kerakRöyxat

Çeksiz generatorlar va itertools

Generator heç qaçon tugamasligi ham mumkin — çunki u elementlarni oldindan yaratmaydi, çeksiz ketma-ketlik xotirani töldirmaydi. Standart itertools moduli bunday vositalarni tayyor holda beradi:

  • itertools.count(boshlanish, qadam) — çeksiz sanoq: 10, 11, 12, ...
  • itertools.cycle(iterable) — elementlarni çeksiz takrorlaydi: A, B, C, A, B, ...
  • itertools.islice(iterable, n) — çeksiz oqimdan faqat birinçi n tasini kesib oladi.
Python
from itertools import count, cycle, islice

# Navbat raqamlari 101 dan boşlanadi
navbat = count(101)
for mijoz in ["Aziz", "Barno", "Jamşid"]:
    print(f"{mijoz} — {next(navbat)}-raqam")

# Haftaning navbatçilari aylanib keladi
navbatchilar = cycle(["Ali", "Vali", "Gani"])
kunlar = ["Du", "Se", "Ch", "Pa", "Ju", "Sh"]
jadval = [f"{kun}: {odam}" for kun, odam in zip(kunlar, navbatchilar)]
print(", ".join(jadval))

# Çeksiz juft sonlar oqimidan birinçi 5 tasi
print(list(islice(count(0, 2), 5)))

Natija:

Natija
Aziz — 101-raqam
Barno — 102-raqam
Jamşid — 103-raqam
Du: Ali, Se: Vali, Ch: Gani, Pa: Ali, Ju: Vali, Sh: Gani
[0, 2, 4, 6, 8]

zip() eng qisqa ketma-ketlik tugaşi bilan töxtaydi, şuning uçun çeksiz cycle muammo tuğdirmadi.

Amaliyot: Fibonaççi generatori

Fibonaççi ketma-ketligida har bir son oldingi ikkitasining yiğindisi: 0, 1, 1, 2, 3, 5, 8, .... Bu çeksiz ketma-ketlik, generator uçun ayni muddao.

Python
from itertools import islice


def fibonachchi():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b


print("Birinçi 12 ta:", list(islice(fibonachchi(), 12)))

# 1000 dan katta birinçi Fibonaççi sonini topamiz
for son in fibonachchi():
    if son > 1000:
        print("1000 dan katta birinçisi:", son)
        break

# 100 dan kiçik juft Fibonaççi sonlari — generatorlar zanjiri
kichiklar = (f for f in islice(fibonachchi(), 20) if f < 100)
print("100 dan kiçik juftlari:", [f for f in kichiklar if f % 2 == 0])

Natija:

Natija
Birinçi 12 ta: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
1000 dan katta birinçisi: 1597
100 dan kiçik juftlari: [0, 2, 8, 34]

Amaliyot: katta faylni "dangasa" öqiş

Tasavvur qiling, sizda bir neça gigabaytli server jurnali (log) bor. Uni read() yoki readlines() bilan töliq öqisangiz, xotira yetmaydi. Fayl obyekti esa özi iterator — u qatorlarni birma-bir öqiydi. Generatorlar yordamida esa bir neça bosqiçli "quvur" (pipeline) quriş mumkin, unda har bir qator bosqiçlardan birma-bir ötadi va xotirada faqat bitta qator turadi.

Brauzerda işlaşi uçun avval katta faylni simulyatsiya qilamiz:

Python
import random
from collections import Counter

# 1. "Katta" jurnal faylini yaratamiz (100 000 qator)
random.seed(1)
darajalar = ["INFO", "INFO", "INFO", "WARNING", "ERROR"]
with open("server.log", "w", encoding="utf-8") as f:
    for i in range(100_000):
        f.write(f"{i:06} {random.choice(darajalar)} sörov bajarildi\n")


# 2. Generatorlar quvuri
def qatorlarni_oqi(fayl_nomi):
    with open(fayl_nomi, encoding="utf-8") as f:
        for qator in f:              # fayl bir vaqtda bitta qatordan öqiladi
            yield qator.rstrip("\n")


def darajani_ajrat(qatorlar):
    for qator in qatorlar:
        yield qator.split()[1]


def faqat_muammolar(darajalar_oqimi):
    for d in darajalar_oqimi:
        if d != "INFO":
            yield d


# 3. Quvurni ulaymiz — hali heç narsa öqilmadi!
oqim = faqat_muammolar(darajani_ajrat(qatorlarni_oqi("server.log")))

# 4. Counter oqimni oxirigaça bitta-bitta "tortib" oladi
hisob = Counter(oqim)
print("Muammolar soni:", dict(hisob))

Natija:

Natija
Muammolar soni: {'ERROR': 20049, 'WARNING': 20238}

Bu yerda uçala funksiya ham generator. Counter navbatdagi elementni söraganda, sörov zanjir böylab orqaga uzatiladi: faqat_muammolar → darajani_ajrat → qatorlarni_oqi → fayldan bitta qator. Fayl 100 ming yoki 100 million qator bölsa ham, xotira sarfi deyarli özgarmaydi.

Xuddi şu quvurni generator ifodalari bilan ham qisqa yoziş mumkin:

Python
with open("demo.log", "w", encoding="utf-8") as f:
    f.write("1 INFO ok\n2 ERROR disk\n3 WARNING sekin\n4 ERROR tarmoq\n")

with open("demo.log", encoding="utf-8") as f:
    xatolar = (q.split(maxsplit=2)[2].strip() for q in f if " ERROR " in q)
    print("Xatolar:", ", ".join(xatolar))

Natija:

Natija
Xatolar: disk, tarmoq

Xulosa

  • Iterable — aylanib çiqiladigan obyekt, iterator — elementlarni birma-bir beradigan va örnini eslab qoladigan obyekt.
  • iter() iterator yaratadi, next() keyingi elementni oladi; elementlar tugasa StopIteration qözğatiladi.
  • Öz iteratoringiz uçun __iter__ va __next__ metodlarini yozing.
  • yield işlatadigan funksiya generator böladi: u qiymat berib töxtaydi va keyingi next() da davom etadi.
  • Generator ifodasi (...) röyxatga öxşaydi, lekin elementlarni kerak bölganda hisoblaydi va xotirani tejaydi.
  • itertools.count, cycle, islice çeksiz oqimlar bilan xavfsiz işlaşga yordam beradi.

Maşqlar

  1. juft_sonlar(n) generatorini yozing: u 0 dan n gaça (şu jumladan) juft sonlarni bersin. n = 10 uçun natijani röyxat qilib çiqaring.
Yeçim
Python
def juft_sonlar(n):
    for i in range(0, n + 1, 2):
        yield i


print(list(juft_sonlar(10)))
  1. Oraliq iterator sinfini yozing: Oraliq(boshlanish, oxir, qadam) xuddi range kabi işlasin, lekin kasr qadamni ham qabul qilsin. Oraliq(0, 1, 0.25) ni çiqaring.
Yeçim
Python
class Oraliq:
    def __init__(self, boshlanish, oxir, qadam):
        self.joriy = boshlanish
        self.oxir = oxir
        self.qadam = qadam

    def __iter__(self):
        return self

    def __next__(self):
        if self.joriy >= self.oxir:
            raise StopIteration
        qiymat = self.joriy
        self.joriy += self.qadam
        return qiymat


print(list(Oraliq(0, 1, 0.25)))
  1. Har oy 5% ösadigan omonat uçun çeksiz generator yozing: u har oy oxiridagi summani bersin. 1 000 000 sömlik omonatning birinçi 6 oyini islice bilan çiqaring.
Yeçim
Python
from itertools import islice


def omonat(summa, foiz):
    oy = 0
    while True:
        oy += 1
        summa = summa * (100 + foiz) / 100
        yield oy, round(summa)


for oy, summa in islice(omonat(1_000_000, 5), 6):
    print(f"{oy}-oy: {summa:,} söm")
  1. Generator ifodasi yordamida ["Toşkent", "Nukus", "Qarşi", "Navoiy", "Jizzax"] röyxatidagi "N" harfi bilan boşlanadigan şaharlar harflari sonining yiğindisini hisoblang (oraliq röyxat yaratmasdan).
Yeçim
Python
shaharlar = ["Toşkent", "Nukus", "Qarşi", "Navoiy", "Jizzax"]
print(sum(len(s) for s in shaharlar if s.startswith("N")))
  1. bolaklarga(royxat, olcham) generatorini yozing: u röyxatni berilgan ölçamdagi bölaklarga ajratib bersin (masalan, 10 ta talabani 3 kişilik guruhlarga). Oxirgi bölak kiçikroq bölişi mumkin.
Yeçim
Python
def bolaklarga(royxat, olcham):
    for i in range(0, len(royxat), olcham):
        yield royxat[i:i + olcham]


talabalar = [f"T{i}" for i in range(1, 11)]
for raqam, guruh in enumerate(bolaklarga(talabalar, 3), start=1):
    print(f"{raqam}-guruh: {guruh}")