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. Uniforbilan 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.
mevalar = ["olma", "anor", "uzum"]
xatchop = iter(mevalar)
print(type(xatchop))
print(next(xatchop))
print(next(xatchop))
print(next(xatchop))
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.
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:
10
20
Elementlar tugadi!
böş
Endi for tsiklining sirini bilasiz. Quyidagi ikki kod bir xil işni bajaradi:
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:
Toşkent
Andijon
Termiz
---
Toşkent
Andijon
Termiz
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:
[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 tugasaStopIterationqözğatadi.
Misol: raketa uçirişdan oldingi teskari sanoq.
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:
5 4 3 2 1 🚀 Start!
Yana bir foydali misol — oylik tölov jadvali. Iterator har bir oy uçun qolgan qarzni hisoblab beradi:
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:
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.
def teskari_sanoq(boshlanish: int):
while boshlanish >= 1:
yield boshlanish
boshlanish -= 1
for son in teskari_sanoq(5):
print(son, end=" ")
print("🚀 Start!")
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:
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:
<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.
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:
[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.
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:
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?
| Holat | Tanlov |
|---|---|
| Maʼlumotni bir marta aylanib çiqiş | Generator |
| Juda katta yoki çeksiz ketma-ketlik | Generator |
Elementlarga indeks orqali murojaat (x[5]) | Röyxat |
Maʼlumotni bir neça marta işlatiş, len() kerak | Rö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çintasini kesib oladi.
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:
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.
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:
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:
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:
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:
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:
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 tugasaStopIterationqözğatiladi.- Öz iteratoringiz uçun
__iter__va__next__metodlarini yozing. yieldişlatadigan funksiya generator böladi: u qiymat berib töxtaydi va keyinginext()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
juft_sonlar(n)generatorini yozing: u0danngaça (şu jumladan) juft sonlarni bersin.n = 10uçun natijani röyxat qilib çiqaring.
Yeçim
def juft_sonlar(n):
for i in range(0, n + 1, 2):
yield i
print(list(juft_sonlar(10)))
Oraliqiterator sinfini yozing:Oraliq(boshlanish, oxir, qadam)xuddirangekabi işlasin, lekin kasr qadamni ham qabul qilsin.Oraliq(0, 1, 0.25)ni çiqaring.
Yeçim
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)))
- 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
islicebilan çiqaring.
Yeçim
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")
- 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
shaharlar = ["Toşkent", "Nukus", "Qarşi", "Navoiy", "Jizzax"]
print(sum(len(s) for s in shaharlar if s.startswith("N")))
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
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}")