Mundarija (24)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. yield funksiyani "fabrika"ga aylantiradi
- 2.2. Bajarilish tartibi
- 2.3. Holat saqlanadi
- 2.4. Generator holatlari
- 2.5. Generatordagi return
- 2.6. PEP 479: generator ichidagi StopIteration
- 2.7. Generator — bir martalik iterator
- 2.8. send() — generatorga qiymat yuborish
- 2.9. throw() va close()
- 2.10. finally va resurslarni tozalash
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Bajarilish tartibi va holatlar
- Misol 2 — return, PEP 479 va finally
- Misol 3 — send() va throw()
- Misol 4 — Amaliy: sinf iteratori va generatorni solishtirish
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
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:
def sanoq(n):
for i in range(1, n + 1):
yield iUch qator. Va u to'liq iterator:
list(sanoq(3)) # [1, 2, 3]
next(sanoq(3)) # 1Lekin 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:
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:
generator yaratildiFunksiya tanasining birorta qatori ham bajarilmadi. U faqat next(g) chaqirilganda boshlanadi.
Bu darsda:
-
yieldfunksiyani nimaga aylantiradi - Bajarilish tartibi: qachon ishlaydi, qachon to'xtaydi
- Generator holatlari va ichki freym
- Generatordagi
returnva PEP 479 send()— generatorga qiymat yuborishthrow()vaclose()— generatorni tashqaridan boshqarishfinallybilan 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:
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:
def aldamchi():
return 42
yield # hech qachon yetib kelinmaydi
aldamchi() # <generator object ...> — 42 emas!2.2. Bajarilish tartibi
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:
- Yaratish hech narsani bajarmaydi
- Har
next()kodni keyingiyieldgacha bajaradi - Funksiya tugaganda (oxiriga yetsa yoki
returnbo'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.
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:
g = yiguvchi()
next(g)
g.gi_frame.f_locals # {'jami': 10, 'x': 10}
g.gi_frame.f_lineno # to'xtagan qator raqami2.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 |
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:
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:
list(gen()) # [1] — 'tayyor' yo'qoldiQaytish 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:
def birinchilar(royxatlar):
for r in royxatlar:
yield next(iter(r)) # r bo'sh bo'lsa — StopIteration
list(birinchilar([[1], [], [3]]))
# ❌ RuntimeError: generator raised StopIterationNega? 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:
def birinchilar(royxatlar):
for r in royxatlar:
el = next(iter(r), None)
if el is not None:
yield el2.7. Generator — bir martalik iterator
Generator obyekti iterator: iter(g) is g va u bir marta tugaydi (10.1-dars).
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:
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 iBu 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:
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:
- To'xtab turgan
yieldifodasiga yuborilgan qiymatni beradi - Kodni keyingi
yieldgacha bajaradi - O'sha
yieldning qiymatini qaytaradi
Yangi generatorga None dan boshqa qiymat yuborib bo'lmaydi — hali qabul qiluvchi yield yo'q:
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:
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 (
returnyokiGeneratorExitni qayta ko'tarish) kerak - yana
yieldqilsa —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)
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
def gen():
yield 1 # beradi va to'xtaydi
x = yield 2 # beradi, keyin send() qiymatini qabul qiladi
return "natija" # StopIteration(value="natija")Boshqaruv
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 GeneratorExitHolat
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 qatorQoidalar
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 → RuntimeError4. Batafsil misollar
Misol 1 — Bajarilish tartibi va holatlar
"""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:
=== 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
"""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:
=== 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 GeneratorExitNima ko'rsatdi: 2.5, 2.6, 2.9, 2.10-bo'limlar.
Misol 3 — send() va throw()
"""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)")
breakNatijaning muhim qismi:
=== 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
"""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:
=== 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 qatorNima 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
natijalar = hisobla(malumot)
print(len(natijalar)) # ❌ TypeError: object of type 'generator' has no len()
natijalar = list(hisobla(malumot)) # ✅2. Generatorni ikki marta aylanish
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
def gen(royxatlar):
for r in royxatlar:
yield next(iter(r)) # ❌ bo'sh r → RuntimeError
yield next(iter(r), None) # ✅ yoki bo'shini tekshiring4. Generatorni tugatish uchun raise StopIteration
def gen():
yield 1
raise StopIteration # ❌ RuntimeError (PEP 479)
def gen():
yield 1
return # ✅5. Yangi generatorga send()
g = ortacha()
g.send(10) # ❌ TypeError
g = ortacha(); next(g); g.send(10) # ✅ yoki ishga tushiruvchi dekorator6. GeneratorExit ni yutish
try:
yield x
except GeneratorExit:
yield "oxirgi" # ❌ RuntimeError: generator ignored GeneratorExit
except GeneratorExit:
tozala()
raise # ✅ yoki shunchaki return7. Resursni generator yo'q bo'lishiga ishonib qoldirish
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
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 —
yieldning 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 vareturnqiymatini olish - 10.6–10.7-darslar:
contextlib.contextmanager— generator asosidagi context manager - 14-qism:
async defvaawait— generatorlardan o'sib chiqqan korutinalar - 17-qism:
pytestfixture'lariyieldbilan tozalashni amalga oshiradi
8. Eng yaxshi amaliyotlar
Oddiy ketma-ketlik uchun iterator sinfi emas, generator yozing. Kod qisqa, holat avtomatik saqlanadi.
Qayta ishlatiladigan to'plam kerak bo'lsa,
__iter__ni generator qiling. Konteyner va iterator ajratilishi saqlanadi.Generatorni
returnbilan tugating.raise StopIterationyozmang.Ichkarida
next()ni sukut bilan chaqiring. PEP 479 dagiRuntimeErrordan saqlaydi.send()ishlatadigan generatorlar uchun ishga tushiruvchi dekorator. Birinchinext()ni unutish xatosini yo'q qiladi.Tozalash uchun
try/finally. Lekin aniq yopish vaqtigaclose()yokiwithbilan erishing.GeneratorExitni ushlasangiz — qayta ko'taring yoki qayting. Hech qachon keyinyieldqilmang.Generator obyektini hujjatda aytib o'ting. Qaytish turini
Iterator[T]yokiGenerator[Y, S, R]deb belgilang (13-qism).
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
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
B— generator yaratildi, lekin tana bajarilmadi[1, 2] [1, 2]— har chaqiruv yangi generator[1]—returnqiymatilist()da yo'qoladiFalse []—yieldborligi uchun generator;return 5darholStopIteration(5)1 40—send(4)xga 4 beradi, keyingiyield40 ni qaytaradi1 tugadiFkeyintamom—close()finallyni bajaradiRuntimeError— ichkaridan chiqqanStopIteration(PEP 479)GEN_CREATED GEN_CLOSEDTrue generator5 8—n += 5→ 5,n += 3→ 8ushlandi
Vazifa 2: Xatolarni tuzating
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
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:
- Rekursiyasiz (stek va
dequebilan) - Har biri 10 qatordan oshmasin
Daraxt.__iter__ni generator qiling va ikki marta aylanish ishlashini tekshiring- 50 000 darajali daraxtda sinang
Vazifa 4: Yuruvchi statistika
send() bilan ishlaydigan statistika() generatorini yozing:
- Har
send(qiymat)dan keyin{"soni", "ortacha", "min", "max"}qaytarsin throw(ValueError)— oxirgi qiymatni bekor qilsin (buning uchun oxirgi holatni saqlang)close()da yakuniy hisobotni chop etsin- Ishga tushiruvchi dekorator bilan ishlasin
Vazifa 5: Tozalanishni kuzatish
resursli()generatorini yozing, u ochilgan va yopilgan resurslar sonini global hisoblagichda saqlasin- Oxirigacha,
break, xato vaclose()holatlarida hisoblagich nolga qaytishini tekshiring - Generatorga havola ro'yxatda qolganda hisoblagich nolga qaytmasligini ko'rsating
weakref.finalizebilan kuzating
Vazifa 6: Holat mashinasi
Misol 3 dagi svetofor ni kengaytiring:
- Noma'lum buyruqda generator yopilmasin —
KeyErrorni ichkarida ushlasin - Har holatda qancha buyruq qolganini (
taymer) saqlasin send(None)— taymerni bir qadamga kamaytirsin, nolga yetsa keyingi holatga o'tsin- Holatlar tarixini
returnqiymati 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.
def f():
x = 1
return x
# f() tugagach x yo'q — freym tashlandi2. 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:
- Qiymat chaqiruvchiga qaytariladi
- Instruksiya ko'rsatkichi va baholash steki freymda qoldiriladi
- 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.
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 joyiYa'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
- Holat freymda: lokal o'zgaruvchilar, baholash steki va instruksiya ko'rsatkichi
- Oddiy funksiya freymni tashlaydi, generator uni o'z obyektida saqlaydi
- Bu ipdan ancha arzon — yuz minglab to'xtab turgan generator mumkin
- PEP 255 dan PEP 492 gacha bo'lgan yo'l — bitta g'oyaning rivojlanishi,
async/awaitham shu mexanizmga tayanadi - 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:
yieldfunksiyani "to'xtab turadigan chaqiruv"ga aylantiradi. Generator funksiyani chaqirish tanani bajarmaydi — faqat generator obyekti yaratiladi. Harnext()kodni keyingiyieldgacha bajaradi va lokal holat freymda saqlanib qoladi.yieldhech qachon bajarilmaydigan joyda bo'lsa ham, funksiya generatorga aylanadi.Generator o'z tugashini boshqaradi.
return qiymat—StopIteration.valuega tushadi vaforuni ko'rmaydi. Ichkaridan tasodifan chiqib ketganStopIteration3.7 dan beriRuntimeErrorberadi (PEP 479), shuning uchun tugatish faqatreturnbilan.try/finallyresurslarni oxirigacha aylantirishda,breakvaclose()da tozalaydi — lekin generatorga havola qolsa, tozalash kechikadi.send(),throw()vaclose()generatorni ikki tomonlama aloqaga aylantiradi.yieldifoda sifatida yuborilgan qiymatni qabul qiladi (avvalnext()bilan ishga tushirish shart),throw()to'xtagan joyda istisno ko'taradi,close()esaGeneratorExityuboradi — uni yutib, yanayieldqilishRuntimeError. Bu mexanizmlar keyinchalikasynciovaasync/awaitning poydevori bo'ldi.
Keyingi darsda generatorlarni amaliyotda ishlatamiz: ma'lumot quvurlari, cheksiz oqimlar bilan ishlash, itertools bilan birlashtirish va generatorlarning keng tarqalgan naqshlari.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!