IlmHamroh
Python kursi/Ilgor til imkoniyatlari3/17-dars26 daqiqa
Mundarija (24)

10.3-dars: Generator va yield

10-QISM — ILG'OR TIL IMKONIYATLARI · 3-dars


1. Kirish va motivatsiya

O'tgan darsda sanab qaytuvchi iterator uchun ikkita sinf va 20 qatorga yaqin kod yozdik. Mana xuddi shu narsa generator bilan:

python
def sanoq(n):
    for i in range(1, n + 1):
        yield i

Uch qator. Va u to'liq iterator:

python
list(sanoq(3))                  # [1, 2, 3]
next(sanoq(3))                  # 1

Lekin yield shunchaki qisqa yozuv emas. U Python'dagi eng g'aroyib mexanizmlardan biri: funksiya ishlash o'rtasida to'xtaydi, o'z lokal o'zgaruvchilarini saqlab qoladi va keyinroq aynan shu joydan davom etadi.

Bu kodni ko'ring:

python
def kuzat():
    print("boshlandi")
    yield 1
    print("davom etdi")
    yield 2
    print("tugadi")

g = kuzat()
print("generator yaratildi")

Nima chiqadi? Ko'pchilik boshlandi deb javob beradi. Aslida:

text
generator yaratildi

Funksiya tanasining birorta qatori ham bajarilmadi. U faqat next(g) chaqirilganda boshlanadi.

Bu darsda:

  • yield funksiyani nimaga aylantiradi
  • Bajarilish tartibi: qachon ishlaydi, qachon to'xtaydi
  • Generator holatlari va ichki freym
  • Generatordagi return va PEP 479
  • send() — generatorga qiymat yuborish
  • throw() va close() — generatorni tashqaridan boshqarish
  • finally bilan resurslarni tozalash

2. Nazariya — chuqur tushuntirish

2.1. yield funksiyani "fabrika"ga aylantiradi

Tanasida kamida bitta yield bo'lgan funksiya — generator funksiya. Uni chaqirish tanani bajarmaydi, balki generator obyekti qaytaradi:

python
def gen():
    yield 1

gen                             # <function gen at 0x...>
gen()                           # <generator object gen at 0x...>

yield ning bor-yo'qligini Python kompilyatsiya paytida aniqlaydi. yield hech qachon bajarilmaydigan joyda bo'lsa ham, funksiya generatorga aylanadi:

python
def aldamchi():
    return 42
    yield                       # hech qachon yetib kelinmaydi

aldamchi()                      # <generator object ...> — 42 emas!

2.2. Bajarilish tartibi

python
def gen():
    print("A")
    yield 1
    print("B")
    yield 2
    print("C")
Qadam Chaqiruv Bajariladigan qism Natija
0 g = gen() hech narsa generator obyekti
1 next(g) print("A") → yield 1 da to'xtaydi 1
2 next(g) print("B") → yield 2 da to'xtaydi 2
3 next(g) print("C") → funksiya oxiri StopIteration

Uchta asosiy xulosa:

  1. Yaratish hech narsani bajarmaydi
  2. Har next() kodni keyingi yield gacha bajaradi
  3. Funksiya tugaganda (oxiriga yetsa yoki return bo'lsa) — StopIteration

2.3. Holat saqlanadi

To'xtagan generator o'zining butun lokal holatini saqlaydi: o'zgaruvchilar, qaysi qatorda turgani, hatto ichidagi for siklining iteratori.

python
def yiguvchi():
    jami = 0
    for x in [10, 20, 30]:
        jami += x
        yield jami

list(yiguvchi())               # [10, 30, 60]

Bu holat freym (frame) obyektida saqlanadi. Oddiy funksiyaning freymi qaytishi bilan yo'q qilinadi; generatorniki esa generator obyekti tirik ekan, yashaydi:

python
g = yiguvchi()
next(g)
g.gi_frame.f_locals             # {'jami': 10, 'x': 10}
g.gi_frame.f_lineno             # to'xtagan qator raqami

2.4. Generator holatlari

inspect.getgeneratorstate() to'rt holatdan birini qaytaradi:

Holat Ma'nosi
GEN_CREATED Yaratilgan, hali boshlanmagan
GEN_RUNNING Hozir bajarilmoqda (faqat o'z ichidan ko'rinadi)
GEN_SUSPENDED yield da to'xtab turibdi
GEN_CLOSED Tugagan yoki yopilgan
python
import inspect

g = gen()
inspect.getgeneratorstate(g)    # 'GEN_CREATED'
next(g)
inspect.getgeneratorstate(g)    # 'GEN_SUSPENDED'

Generator o'zini o'zi next() qila olmaydi: GEN_RUNNING holatda qayta kirishga urinish ValueError: generator already executing beradi.

2.5. Generatordagi return

Generatorda return qiymat yozish mumkin. Qiymat yield qilinmaydi — u StopIteration istisnosining value atributiga joylanadi:

python
def gen():
    yield 1
    return "tayyor"

g = gen()
next(g)                         # 1
try:
    next(g)
except StopIteration as e:
    e.value                     # 'tayyor'

for va list() bu qiymatni e'tiborsiz qoldiradi:

python
list(gen())                     # [1] — 'tayyor' yo'qoldi

Qaytish qiymatini qulay olish yo'li — yield from (10.5-dars).

2.6. PEP 479: generator ichidagi StopIteration

Python 3.7 dan boshlab generator ichidan tasodifan chiqib ketgan StopIteration RuntimeError ga aylantiriladi:

python
def birinchilar(royxatlar):
    for r in royxatlar:
        yield next(iter(r))     # r bo'sh bo'lsa — StopIteration

list(birinchilar([[1], [], [3]]))
# ❌ RuntimeError: generator raised StopIteration

Nega? Aks holda bo'sh ro'yxat generatorni jimgina tugatib yuborardi va natija [1] bo'lardi — xato izsiz yo'qolardi.

Generatorni tugatish uchun return yozing. next() ni ichkarida sukut bilan chaqiring:

python
def birinchilar(royxatlar):
    for r in royxatlar:
        el = next(iter(r), None)
        if el is not None:
            yield el

2.7. Generator — bir martalik iterator

Generator obyekti iterator: iter(g) is g va u bir marta tugaydi (10.1-dars).

python
g = sanoq(3)
list(g), list(g)                # ([1, 2, 3], [])

Qayta ishlatiladigan narsa kerak bo'lsa, generator funksiyani saqlang va har safar chaqiring, yoki __iter__ ni generator qiling:

python
class Sanoq:
    def __init__(self, n):
        self.n = n

    def __iter__(self):                 # ⭐ har chaqiruv — yangi generator
        for i in range(1, self.n + 1):
            yield i

Bu 10.2-darsdagi ikkita sinfni bittaga qisqartiradi va konteyner/iterator ajratilishi saqlanib qoladi.

2.8. send() — generatorga qiymat yuborish

yield faqat qiymat beradi emas, qabul ham qiladi. yield ifoda sifatida ishlatilganda, uning qiymati send() bilan yuborilgan narsa bo'ladi:

python
def aks_sado():
    javob = None
    while True:
        qabul = yield javob
        javob = f"eshitdim: {qabul}"

g = aks_sado()
next(g)                         # ⭐ "ishga tushirish" — birinchi yield gacha
g.send("salom")                 # 'eshitdim: salom'
g.send("dunyo")                 # 'eshitdim: dunyo'

send() qanday ishlaydi:

  1. To'xtab turgan yield ifodasiga yuborilgan qiymatni beradi
  2. Kodni keyingi yield gacha bajaradi
  3. O'sha yield ning qiymatini qaytaradi

Yangi generatorga None dan boshqa qiymat yuborib bo'lmaydi — hali qabul qiluvchi yield yo'q:

python
g = aks_sado()
g.send("salom")                 # ❌ TypeError: can't send non-None value to a just-started generator

next(g) — bu g.send(None) bilan bir xil.

2.9. throw() va close()

throw(istisno) — to'xtab turgan yield joyida istisno ko'taradi. Generator uni ushlab, davom etishi mumkin:

python
def chidamli():
    while True:
        try:
            yield "ishlayapman"
        except ValueError:
            yield "xato ushlandi"

g = chidamli()
next(g)                         # 'ishlayapman'
g.throw(ValueError)             # 'xato ushlandi'

close() — yield joyida GeneratorExit ko'taradi. Generator:

  • tugashi (return yoki GeneratorExit ni qayta ko'tarish) kerak
  • yana yield qilsa — RuntimeError: generator ignored GeneratorExit

2.10. finally va resurslarni tozalash

Generator yield da to'xtab turgan paytda try/finally ichida bo'lsa, finally quyidagi holatlarda bajariladi:

  • generator oxirigacha aylantirilganda
  • close() chaqirilganda
  • generator obyekti yo'q qilinganda (CPython'da odatda oxirgi havola yo'qolishi bilan)
python
def qatorlar(yol):
    f = open(yol)
    try:
        for q in f:
            yield q
    finally:
        f.close()               # ⭐ break bo'lsa ham, close() da ham

Lekin "yo'q qilinganda" bajarilishiga tayanmang: generatorga havola biror joyda qolsa (masalan, traceback yoki global o'zgaruvchida), finally kechikadi. Aniq yopish kerak bo'lsa — close() yoki with (10.6–10.7-darslar).


3. Tez ma'lumotnoma

Generator funksiya

python
def gen():
    yield 1              # beradi va to'xtaydi
    x = yield 2          # beradi, keyin send() qiymatini qabul qiladi
    return "natija"      # StopIteration(value="natija")

Boshqaruv

python
g = gen()                hech narsa bajarilmaydi
next(g)                  keyingi yield gacha   (== g.send(None))
g.send(qiymat)           yield ga qiymat berib, keyingi yield gacha
g.throw(Istisno)         yield joyida istisno
g.close()                yield joyida GeneratorExit

Holat

python
inspect.getgeneratorstate(g)   GEN_CREATED / RUNNING / SUSPENDED / CLOSED
g.gi_frame.f_locals            lokal o'zgaruvchilar (to'xtagan paytda)
g.gi_frame.f_lineno            to'xtagan qator

Qoidalar

yield bor → funksiya generator (hatto yetib kelinmasa ham)
Ichkaridan chiqqan StopIteration → RuntimeError (PEP 479)
Generatorni tugatish → return
Yangi generatorga send(qiymat) → TypeError (avval next)
GeneratorExit dan keyin yield → RuntimeError

4. Batafsil misollar

Misol 1 — Bajarilish tartibi va holatlar

python
"""Generator qachon ishlaydi, qachon to'xtaydi — qadamma-qadam."""

import inspect

print("=== 1. Chaqirish tanani bajarmaydi ===")


def kuzat():
    print("    [tana] boshlandi")
    yield "birinchi"
    print("    [tana] ikkinchi qismga o'tdi")
    yield "ikkinchi"
    print("    [tana] oxiriga yetdi")


g = kuzat()
print(f"  g = kuzat()  → {type(g).__name__}, holat: {inspect.getgeneratorstate(g)}")
print(f"  next(g) → {next(g)!r}, holat: {inspect.getgeneratorstate(g)}")
print(f"  next(g) → {next(g)!r}, holat: {inspect.getgeneratorstate(g)}")
try:
    next(g)
except StopIteration:
    print(f"  next(g) → StopIteration, holat: {inspect.getgeneratorstate(g)}")

print("\n=== 2. yield yetib kelinmasa ham — generator ===")


def aldamchi():
    return 42
    yield


def oddiy():
    return 42


print(f"  oddiy()    → {oddiy()!r}")
print(f"  aldamchi() → {type(aldamchi()).__name__}")
print(f"  list(aldamchi()) → {list(aldamchi())}")
print(f"  inspect.isgeneratorfunction: oddiy={inspect.isgeneratorfunction(oddiy)}, "
      f"aldamchi={inspect.isgeneratorfunction(aldamchi)}")

print("\n=== 3. Lokal holat freymda saqlanadi ===")


def yiguvchi(sonlar):
    jami = 0
    for tartib, x in enumerate(sonlar, 1):
        jami += x
        yield jami


g = yiguvchi([10, 20, 30])
for _ in range(2):
    qiymat = next(g)
    lokal = {k: v for k, v in g.gi_frame.f_locals.items() if k != "sonlar"}
    print(f"  yield {qiymat:<3} → lokal: {lokal}")
print(f"  qolgani: {list(g)}, freym: {g.gi_frame}")

print("\n=== 4. Ikki generator — ikki mustaqil holat ===")
a, b = yiguvchi([1, 2, 3]), yiguvchi([100, 200])
print(f"  next(a)={next(a)}, next(b)={next(b)}, next(a)={next(a)}, next(b)={next(b)}")

print("\n=== 5. ⚠️ Generator o'zini qayta chaqira olmaydi ===")


def ozini_chaqiruvchi():
    yield next(g_oz)


g_oz = ozini_chaqiruvchi()
try:
    next(g_oz)
except ValueError as e:
    print(f"  ❌ ValueError: {e}")

print("\n=== 6. Dangasalik: faqat so'ralgani hisoblanadi ===")
hisoblangan = []


def kvadratlar():
    n = 0
    while True:
        hisoblangan.append(n)
        yield n * n
        n += 1


for kv in kvadratlar():
    if kv > 50:
        break
print(f"  50 dan katta birinchi kvadrat topilguncha hisoblandi: {hisoblangan}")

Natijaning muhim qismi:

text
=== 1. Chaqirish tanani bajarmaydi ===
  g = kuzat()  → generator, holat: GEN_CREATED
    [tana] boshlandi
  next(g) → 'birinchi', holat: GEN_SUSPENDED
    [tana] ikkinchi qismga o'tdi
  next(g) → 'ikkinchi', holat: GEN_SUSPENDED
    [tana] oxiriga yetdi
  next(g) → StopIteration, holat: GEN_CLOSED

=== 2. yield yetib kelinmasa ham — generator ===
  oddiy()    → 42
  aldamchi() → generator
  list(aldamchi()) → []
  inspect.isgeneratorfunction: oddiy=False, aldamchi=True

=== 3. Lokal holat freymda saqlanadi ===
  yield 10  → lokal: {'jami': 10, 'tartib': 1, 'x': 10}
  yield 30  → lokal: {'jami': 30, 'tartib': 2, 'x': 20}
  qolgani: [60], freym: None

=== 4. Ikki generator — ikki mustaqil holat ===
  next(a)=1, next(b)=100, next(a)=3, next(b)=300

=== 5. ⚠️ Generator o'zini qayta chaqira olmaydi ===
  ❌ ValueError: generator already executing

=== 6. Dangasalik: faqat so'ralgani hisoblanadi ===
  50 dan katta birinchi kvadrat topilguncha hisoblandi: [0, 1, 2, 3, 4, 5, 6, 7, 8]

Nima ko'rsatdi: 2.1, 2.2, 2.3, 2.4-bo'limlar.

Misol 2 — return, PEP 479 va finally

python
"""Generator qanday tugaydi va resurslar qachon tozalanadi."""

import gc

print("=== 1. return qiymati StopIteration.value da ===")


def hisobla(sonlar):
    jami = 0
    for x in sonlar:
        jami += x
        yield x
    return jami


g = hisobla([3, 4, 5])
berilganlar = []
while True:
    try:
        berilganlar.append(next(g))
    except StopIteration as e:
        print(f"  berilganlar: {berilganlar}, return qiymati: {e.value}")
        break
print(f"  list() bilan: {list(hisobla([3, 4, 5]))}   ⚠️ return qiymati yo'qoldi")

print("\n=== 2. PEP 479: ichkaridan chiqqan StopIteration ===")


def birinchilar_yomon(royxatlar):
    for r in royxatlar:
        yield next(iter(r))


def birinchilar_yaxshi(royxatlar):
    for r in royxatlar:
        el = next(iter(r), None)
        if el is not None:
            yield el


malumot = [[1, 2], [], [3]]
try:
    print(list(birinchilar_yomon(malumot)))
except RuntimeError as e:
    print(f"  yomon:   ❌ RuntimeError: {e}")
    print(f"           sabab: {type(e.__cause__).__name__}")
print(f"  yaxshi:  {list(birinchilar_yaxshi(malumot))}")

print("\n=== 3. finally qachon bajariladi ===")


def resursli(nom):
    print(f"    [{nom}] resurs ochildi")
    try:
        for i in range(5):
            yield i
    finally:
        print(f"    [{nom}] resurs yopildi")


print("  a) oxirigacha aylantirish:")
list(resursli("a"))

print("  b) break bilan chiqish:")
for x in resursli("b"):
    if x == 1:
        break
print("     ⭐ for tugagach generatorga havola qolmadi — CPython uni darhol")
print("        yo'q qildi va finally shu zahoti bajarildi")

print("  c) close() bilan:")
g = resursli("c")
next(g)
g.close()

print("  d) boshlanmagan generatorni yopish:")
g = resursli("d")
g.close()
print("     (try ga kirilmagan — finally ham, 'ochildi' ham yo'q)")

print("  e) ⚠️ havola saqlanib qolsa:")
saqlovchi = []
g = resursli("e")
next(g)
saqlovchi.append(g)
del g
gc.collect()
print("     del g dan keyin yopilmadi — saqlovchi ro'yxatda havola bor")
saqlovchi.clear()
gc.collect()

print("\n=== 4. ⚠️ GeneratorExit ni yutish ===")


def qaysar():
    while True:
        try:
            yield "ishlayapman"
        except GeneratorExit:
            print("    GeneratorExit keldi, lekin yana yield qilaman...")
            yield "to'xtamayman"


g = qaysar()
next(g)
try:
    g.close()
except RuntimeError as e:
    print(f"  ❌ RuntimeError: {e}")

Natijaning muhim qismi:

text
=== 1. return qiymati StopIteration.value da ===
  berilganlar: [3, 4, 5], return qiymati: 12
  list() bilan: [3, 4, 5]   ⚠️ return qiymati yo'qoldi

=== 2. PEP 479: ichkaridan chiqqan StopIteration ===
  yomon:   ❌ RuntimeError: generator raised StopIteration
           sabab: StopIteration
  yaxshi:  [1, 3]

=== 3. finally qachon bajariladi ===
  a) oxirigacha aylantirish:
    [a] resurs ochildi
    [a] resurs yopildi
  b) break bilan chiqish:
    [b] resurs ochildi
    [b] resurs yopildi
     ⭐ for tugagach generatorga havola qolmadi — CPython uni darhol
        yo'q qildi va finally shu zahoti bajarildi
  c) close() bilan:
    [c] resurs ochildi
    [c] resurs yopildi
  d) boshlanmagan generatorni yopish:
     (try ga kirilmagan — finally ham, 'ochildi' ham yo'q)
  e) ⚠️ havola saqlanib qolsa:
    [e] resurs ochildi
     del g dan keyin yopilmadi — saqlovchi ro'yxatda havola bor
    [e] resurs yopildi

=== 4. ⚠️ GeneratorExit ni yutish ===
    GeneratorExit keldi, lekin yana yield qilaman...
  ❌ RuntimeError: generator ignored GeneratorExit

Nima ko'rsatdi: 2.5, 2.6, 2.9, 2.10-bo'limlar.

Misol 3 — send() va throw()

python
"""Generatorga qiymat yuborish: o'rtacha hisoblovchi va holat mashinasi."""

import functools

print("=== 1. send() bajarilish tartibi ===")


def kuzatuvchi():
    print("    [gen] boshlandi")
    birinchi = yield "tayyor"
    print(f"    [gen] qabul qildi: {birinchi!r}")
    ikkinchi = yield f"{birinchi} olindi"
    print(f"    [gen] qabul qildi: {ikkinchi!r}")


g = kuzatuvchi()
print(f"  next(g)       → {next(g)!r}")
print(f"  g.send('A')   → {g.send('A')!r}")
try:
    g.send("B")
except StopIteration:
    print("  g.send('B')   → StopIteration (oxirgi yield dan keyin funksiya tugadi)")

print("\n=== 2. ⚠️ Ishga tushirmasdan send ===")
try:
    kuzatuvchi().send("X")
except TypeError as e:
    print(f"  ❌ TypeError: {e}")

print("\n=== 3. Joriy o'rtacha — ishga tushiruvchi dekorator bilan ===")


def ishga_tushir(funksiya):
    """Generatorni yaratib, birinchi yield gacha olib boradi."""
    @functools.wraps(funksiya)
    def oralgich(*args, **kwargs):
        g = funksiya(*args, **kwargs)
        next(g)
        return g
    return oralgich


@ishga_tushir
def ortacha():
    jami, soni = 0.0, 0
    natija = None
    while True:
        qiymat = yield natija
        jami += qiymat
        soni += 1
        natija = round(jami / soni, 2)


o = ortacha()
for narx in [12_000, 15_000, 9_000, 20_000]:
    print(f"  send({narx:>6}) → joriy o'rtacha: {o.send(narx)}")

print("\n=== 4. throw() — xatoni ushlab davom etish ===")


@ishga_tushir
def xavfsiz_ortacha():
    jami, soni, xatolar = 0.0, 0, 0
    natija = None
    while True:
        try:
            qiymat = yield natija
            if qiymat < 0:
                raise ValueError(f"manfiy qiymat: {qiymat}")
            jami += qiymat
            soni += 1
            natija = {"ortacha": round(jami / soni, 2), "xatolar": xatolar}
        except ValueError:
            xatolar += 1
            natija = {"ortacha": round(jami / soni, 2) if soni else None, "xatolar": xatolar}


x = xavfsiz_ortacha()
print(f"  send(10)                   → {x.send(10)}")
print(f"  send(-5)                   → {x.send(-5)}")
print(f"  throw(ValueError('tashqi')) → {x.throw(ValueError('tashqi'))}")
print(f"  send(20)                   → {x.send(20)}")

print("\n=== 5. Holat mashinasi: svetofor ===")


@ishga_tushir
def svetofor():
    holat = "QIZIL"
    OTISH = {"QIZIL": "YASHIL", "YASHIL": "SARIQ", "SARIQ": "QIZIL"}
    while True:
        buyruq = yield holat
        if buyruq == "keyingi":
            holat = OTISH[holat]
        elif buyruq == "favqulodda":
            holat = "SARIQ-MILTILLOQ"
        elif buyruq == "tikla":
            holat = "QIZIL"


s = svetofor()
for buyruq in ["keyingi", "keyingi", "favqulodda", "keyingi", "tikla", "keyingi"]:
    try:
        print(f"  {buyruq:<11} → {s.send(buyruq)}")
    except KeyError as e:
        print(f"  {buyruq:<11} → ❌ KeyError: {e}  (generator yopildi)")
        break

Natijaning muhim qismi:

text
=== 1. send() bajarilish tartibi ===
    [gen] boshlandi
  next(g)       → 'tayyor'
    [gen] qabul qildi: 'A'
  g.send('A')   → 'A olindi'
    [gen] qabul qildi: 'B'
  g.send('B')   → StopIteration (oxirgi yield dan keyin funksiya tugadi)

=== 2. ⚠️ Ishga tushirmasdan send ===
  ❌ TypeError: can't send non-None value to a just-started generator

=== 3. Joriy o'rtacha — ishga tushiruvchi dekorator bilan ===
  send( 12000) → joriy o'rtacha: 12000.0
  send( 15000) → joriy o'rtacha: 13500.0
  send(  9000) → joriy o'rtacha: 12000.0
  send( 20000) → joriy o'rtacha: 14000.0

=== 4. throw() — xatoni ushlab davom etish ===
  send(10)                   → {'ortacha': 10.0, 'xatolar': 0}
  send(-5)                   → {'ortacha': 10.0, 'xatolar': 1}
  throw(ValueError('tashqi')) → {'ortacha': 10.0, 'xatolar': 2}
  send(20)                   → {'ortacha': 15.0, 'xatolar': 2}

=== 5. Holat mashinasi: svetofor ===
  keyingi     → YASHIL
  keyingi     → SARIQ
  favqulodda  → SARIQ-MILTILLOQ
  keyingi     → ❌ KeyError: 'SARIQ-MILTILLOQ'  (generator yopildi)

Nima ko'rsatdi: 2.8, 2.9-bo'limlar.

Misol 4 — Amaliy: sinf iteratori va generatorni solishtirish

python
"""Bir xil vazifa uch xil yozilishi: iterator sinfi, generator __iter__, generator funksiya."""

import io
import time
import tracemalloc

CSV = "sana,mahsulot,soni,narx\n" + "".join(
    f"2026-09-{(i % 28) + 1:02d},mahsulot-{i % 50},{i % 7 + 1},{(i % 90 + 10) * 1000}\n"
    for i in range(300_000)
)


# ── 1. Iterator sinfi (10.2-dars uslubi) ──
class SotuvlarIterator:
    def __init__(self, fayl):
        self.fayl = fayl
        self.sarlavha = next(fayl).strip().split(",")

    def __iter__(self):
        return self

    def __next__(self):
        qator = next(self.fayl)                 # fayl tugasa StopIteration o'zi chiqadi
        qiymatlar = qator.strip().split(",")
        yozuv = dict(zip(self.sarlavha, qiymatlar))
        yozuv["soni"] = int(yozuv["soni"])
        yozuv["narx"] = int(yozuv["narx"])
        return yozuv


# ── 2. Generator funksiya ──
def sotuvlar(fayl):
    sarlavha = next(fayl).strip().split(",")
    for qator in fayl:
        yozuv = dict(zip(sarlavha, qator.strip().split(",")))
        yozuv["soni"] = int(yozuv["soni"])
        yozuv["narx"] = int(yozuv["narx"])
        yield yozuv


# ── 3. Qayta ishlatiladigan konteyner: __iter__ generator ──
class SotuvlarFayli:
    def __init__(self, matn):
        self.matn = matn

    def __iter__(self):
        yield from sotuvlar(io.StringIO(self.matn))


def tushum(yozuvlar):
    return sum(y["soni"] * y["narx"] for y in yozuvlar)


print("=== 1. Uchala usul bir xil natija beradi ===")
usullar = [
    ("iterator sinfi", lambda: SotuvlarIterator(io.StringIO(CSV))),
    ("generator funksiya", lambda: sotuvlar(io.StringIO(CSV))),
    ("konteyner (__iter__ gen)", lambda: SotuvlarFayli(CSV)),
]
for nom, yasash in usullar:
    boshlandi = time.perf_counter()
    natija = tushum(yasash())
    vaqt = time.perf_counter() - boshlandi
    print(f"  {nom:<26} tushum = {natija:>16,} so'm   ({vaqt:.2f} s)")

print("\n=== 2. Xotira: ro'yxat vs generator ===")


def olcha(nom, funksiya):
    fayl = io.StringIO(CSV)
    tracemalloc.start()
    natija = funksiya(fayl)
    _, tepa = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    print(f"  {nom:<30} natija={natija:>16,}  cho'qqi={tepa / 1_048_576:>7.1f} MB")


olcha("list(sotuvlar(...)) bilan", lambda f: tushum(list(sotuvlar(f))))
olcha("generator bilan", lambda f: tushum(sotuvlar(f)))

print("\n=== 3. Qayta ishlatish ===")
gen = sotuvlar(io.StringIO(CSV))
konteyner = SotuvlarFayli(CSV)
print(f"  generator:  birinchi={tushum(gen):,}, ikkinchi={tushum(gen):,}   ⚠️")
print(f"  konteyner:  birinchi={tushum(konteyner):,}, ikkinchi={tushum(konteyner):,}   ✅")

print("\n=== 4. Kod hajmi ===")
import inspect
for nom, obj in [("SotuvlarIterator", SotuvlarIterator), ("sotuvlar", sotuvlar),
                 ("SotuvlarFayli", SotuvlarFayli)]:
    if obj is SotuvlarFayli:
        nom = "SotuvlarFayli*"
    qatorlar = [q for q in inspect.getsource(obj).splitlines() if q.strip()]
    print(f"  {nom:<18} {len(qatorlar):>3} qator")
print("  * sotuvlar() generatoriga tayanadi — qayta ishlatish uchun +5 qator")

Natijaning muhim qismi:

text
=== 1. Uchala usul bir xil natija beradi ===
  iterator sinfi             tushum =   65,396,489,000 so'm   (0.32 s)
  generator funksiya         tushum =   65,396,489,000 so'm   (0.31 s)
  konteyner (__iter__ gen)   tushum =   65,396,489,000 so'm   (0.30 s)

=== 2. Xotira: ro'yxat vs generator ===
  list(sotuvlar(...)) bilan      natija=  65,396,489,000  cho'qqi=   92.5 MB
  generator bilan                natija=  65,396,489,000  cho'qqi=    0.0 MB

=== 3. Qayta ishlatish ===
  generator:  birinchi=65,396,489,000, ikkinchi=0   ⚠️
  konteyner:  birinchi=65,396,489,000, ikkinchi=65,396,489,000   ✅

=== 4. Kod hajmi ===
  SotuvlarIterator    13 qator
  sotuvlar             7 qator
  SotuvlarFayli*       5 qator
  * sotuvlar() generatoriga tayanadi — qayta ishlatish uchun +5 qator

Nima ko'rsatdi: 2.7-bo'lim va generatorning iterator sinfiga nisbatan afzalliklari.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"Generator funksiyani chaqirish uni bajaradi" Faqat generator obyekti yaratiladi; tana next() da boshlanadi
"yield faqat qiymat beradi" Ifoda sifatida send() qiymatini qabul qiladi
"Generatordagi return qiymati for da ko'rinadi" StopIteration.value da qoladi, for e'tiborsiz qoldiradi
"Ichkaridagi StopIteration generatorni tugatadi" 3.7+ da RuntimeError ga aylanadi (PEP 479)
"send() ni istalgan paytda chaqirish mumkin" Avval next() bilan birinchi yield gacha olib borish kerak
"finally generator yo'q bo'lganda doim darhol bajariladi" Havola qolsa kechikadi; aniq close() ishonchliroq
"Generator — faqat sintaktik qulaylik" Freym saqlanishi — to'liq alohida mexanizm
"Generator qayta ishlatiladi" Obyekt bir martalik; funksiyani qayta chaqiring

6. Keng tarqalgan xatolar va yechimlari

1. Generatorni ro'yxat deb ishlatish

python
natijalar = hisobla(malumot)
print(len(natijalar))                   # ❌ TypeError: object of type 'generator' has no len()

natijalar = list(hisobla(malumot))      # ✅

2. Generatorni ikki marta aylanish

python
g = sotuvlar(fayl)
jami = tushum(g)
soni = sum(1 for _ in g)                # ❌ 0 — generator tugagan

Bitta o'tishda ikkalasini hisoblang yoki qayta ishlatiladigan konteyner yasang (__iter__ generator).

3. Generator ichida next() sukutsiz

python
def gen(royxatlar):
    for r in royxatlar:
        yield next(iter(r))             # ❌ bo'sh r → RuntimeError

        yield next(iter(r), None)       # ✅ yoki bo'shini tekshiring

4. Generatorni tugatish uchun raise StopIteration

python
def gen():
    yield 1
    raise StopIteration                 # ❌ RuntimeError (PEP 479)

def gen():
    yield 1
    return                              # ✅

5. Yangi generatorga send()

python
g = ortacha()
g.send(10)                              # ❌ TypeError

g = ortacha(); next(g); g.send(10)      # ✅ yoki ishga tushiruvchi dekorator

6. GeneratorExit ni yutish

python
try:
    yield x
except GeneratorExit:
    yield "oxirgi"                      # ❌ RuntimeError: generator ignored GeneratorExit

except GeneratorExit:
    tozala()
    raise                               # ✅ yoki shunchaki return

7. Resursni generator yo'q bo'lishiga ishonib qoldirish

python
for q in qatorlar("katta.log"):         # ⚠️ break bo'lsa, fayl qachon yopiladi?
    if q.startswith("X"): break

with open("katta.log") as f:            # ✅ aniq vaqtda yopiladi
    for q in f: ...

8. Chaqirishni unutish

python
for x in sanoq:                         # ❌ TypeError: 'function' object is not iterable
for x in sanoq(5):                      # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 6.17-dars (o'tilgan): generator ifodalari — yield ning qisqa shakli
  • 10.1–10.2-darslar (o'tilgan): iterator protokoli; generator uni avtomatik bajaradi
  • 10.4-dars: generator quvurlari va amaliy naqshlar
  • 10.5-dars: yield from — delegatsiya va return qiymatini olish
  • 10.6–10.7-darslar: contextlib.contextmanager — generator asosidagi context manager
  • 14-qism: async def va await — generatorlardan o'sib chiqqan korutinalar
  • 17-qism: pytest fixture'lari yield bilan tozalashni amalga oshiradi

8. Eng yaxshi amaliyotlar

  1. Oddiy ketma-ketlik uchun iterator sinfi emas, generator yozing. Kod qisqa, holat avtomatik saqlanadi.

  2. Qayta ishlatiladigan to'plam kerak bo'lsa, __iter__ ni generator qiling. Konteyner va iterator ajratilishi saqlanadi.

  3. Generatorni return bilan tugating. raise StopIteration yozmang.

  4. Ichkarida next() ni sukut bilan chaqiring. PEP 479 dagi RuntimeError dan saqlaydi.

  5. send() ishlatadigan generatorlar uchun ishga tushiruvchi dekorator. Birinchi next() ni unutish xatosini yo'q qiladi.

  6. Tozalash uchun try/finally. Lekin aniq yopish vaqtiga close() yoki with bilan erishing.

  7. GeneratorExit ni ushlasangiz — qayta ko'taring yoki qayting. Hech qachon keyin yield qilmang.

  8. Generator obyektini hujjatda aytib o'ting. Qaytish turini Iterator[T] yoki Generator[Y, S, R] deb belgilang (13-qism).


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
1.  def g():
        print("A")
        yield 1
    x = g()
    print("B")
2.  def g():
        yield 1
        yield 2
    print(list(g()), list(g()))
3.  def g():
        yield 1
        return 99
    print(list(g()))
4.  def g():
        return 5
        yield
    print(g() == 5, list(g()))
5.  def g():
        x = yield 1
        yield x * 10
    it = g()
    print(next(it), it.send(4))
6.  def g():
        yield 1
    it = g()
    print(next(it), next(it, "tugadi"))
7.  def g():
        try:
            yield 1
        finally:
            print("F")
    it = g()
    next(it)
    it.close()
    print("tamom")
8.  def g():
        yield next(iter([]))
    try: list(g())
    except RuntimeError: print("RuntimeError")
9.  import inspect
    def g(): yield 1
    it = g()
    print(inspect.getgeneratorstate(it), end=" ")
    list(it)
    print(inspect.getgeneratorstate(it))
10. def g():
        yield 1
    it = g()
    print(iter(it) is it, type(it).__name__)
11. def g():
        n = 0
        while True:
            n += yield n
    it = g()
    next(it)
    print(it.send(5), it.send(3))
12. def g():
        try:
            yield 1
        except ValueError:
            yield "ushlandi"
    it = g()
    next(it)
    print(it.throw(ValueError))
Javoblar
  1. B — generator yaratildi, lekin tana bajarilmadi
  2. [1, 2] [1, 2] — har chaqiruv yangi generator
  3. [1] — return qiymati list() da yo'qoladi
  4. False [] — yield borligi uchun generator; return 5 darhol StopIteration(5)
  5. 1 40 — send(4) x ga 4 beradi, keyingi yield 40 ni qaytaradi
  6. 1 tugadi
  7. F keyin tamom — close() finally ni bajaradi
  8. RuntimeError — ichkaridan chiqqan StopIteration (PEP 479)
  9. GEN_CREATED GEN_CLOSED
  10. True generator
  11. 5 8 — n += 5 → 5, n += 3 → 8
  12. ushlandi

Vazifa 2: Xatolarni tuzating

python
1.  def juftlar(sonlar):
        for x in sonlar:
            if x % 2 == 0:
                return x

2.  def bolaklar(fayl):
        while True:
            bolak = fayl.read(1024)
            if not bolak:
                raise StopIteration
            yield bolak

3.  def jami():
        s = 0
        while True:
            s += yield s
    j = jami()
    print(j.send(10))

4.  def qatorlar(yol):
        f = open(yol)
        for q in f:
            yield q
        f.close()

5.  def natijalar():
        yield from range(3)
    n = natijalar()
    if len(list(n)) > 0:
        for x in n:
            print(x)
Javoblar
python
1.  yield x                         # return birinchi juftda funksiyani tugatadi

2.  if not bolak:
        return                      # raise StopIteration → RuntimeError

3.  j = jami()
    next(j)                         # avval birinchi yield gacha
    print(j.send(10))               # 10

4.  def qatorlar(yol):
        with open(yol) as f:        # break yoki xato bo'lsa ham yopiladi
            yield from f

5.  n = list(natijalar())           # generator tekshiruvda tugab qolardi
    if n:
        for x in n:
            print(x)

Vazifa 3: Generator bilan qayta yozing

10.2-darsdagi ChuqurlikIterator va KenglikIterator sinflarini generator funksiyalarga aylantiring:

  1. Rekursiyasiz (stek va deque bilan)
  2. Har biri 10 qatordan oshmasin
  3. Daraxt.__iter__ ni generator qiling va ikki marta aylanish ishlashini tekshiring
  4. 50 000 darajali daraxtda sinang

Vazifa 4: Yuruvchi statistika

send() bilan ishlaydigan statistika() generatorini yozing:

  1. Har send(qiymat) dan keyin {"soni", "ortacha", "min", "max"} qaytarsin
  2. throw(ValueError) — oxirgi qiymatni bekor qilsin (buning uchun oxirgi holatni saqlang)
  3. close() da yakuniy hisobotni chop etsin
  4. Ishga tushiruvchi dekorator bilan ishlasin

Vazifa 5: Tozalanishni kuzatish

  1. resursli() generatorini yozing, u ochilgan va yopilgan resurslar sonini global hisoblagichda saqlasin
  2. Oxirigacha, break, xato va close() holatlarida hisoblagich nolga qaytishini tekshiring
  3. Generatorga havola ro'yxatda qolganda hisoblagich nolga qaytmasligini ko'rsating
  4. weakref.finalize bilan kuzating

Vazifa 6: Holat mashinasi

Misol 3 dagi svetofor ni kengaytiring:

  1. Noma'lum buyruqda generator yopilmasin — KeyError ni ichkarida ushlasin
  2. Har holatda qancha buyruq qolganini (taymer) saqlasin
  3. send(None) — taymerni bir qadamga kamaytirsin, nolga yetsa keyingi holatga o'tsin
  4. Holatlar tarixini return qiymati sifatida qaytarsin (close() o'rniga maxsus "to'xta" buyrug'i bilan)

Vazifa 7: O'ylash

Generator to'xtab turganda funksiyaning lokal o'zgaruvchilari va "qayerda turgani" qayerda saqlanadi? Nega oddiy funksiya chaqiruvidan keyin bu ma'lumotlar yo'qoladi, generatorniki esa yo'qolmaydi? Bu mexanizm Python'ga qanday yangi imkoniyatlar ochdi?

Javob

Qisqa javob: holat freym (frame) obyektida saqlanadi. Oddiy funksiyada freym chaqiruv tugashi bilan tashlab yuboriladi, generatorda esa u generator obyektiga bog'langan holda yashab qoladi. Aynan shu "freymni tashlab yubormaslik" g'oyasi keyinchalik butun asinxron dasturlashning (async/await) asosiga aylandi.

1. Oddiy funksiya chaqiruvi qanday ishlaydi

Har funksiya chaqiruvida CPython freym yaratadi. Unda:

  • lokal o'zgaruvchilar
  • baholash steki (hisoblash oraliq qiymatlari)
  • joriy instruksiya ko'rsatkichi ("qaysi bayt-kodda turibman")
  • chaqiruvchiga havola

saqlanadi. Funksiya return qilganda freym kerak emas — u yo'q qilinadi va barcha lokal holat yo'qoladi. Keyingi chaqiruv yangi, bo'sh freym bilan boshlanadi.

python
def f():
    x = 1
    return x
# f() tugagach x yo'q — freym tashlandi

2. Generator nimani boshqacha qiladi

Generator funksiyani chaqirganda CPython freym yaratadi, lekin uni bajarmaydi — freym generator obyektiga joylanadi (g.gi_frame). next(g) freymni bajarishni davom ettiradi. yield ga yetganda:

  1. Qiymat chaqiruvchiga qaytariladi
  2. Instruksiya ko'rsatkichi va baholash steki freymda qoldiriladi
  3. Freym yo'q qilinmaydi — generator obyekti unga havola saqlaydi

Keyingi next() xuddi shu freymni aynan to'xtagan instruksiyadan davom ettiradi. Shuning uchun lokal o'zgaruvchilar, hatto ichki for siklining iteratori ham joyida turadi.

python
g = yiguvchi([10, 20, 30])
next(g)
g.gi_frame.f_locals     # {'jami': 10, 'x': 10, ...}
g.gi_frame.f_lasti      # to'xtagan bayt-kod joyi

Ya'ni generator — bu "to'xtatib qo'yilgan funksiya chaqiruvi", va u oddiy obyekt kabi o'zgaruvchida saqlanishi, boshqa funksiyaga berilishi mumkin.

3. Nega bu arzon

Tizim darajasidagi ip (thread) ham "to'xtab turadigan hisoblash", lekin u:

  • operatsion tizim tomonidan boshqariladi
  • har biri megabaytlab stek xotirasini oladi
  • almashtirish (context switch) qimmat

Generator esa bitta freym — bir necha yuz bayt, va almashtirish oddiy funksiya chaqiruvi narxida. Shuning uchun bir vaqtda yuz minglab to'xtab turgan generator bo'lishi mumkin, yuz ming ip esa tizimni qulatadi.

4. Tarixi: PEP 255 → PEP 342 → PEP 380 → PEP 492

Versiya PEP Nima qo'shildi
2.2 (2001) 255 yield — oddiy generatorlar, faqat qiymat beradi
2.5 (2006) 342 send(), throw(), close(), yield ifoda sifatida — generatorlar korutinaga aylandi
3.3 (2012) 380 yield from — generatorlar bir-biriga ishni topshira oladi, return qiymati
3.4 (2014) 3156 asyncio — yield from asosida asinxron kutubxona
3.5 (2015) 492 async def / await — alohida sintaksis, lekin ichki mexanizm o'sha freym saqlash

Bu jadvaldagi har bir qadam bitta g'oyaga qurilgan: freymni to'xtatib, keyin davom ettirish mumkin. await aslida "shu korutinani to'xtat, natija tayyor bo'lganda shu joydan davom ettir" degani — xuddi yield kabi.

5. Ochilgan imkoniyatlar

a) Dangasa ketma-ketliklar — cheksiz oqimlar, katta fayllarni xotiraga yuklamasdan o'qish (10.1, 10.4-darslar).

b) Holat mashinalari va tahlilchilar — holatni o'zgaruvchilarda emas, kodning qayerda turganida ifodalash (Misol 3 dagi svetofor). Tokenizator yoki protokol tahlilchisi if holat == ... zanjirisiz oddiy ketma-ket kod bo'lib qoladi.

c) Kooperativ ko'p vazifalilik — bir necha generatorni navbatma-navbat next() qilib, bitta ipda "parallel" ishlarni bajarish. asyncio ning hodisalar sikli (event loop) aynan shunday ishlaydi.

d) Resurs boshqaruvi — contextlib.contextmanager generatorning yield dan oldingi qismini "kirish", keyingisini "chiqish" deb ishlatadi (10.7-dars).

e) Test fixture'lari — pytest yield dan oldin tayyorlaydi, keyin tozalaydi.

6. Boshqa tillar

Til Mexanizm
C# yield return (2005) — kompilyator holat mashinasi sinfini yaratadi; freym saqlanmaydi, lokal o'zgaruvchilar maydonlarga ko'chiriladi
JavaScript function* va yield (ES2015) — Python'ga juda o'xshash, next(qiymat) bilan yuborish
Kotlin sequence { yield(x) } va korutinalar — kompilyator "davom ettirish" (continuation) obyektlarini yaratadi
Go generator yo'q; o'rniga goroutine + kanal — arzon, lekin haqiqiy parallel ishlar
Lua coroutine.create — assimetrik korutinalar, Python generatorlaridan oldin paydo bo'lgan

C# va Kotlin kompilyatsiya paytida funksiyani holat mashinasiga aylantiradi; CPython esa haqiqiy freymni saqlaydi. Natija bir xil — "to'xtab, davom etadigan funksiya" — lekin CPython yondashuvi gi_frame.f_locals orqali holatni ish vaqtida ko'rish imkonini beradi.

7. Xulosa

  1. Holat freymda: lokal o'zgaruvchilar, baholash steki va instruksiya ko'rsatkichi
  2. Oddiy funksiya freymni tashlaydi, generator uni o'z obyektida saqlaydi
  3. Bu ipdan ancha arzon — yuz minglab to'xtab turgan generator mumkin
  4. PEP 255 dan PEP 492 gacha bo'lgan yo'l — bitta g'oyaning rivojlanishi, async/await ham shu mexanizmga tayanadi
  5. Imkoniyatlar: dangasa oqimlar, holat mashinalari, kooperativ ko'p vazifalilik, resurs boshqaruvi

Nimani mustahkamlaydi: 2.1–2.10-bo'limlar.


Xulosa

Bu darsda yield kalit so'zining ichki mexanizmini o'rgandik.

Eng muhim uch fikr:

  1. yield funksiyani "to'xtab turadigan chaqiruv"ga aylantiradi. Generator funksiyani chaqirish tanani bajarmaydi — faqat generator obyekti yaratiladi. Har next() kodni keyingi yield gacha bajaradi va lokal holat freymda saqlanib qoladi. yield hech qachon bajarilmaydigan joyda bo'lsa ham, funksiya generatorga aylanadi.

  2. Generator o'z tugashini boshqaradi. return qiymat — StopIteration.value ga tushadi va for uni ko'rmaydi. Ichkaridan tasodifan chiqib ketgan StopIteration 3.7 dan beri RuntimeError beradi (PEP 479), shuning uchun tugatish faqat return bilan. try/finally resurslarni oxirigacha aylantirishda, break va close() da tozalaydi — lekin generatorga havola qolsa, tozalash kechikadi.

  3. send(), throw() va close() generatorni ikki tomonlama aloqaga aylantiradi. yield ifoda sifatida yuborilgan qiymatni qabul qiladi (avval next() bilan ishga tushirish shart), throw() to'xtagan joyda istisno ko'taradi, close() esa GeneratorExit yuboradi — uni yutib, yana yield qilish RuntimeError. Bu mexanizmlar keyinchalik asyncio va async/await ning poydevori bo'ldi.

Keyingi darsda generatorlarni amaliyotda ishlatamiz: ma'lumot quvurlari, cheksiz oqimlar bilan ishlash, itertools bilan birlashtirish va generatorlarning keng tarqalgan naqshlari.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
10.3-dars: Generator va yield — IlmHamroh