Python generatorlari va yield: katta ma’lumotni tejamkor ishlash
Amaliyot3 daqInnovialabs tahririyati
Qisqacha
Generator qiymatlarni yield orqali bittadan beradi va butun to‘plamni xotirada saqlamaydi, shuning uchun istalgan hajmdagi faylni deyarli doimiy xotira sarfi bilan qatorma-qator qayta ishlash mumkin.
Qisqacha javob
Generator — yield li funksiya bo‘lib, qiymatlarni so‘ralganda bittadan qaytaradi. Keyingi elementni so‘ramaguningizcha kod bajarilmaydi. Shuning uchun generator butun to‘plamni xotiraga yuklamaydi: gigabaytlik faylni qayta ishlash taxminan bitta qatorni qayta ishlashchalik xotira oladi.
Iterable, iterator, generator
| Tushuncha | Nima | Misol |
|---|---|---|
| Iterable | sikl bilan aylanib chiqish mumkin bo‘lgan obyekt, unda __iter__ bor | list, str, dict, fayl |
| Iterator | keyingi elementni qaytaradigan va o‘rnini eslab qoladigan __next__ li obyekt | iter([1, 2, 3]) |
| Generator | yield li funksiya yoki generator ifodasi yaratgan iterator | (x * 2 for x in data) |
Ro‘yxatni istalgancha aylanib chiqish mumkin. Iterator va generator esa faqat bir marta ishlaydi: tugagach, ular bo‘sh.
yield qanday ishlaydi
def countdown(n):
while n > 0:
yield n
n -= 1
gen = countdown(3)
next(gen) # 3
next(gen) # 2
countdown(3) chaqiruvi hech narsa hisoblamaydi — u generator obyektini qaytaradi. Har bir next() tanani eng yaqin yield gacha bajaradi, qiymatni beradi va funksiya holatini muzlatadi. Tana tugaganda StopIteration ko‘tariladi va for sikli o‘zi to‘xtaydi.
Generator ifodalari
Kvadrat qavslar ro‘yxat yaratadi, oddiy qavslar — generator:
squares_list = [x * x for x in range(10_000_000)] # hammasi xotirada
squares_gen = (x * x for x in range(10_000_000)) # bittadan
total = sum(x * x for x in range(10_000_000)) # qavslarni tushirish mumkin
Natija bir marta kerak bo‘lsa — sum, max, any yoki faylga yozish uchun — generator ifodasidan foydalaning.
Katta fayl uchun lazy konveyer
Generatorlarning asosiy kuchi — ularni zanjirga ulash mumkin. Har bir qadam oqimni qabul qilib, oqim qaytaradi:
import csv
def read_rows(path):
with open(path, newline="", encoding="utf-8") as f:
yield from csv.DictReader(f)
def only_paid(rows):
for row in rows:
if row["status"] == "paid":
yield row
def amounts(rows):
for row in rows:
yield float(row["amount"])
total = sum(amounts(only_paid(read_rows("orders.csv"))))
Hech bir qadamda butun fayl xotirada bo‘lmaydi: qator o‘qiladi, filtrlanadi, o‘zgartiriladi va darhol qo‘shiladi. Xuddi shu yondashuv loglar uchun ham ishlaydi:
def errors(path):
with open(path, encoding="utf-8") as f:
for line in f:
if " ERROR " in line:
yield line.rstrip()
Haqiqiy kodda pul summalari uchun float emas, Decimal afzal — bu yerda float faqat qisqalik uchun.
yield from
yield from iterable boshqa iteratorning barcha elementlarini beradi. Bu sikldan qisqaroq va rekursiya hamda generatorni qismlarga bo‘lish uchun qulay:
def walk(tree):
yield tree.value
for child in tree.children:
yield from walk(child)
Bundan tashqari, yield from ichki generatorning send(), istisnolari va qaytariladigan qiymatini to‘g‘ri uzatadi.
Ko‘p uchraydigan tuzoqlar
- Bir martalik. Xuddi shu generatorni ikkinchi marta aylanib chiqish hech narsa bermaydi:
gen = (x for x in range(3))
list(gen) # [0, 1, 2]
list(gen) # []
Ma’lumot ikki marta kerak bo‘lsa, generatorni qaytadan yarating yoki xotiraga sig‘sa, ro‘yxatga saqlang.
- Yopilgan fayl. Agar funksiya faylni
withbilan ochib, blok ichidayieldqilmasdan generator qaytarsa, fayl o‘qilishidan oldin yopiladi.yieldniwithbloki ichida saqlang. len()va indekslar ishlamaydi. Generatorning uzunligi yo‘q,gen[5]xato beradi. Kesimlar uchunitertools.islicebor.- Kechikkan xatolar. Istisno generator yaratilganda emas, element o‘qilganda yuzaga keladi, shuning uchun traceback kutilmagan joyni ko‘rsatishi mumkin.
print(gen)bilan debugging faqat<generator object ...>ni ko‘rsatadi, ma’lumotni emas.
Generator yoki ro‘yxat
- Ma’lumot katta yoki cheksiz — generator.
- Bitta o‘tish va yig‘indi natija kerak — generator.
- Takroriy o‘tishlar, saralash,
lenyoki indekslar kerak — ro‘yxat. - Ma’lumot kam — farq sezilmaydi, o‘qilishi osonini tanlang.