Generator va yield — lazy evaluation, xotira tejash

~20 daq · Python · Ilg'or mavzular

Muammodan boshlaylik

Server log fayli — 8 GB. Undan faqat ERROR qatorlarini topish kerak:

def xatolarni_ol(yol):
    natija = []
    with open(yol, encoding="utf-8") as f:
        for qator in f:
            if "ERROR" in qator:
                natija.append(qator)
    return natija              # millionlab qator — hammasi xotirada

Funksiya avval hamma natijani ro'yxatga yig'adi, keyin qaytaradi. Kompyuterda 4 GB RAM — dastur yiqiladi. Va siz faqat birinchi 10 ta xatoni ko'rmoqchi edingiz.


Generator nima?

Generator — qiymatlarni birdaniga emas, birma-bir, so'ralganda ishlab chiqaradigan funksiya. return o'rniga — yield:

def xatolarni_ol(yol):
    with open(yol, encoding="utf-8") as f:
        for qator in f:
            if "ERROR" in qator:
                yield qator          # bittasini berib, shu yerda "pauza"

for xato in xatolarni_ol("server.log"):
    print(xato)

Xotirada bir vaqtda faqat bitta qator. Fayl 8 GB yoki 800 GB — farqi yo'q.


Hayotiy o'xshatma

Bufet va oshpaz. Ro'yxat — bufet: barcha taomlar oldindan tayyorlangan va stolga qo'yilgan (xotira band). Generator — buyurtma bo'yicha oshpaz: siz "keyingisi" deganingizda bitta taom tayyorlaydi. Agar 3 taomdan keyin to'ysangiz — qolganlari umuman pishirilmaydi.


yield qanday ishlaydi

def sanash():
    print("Boshlandi")
    yield 1
    print("Davom")
    yield 2
    print("Oxiri")
    yield 3

gen = sanash()          # hech narsa chiqmaydi! funksiya hali ishga tushmagan
print(gen)              # <generator object sanash at 0x...>

print(next(gen))        # Boshlandi → 1
print(next(gen))        # Davom → 2
print(next(gen))        # Oxiri → 3
print(next(gen))        # StopIteration — qiymatlar tugadi
  • Generator funksiyasini chaqirish — generator ob'ekti qaytaradi, kod bajarilmaydi
  • next() — keyingi yield gacha bajaradi, qiymatni qaytaradi va to'xtaydi
  • Keyingi next() — aynan to'xtagan joydan davom etadi; barcha local o'zgaruvchilar saqlangan
  • Funksiya tugasa — StopIteration

for sikl next() ni o'zi chaqiradi va StopIteration da jim to'xtaydi:

for son in sanash():
    print(son)

Lazy evaluation — kerak bo'lganda hisoblash

Cheksiz ketma-ketlik

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

for i, son in enumerate(fibonachchi()):
    if i == 10:
        break
    print(son, end=" ")      # 0 1 1 2 3 5 8 13 21 34

Ro'yxat bilan cheksiz ketma-ketlik — imkonsiz. Generator bilan — faqat so'ralgancha hisoblanadi.

Xotira farqi

import sys

def kvadratlar_royxat(n):
    return [i * i for i in range(n)]

def kvadratlar_gen(n):
    for i in range(n):
        yield i * i

print(sys.getsizeof(kvadratlar_royxat(1_000_000)))   # ~8 000 000 bayt
print(sys.getsizeof(kvadratlar_gen(1_000_000)))      # ~200 bayt
print(sum(kvadratlar_gen(1_000_000)))                # hisoblaydi, xotira tejab

Generator expression (16-dars) — xuddi shu, bir qatorda: (i * i for i in range(n)).


Generator zanjiri — pipeline

Har bir bosqich — kichik generator; ma'lumot ular orqali birma-bir oqib o'tadi:

def qatorlar(yol):
    with open(yol, encoding="utf-8") as f:
        yield from f                      # har bir qatorni beradi

def faqat_xatolar(qatorlar):
    for q in qatorlar:
        if "ERROR" in q:
            yield q

def vaqt_va_xabar(qatorlar):
    for q in qatorlar:
        vaqt, _, xabar = q.partition(" ERROR ")
        yield vaqt.strip(), xabar.strip()

pipeline = vaqt_va_xabar(faqat_xatolar(qatorlar("server.log")))

for vaqt, xabar in pipeline:
    print(f"[{vaqt}] {xabar}")

8 GB fayl, uch bosqich — xotirada baribir bitta qator. Har bir funksiya kichik, alohida test qilinadi.

yield from

def ichma_ich(royxat):
    for el in royxat:
        if isinstance(el, list):
            yield from ichma_ich(el)      # ichki generatorning barcha qiymatlarini uzatish
        else:
            yield el

print(list(ichma_ich([1, [2, [3, 4]], 5])))    # [1, 2, 3, 4, 5]

yield from boshqa — for x in boshqa: yield x ning qisqa shakli.


Bo'laklab qayta ishlash

def bolaklar(iterable, hajm):
    bolak = []
    for el in iterable:
        bolak.append(el)
        if len(bolak) == hajm:
            yield bolak
            bolak = []
    if bolak:
        yield bolak

for partiya in bolaklar(range(10), 4):
    print(partiya)
# [0, 1, 2, 3]
# [4, 5, 6, 7]
# [8, 9]

Millionlab yozuvni bazaga 1000 tadan yozish, API ga partiyalab yuborish uchun. Python 3.12+ da tayyor: itertools.batched(iterable, 4) (46-dars).


Generator bir marta ishlatiladi

gen = (x * 2 for x in range(3))
print(list(gen))    # [0, 2, 4]
print(list(gen))    # [] — tugagan

[!WARNING] Generatorni ikki marta aylanib chiqib bo'lmaydi va unda len() yoki indeks (gen[0]) yo'q. Ma'lumot bir necha marta kerak bo'lsa — list(gen) bilan ro'yxatga aylantiring (agar xotiraga sig'sa) yoki generator funksiyasini qayta chaqiring.


Qachon generator

Vaziyat Tanlov
Katta fayl, ko'p yozuvlar Generator
Cheksiz yoki uzunligi noma'lum ketma-ketlik Generator
Bir marta aylanib chiqish, sum/max/any Generator
Bosqichli qayta ishlash (pipeline) Generator
Bir necha marta aylanish, len, indeks, saralash List
Kichik ma'lumot Ikkalasi ham; list soddaroq

[!INFO] Siz generatorlarni allaqachon ko'p ishlatgansiz: fayl ob'ekti (for qator in f), range(), dict.items(), zip(), enumerate(), map() — hammasi qiymatlarni "lazy" tarzda, birma-bir beradi.


Tez-tez uchraydigan xatolar

Generator funksiyasini chaqirib, natija kutish:

natija = sanash()
print(natija)          # ❌ <generator object> — ro'yxat emas
print(list(natija))    # ✅

yield va return qiymatini aralashtirish:

Generator ichidagi return qiymat — for siklga berilmaydi; u faqat generatorni tugatadi.

Generatorni ikki marta ishlatish — ikkinchi safar bo'sh.

Fayl yopilgandan keyin generator:

def ol():
    with open("a.txt") as f:
        return (q for q in f)      # ❌ with tugadi — fayl yopiq

yield bilan yozing — with generator tugaguncha ochiq qoladi.


Xulosa

Generator — qiymatlarni so'ralganda birma-bir beruvchi funksiya; xotirani tejaydi.

Eslab qoling:

  • yield — qiymat berib, shu joyda pauza; keyingi next() — davom
  • Chaqirish — generator ob'ekti; kod next()/for da bajariladi
  • Lazy: cheksiz ketma-ketliklar, ulkan fayllar
  • Pipeline — generatorlar zanjiri; yield from
  • Bir marta ishlatiladi; len va indeks yo'q
  • (x for x in ...) — generator expression

Keyingi dars

46-dars: itertools — chain, combinations, permutations, product, islice