aidoc.uz

2-qism · Ma'lumot tuzilmalari va funksiyalar·07-bob

Comprehension'lar, iteratorlar va generatorlar

List/dict/set comprehension, iteratsiya protokoli qanday ishlaydi, generatorlar va yield, dangasa (lazy) hisoblash, katta ma'lumotlarni xotiraga sig'dirish va itertools.

Bu bob ikki narsani o'rgatadi. Birinchisi — comprehension: ro'yxat, lug'at va to'plamlarni bitta o'qiladigan ifoda bilan yaratish. Ikkinchisi — iteratorlar va generatorlar: ma'lumotni bir vaqtda emas, bittalab ishlab chiqarish. Ikkinchisi ayniqsa AI/ML uchun muhim: 50 GB dataset xotiraga sig'maydi, lekin generator bilan uni qatorma-qator qayta ishlash mumkin. PyTorch'ning DataLoaderi, Hugging Face'ning streaming dataset'lari, LLM'ning token-token javob qaytarishi (streaming) — hammasi shu g'oyaga asoslangan.

▶ Video dars· Corey Schafer· 11:14· ingliz tilida
Python Tutorial: Generators - How to use them and the benefits you receive

Generatorlar va yield: xotirani tejash va dangasa hisoblash amalda. Bobdagi iteratsiya protokoli bilan birga ko'ring.

YouTube'da ochish ↗

1. List comprehension#

# Odatiy sikl
kvadratlar = []
for x in range(10):
    kvadratlar.append(x ** 2)
 
# Comprehension — bir xil natija
kvadratlar = [x ** 2 for x in range(10)]

Umumiy shakl:

[ IFODA   for ELEMENT in ITERABLE   if SHART ]
   ↑          ↑                        ↑
 nima       nimadan                 qaysilarini
 qo'yish    olish                   (ixtiyoriy)
juftlar = [x for x in range(20) if x % 2 == 0]
ismlar = [ism.strip().title() for ism in xom_ismlar if ism.strip()]
uzunliklar = [len(s) for s in so_zlar]

Shartli ifoda va filtr farqi#

[x for x in sonlar if x > 0]              # filtr: musbatlarni OLADI (kam element)
[x if x > 0 else 0 for x in sonlar]       # ifoda: manfiylarni 0 ga ALMASHTIRADI (element soni o'zgarmaydi)

if oxirida — filtr; if/else boshida — har bir element uchun qiymat tanlash. Bu ko'pincha chalkashtiriladi.

Ichma-ich comprehension#

matritsa = [[1, 2, 3], [4, 5, 6]]
tekis = [x for qator in matritsa for x in qator]     # [1, 2, 3, 4, 5, 6]

forlar oddiy ichma-ich sikl tartibida yoziladi:

tekis = []
for qator in matritsa:        # birinchi for
    for x in qator:           # ikkinchi for
        tekis.append(x)

2. Dict va set comprehension#

so_zlar = ["olma", "nok", "uzum"]
 
{s: len(s) for s in so_zlar}               # {'olma': 4, 'nok': 3, 'uzum': 4}
{v: k for k, v in lug_at.items()}          # lug'atni teskari aylantirish
{k: v for k, v in config.items() if v is not None}   # None qiymatlarni olib tashlash
{x % 3 for x in range(10)}                 # {0, 1, 2} — set

Amaliy misol — API'ga faqat berilgan parametrlarni yuborish:

def build_params(model: str, max_tokens: int, temperature: float | None = None,
                 top_p: float | None = None) -> dict:
    params = {"model": model, "max_tokens": max_tokens,
              "temperature": temperature, "top_p": top_p}
    return {k: v for k, v in params.items() if v is not None}
 
build_params("claude-opus-5-5", 1024)   # {'model': 'claude-opus-5-5', 'max_tokens': 1024}

Comprehension'ning o'z doirasi bor#

x = "tashqi"
kv = [x for x in range(3)]
print(x)          # 'tashqi' — comprehension ichidagi x tashqariga "oqib chiqmaydi"

3. Iteratsiya qanday ishlaydi#

for x in narsa: yozganingizda Python aslida nima qiladi? Bu savolning javobi — iteratsiya protokoli:

  1. iter() — iterator olish

    Python iter(narsa) ni chaqiradi. Bu narsa.__iter__() metodini ishlatadi va iterator obyektini qaytaradi.

  2. next() — keyingi elementni so'rash

    Har bir qadamda next(iterator) chaqiriladi (iterator.__next__()) va keyingi element qaytariladi.

  3. StopIteration — tugadi

    Elementlar tugaganda __next__ StopIteration xatosini ko'taradi. for sikli uni ushlab, jimgina to'xtaydi.

Buni qo'lda qilib ko'ramiz:

mevalar = ["olma", "nok"]
it = iter(mevalar)
next(it)        # 'olma'
next(it)        # 'nok'
next(it)        # StopIteration
next(it, "tugadi")   # 'tugadi' — standart qiymat bilan xato chiqmaydi

Demak for sikli aslida shu:

it = iter(mevalar)
while True:
    try:
        meva = next(it)
    except StopIteration:
        break
    print(meva)

Iterable va iterator — farqi#

IterableIterator
Ta'rifiter() ga berish mumkin bo'lgan narsanext() ga berish mumkin bo'lgan narsa
Misollarlist, str, dict, range, fayliter(lst), generator, fayl, zip(...), map(...)
Qayta aylanishHa — har safar yangi iteratorYo'q — bir marta ishlatiladi
Holat saqlaydimiYo'qHa (qayerda turganini biladi)

Oxirgi qator — eng ko'p xatoga sabab bo'ladigan fakt:

juftlar = zip([1, 2, 3], "abc")    # zip — iterator
print(list(juftlar))               # [(1, 'a'), (2, 'b'), (3, 'c')]
print(list(juftlar))               # [] — tugab bo'lgan!
kv = (x * x for x in range(3))     # generator ifodasi — iterator
print(sum(kv))                     # 5
print(sum(kv))                     # 0 — "bo'sh" generator, xato yo'q!

4. Generatorlar — yield#

Generator funksiyasi — return o'rniga yield ishlatadigan funksiya. U chaqirilganda kod bajarilmaydi — generator obyekti qaytadi. Kod har next() chaqiruvida keyingi yieldgacha bajariladi va to'xtab turadi:

def hisobla():
    print("boshlandi")
    yield 1
    print("o'rtada")
    yield 2
    print("tugadi")
 
 
g = hisobla()           # hech narsa chiqmaydi — kod hali ishlamadi
for v in g:
    print("olindi", v)
boshlandi
olindi 1
o'rtada
olindi 2
tugadi

Generator o'zining lokal o'zgaruvchilari va qayerda to'xtaganini eslab qoladi. Bu — oddiy funksiyaning "muzlatilgan" holati.

next(g)
kod yield gacha ishlaydi
lokal holat saqlanadi
qiymat qaytariladi
funksiya muzlaydi
keyingi next(g)
aynan shu joydan davom etadi

Nega generator kerak: xotira#

import sys
 
ro_yxat = [x * x for x in range(1_000_000)]
gen = (x * x for x in range(1_000_000))
 
sys.getsizeof(ro_yxat)   # ~8 400 000 bayt (8 MB, va bu faqat ko'rsatkichlar!)
sys.getsizeof(gen)       # ~200 bayt — hajmdan qat'i nazar

Generator hech qachon hamma qiymatlarni xotirada saqlamaydi — har birini kerak bo'lganda hisoblaydi.

Katta faylni qayta ishlash#

import json
from collections.abc import Iterator
 
 
def read_jsonl(path: str) -> Iterator[dict]:
    """JSONL faylni qatorma-qator o'qiydi — fayl hajmi xotiraga ta'sir qilmaydi."""
    with open(path, encoding="utf-8") as f:
        for line_no, line in enumerate(f, 1):
            line = line.strip()
            if not line:
                continue
            try:
                yield json.loads(line)
            except json.JSONDecodeError:
                print(f"⚠️ {line_no}-qator buzilgan, o'tkazib yuborildi")
 
 
def filter_long(records: Iterator[dict], min_len: int) -> Iterator[dict]:
    for r in records:
        if len(r.get("text", "")) >= min_len:
            yield r
 
 
def batched(items: Iterator[dict], size: int) -> Iterator[list[dict]]:
    batch = []
    for item in items:
        batch.append(item)
        if len(batch) == size:
            yield batch
            batch = []
    if batch:
        yield batch
 
 
# Pipeline: hech qaysi qadam butun faylni xotiraga yuklamaydi
records = read_jsonl("dataset.jsonl")
long_records = filter_long(records, min_len=200)
for batch in batched(long_records, size=32):
    process(batch)       # masalan, modelga yuborish

Bu dangasa pipeline (lazy pipeline): har bir bosqich keyingisidan element "so'raydi", hech kim butun ma'lumotni ushlab turmaydi. 100 GB fayl bo'lsa ham, xotirada bir vaqtning o'zida faqat bitta batch bo'ladi. Hugging Face datasets kutubxonasining streaming=True rejimi aynan shu tamoyilda ishlaydi.

Cheksiz generatorlar#

def fibonacci():
    a, b = 0, 1
    while True:            # cheksiz — lekin muammo emas, chunki dangasa
        yield a
        a, b = b, a + b
 
from itertools import islice
list(islice(fibonacci(), 10))    # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

yield from — boshqa generatorga uzatish#

def flatten(nested):
    for item in nested:
        if isinstance(item, list):
            yield from flatten(item)    # ichki generatorning hamma qiymatlarini uzatadi
        else:
            yield item
 
list(flatten([1, [2, [3, 4]], 5]))   # [1, 2, 3, 4, 5]

5. Generator ifodalari#

Comprehension'ni [] o'rniga () bilan yozsangiz — generator ifodasi:

sum(x * x for x in range(10))                  # funksiyaning yagona argumenti bo'lsa, qo'shimcha qavs shart emas
any(line.startswith("ERROR") for line in log)  # birinchi topilganda to'xtaydi!
max(len(s) for s in so_zlar)
", ".join(str(x) for x in sonlar)

any() va all() bilan generator ifodasi ayniqsa samarali: any birinchi True topilganda to'xtaydi va qolgan elementlarni umuman hisoblamaydi.

Qachon ro'yxat, qachon generator?

Ro'yxat [...]Generator (...)
Natijani bir necha marta ishlatasizBir marta aylanasiz
len(), indeks, slicing kerakFaqat ketma-ket o'qish
Ma'lumot kichikMa'lumot katta yoki cheksiz
Debug qilish (ko'rish) oson bo'lishi kerakXotira muhim

6. map, filter — va nega comprehension afzal#

list(map(str.upper, ["a", "b"]))          # ['A', 'B']
list(filter(lambda x: x > 0, [-1, 2, 3])) # [2, 3]
list(filter(None, [0, 1, "", 2]))         # [1, 2] — falsy'larni olib tashlaydi

Python 3'da map va filter iterator qaytaradi. Ular ishlaydi, lekin Python jamiyatida comprehension ko'proq o'qiladigan deb hisoblanadi: [s.upper() for s in lst]. map tayyor funksiya bilan (masalan, map(int, satrlar)) qisqa va chiroyli — lambda kerak bo'lsa, comprehension yozing.

7. itertools — iteratorlar uchun asboblar qutisi#

import itertools as it
 
it.count(10, 5)                    # 10, 15, 20, ... (cheksiz)
it.cycle("ab")                     # a, b, a, b, ... (cheksiz)
it.repeat("x", 3)                  # x, x, x
it.islice(gen, 5)                  # iteratordan "slice" — birinchi 5 tasi
it.chain([1, 2], [3])              # 1, 2, 3 — ketma-ket ulash
it.chain.from_iterable(ro_yxatlar) # ro'yxatlar ro'yxatini tekislash
it.batched(range(7), 3)            # (0, 1, 2), (3, 4, 5), (6,) — Python 3.12+
it.pairwise([1, 2, 3, 4])          # (1, 2), (2, 3), (3, 4) — Python 3.10+
it.accumulate([1, 2, 3, 4])        # 1, 3, 6, 10 — yig'ma yig'indi
it.zip_longest([1, 2], [1], fillvalue=0)   # (1, 1), (2, 0)
it.takewhile(lambda x: x < 3, [1, 2, 5, 1])   # 1, 2 — shart buzilguncha

Kombinatorika — hyperparameter qidirish#

it.product("ab", [0, 1])          # ('a', 0), ('a', 1), ('b', 0), ('b', 1)
it.combinations([1, 2, 3], 2)     # (1, 2), (1, 3), (2, 3)
it.permutations([1, 2, 3], 2)     # (1, 2), (1, 3), (2, 1), ... — tartib muhim

product — grid search'ning asosi:

grid = {
    "lr": [1e-4, 3e-4, 1e-3],
    "batch_size": [16, 32],
    "dropout": [0.1, 0.3],
}
 
keys = list(grid)
for values in it.product(*grid.values()):
    params = dict(zip(keys, values))
    print(params)       # 3 * 2 * 2 = 12 ta kombinatsiya

groupby — ketma-ket guruhlash#

[(k, list(g)) for k, g in it.groupby("aaabbc")]
# [('a', ['a', 'a', 'a']), ('b', ['b', 'b']), ('c', ['c'])]

8. Performans haqida halol gap#

"Comprehension sikldan tezroq" degan gap to'g'ri, lekin farq odatda kichik (zamonaviy Python'da 10–30% atrofida): ikkalasi ham Python bytecode'da ishlaydi. Comprehension'ni tezlik uchun emas, o'qilishi uchun tanlang.

Haqiqiy tezlik farqi (10–100 baravar) — sof Python siklidan vektorlashtirilgan NumPy amaliga o'tishda keladi:

import numpy as np
 
sonlar = list(range(1_000_000))
sum(x * x for x in sonlar)          # ~25–50 ms (kompyuterga bog'liq)
 
arr = np.arange(1_000_000)
(arr * arr).sum()                   # ~1 ms

19-bobda bu farqning sababini to'liq ochamiz.

Nima noto'g'ri ketishi mumkin#

HolatNatijaTo'g'risi
Iteratorni ikki marta aylanishIkkinchisi bo'sh — xatosiz!list() ga aylantiring yoki qayta yarating
zip, map, filter natijasini ikki marta ishlatishXuddi shulist(zip(...))
Katta fayl uchun f.readlines()Butun fayl xotiradafor line in f:
Juda murakkab comprehensionO'qilmaydiOddiy sikl yoki funksiya
[x if x > 0 for x in lst]SyntaxErrorFiltr uchun if oxirida; ifoda uchun if/else boshida
groupby saralanmagan ma'lumotdaBitta kalit bir necha guruhdaAvval sorted(..., key=)
Generatorga len()TypeErrorSanash uchun sum(1 for _ in gen) (generatorni tugatadi!)
Generator ichida xatoFaqat iteratsiya paytida chiqadiPipeline'ni kichik ma'lumotda sinab ko'ring
Amaliy topshiriq

1. Comprehension'ga aylantiring

Quyidagi sikllarni comprehension bilan qayta yozing:

a = []
for s in so_zlar:
    if len(s) > 3:
        a.append(s.lower())
 
b = {}
for i, s in enumerate(so_zlar):
    b[s] = i
 
c = []
for x in sonlar:
    if x < 0:
        c.append(0)
    else:
        c.append(x)
Maslahatni ko'rsatish

Har biri bitta qator: filtr if oxirida, almashtirish if/else boshida, lug'at uchun {k: v for ...}.

Amaliy topshiriq

2. Matnni bo'laklash (chunking)

RAG tizimlarida hujjat bo'laklarga bo'linadi. chunk_text(text, size, overlap) generatorini yozing: har bir bo'lak size belgidan, qo'shni bo'laklar overlap belgi bilan ustma-ust tushsin. list(chunk_text("abcdefghij", 4, 1)) → ['abcd', 'defg', 'ghij', 'j']. Oxirgi juda qisqa bo'lakni (masalan, overlapdan qisqa) tashlab yuborish variantini ham qo'shing.

Maslahatni ko'rsatish

Generator: start=0; while start < len(text): yield text[start:start+size]; start += size - overlap. overlap >= size bo'lsa ValueError.

Amaliy topshiriq

3. Log tahlili pipeline'i

Million qatorli log faylidan (o'zingiz generator bilan yasang: f"2026-10-01 {random.choice(['INFO','WARN','ERROR'])} msg {i}") generatorlar zanjiri orqali faqat ERROR qatorlarini olib, ularning soatlar bo'yicha taqsimotini Counter bilan hisoblang. Xotira sarfini tracemalloc bilan o'lchab, ro'yxatli versiya bilan solishtiring.

Maslahatni ko'rsatish

read_lines → parse (generator) → filter (generator) → Counter. Hech qaysi bosqich ro'yxat yaratmasin.

Amaliy topshiriq

4. Grid search

itertools.product yordamida {"model": ["small", "large"], "lr": [0.1, 0.01], "epochs": [5, 10]} gridining barcha kombinatsiyalarini lug'atlar ro'yxati sifatida yarating. Har biri uchun soxta "score" hisoblang (random.random(), seed bilan) va eng yaxshi 3 tasini chiqaring.

Yodda saqlang
  • 01Comprehension: [ifoda for x in iterable if shart]; lug'at va to'plam uchun ham; o'qilishi uchun tanlang.
  • 02for sikli iter() va next() ustida ishlaydi; StopIteration tugashni bildiradi.
  • 03Iterator bir marta ishlatiladi va ikkinchi marta xatosiz bo'sh qaytadi — eng xavfli tuzoq.
  • 04yield — funksiyani 'muzlatib' qiymat qaytaradi; generator xotirada faqat joriy holatni saqlaydi.
  • 05Generatorlar zanjiri — katta ma'lumotlar uchun dangasa pipeline.
  • 06itertools: islice, chain, batched, pairwise, product, groupby — tayyor asboblar.
  • 07Katta tezlik farqi comprehension'dan emas, vektorlashtirishdan (NumPy) keladi.

Mustahkamlash

Test va amaliy masalalar

Avval testni ishlang — har javobdan keyin izoh chiqadi. Keyin masalalarni shu yerning o'zida yechib, brauzerdagi haqiqiy Python bilan avtomatik tekshiring. Natijalaringiz kurs progressi sahifasida jamlanadi.

Test: topshirilmaganMasalalar: 0/3

Bilimni tekshirish

Bob bo'yicha test

12 ta savol · har javobdan keyin to'g'ri yoki xatoligi va izoh ko'rsatiladi · oxirida natija foizda.

Amaliy masalalar

Kodni yozing va «Tekshirish»ni bosing (yoki Ctrl/⌘ + Enter). Kodingiz shu brauzerda saqlanib turadi.

01Matnni bo'laklash (generator)

chunk_text(text, size, overlap) — generator bo'lsin: har bo'lak size belgi, qo'shni bo'laklar overlap belgi ustma-ust. list(chunk_text("abcdefghij", 4, 1)) → ['abcd', 'defg', 'ghij'] — oxirgi bo'lak avvalgisi ichida to'liq bo'lsa, qo'shilmaydi. overlap >= size bo'lsa ValueError.

02Batch generatori

batched(items, size) — istalgan iterable'dan (generator ham bo'lishi mumkin) size tadan ro'yxatlar chiqaruvchi generator. Oxirgi to'liq bo'lmagan batch ham chiqsin. len() ishlatmang — kirish generator bo'lishi mumkin.

03Parametrlar filtri (dict comprehension)

build_params(**kwargs) — None bo'lmagan barcha argumentlardan lug'at qaytarsin, bitta qator dict comprehension bilan. build_params(model="opus", max_tokens=100, system=None) → {"model": "opus", "max_tokens": 100}.