Tutorial Material

Iterator'lar va generator'lar

Share to
Python iterators va generators

Python'da iterator va generator'lar xotira tejamkorligi (memory efficiency) uchun juda muhim. Ular katta hajmdagi ma'lumotni (hatto cheksiz ketma-ketliklarni ham) hammasini bir yo'la RAM'ga yuklamasdan, bo'laklab qayta ishlashga imkon beradi.

Qalin kitobni o'qish misolini oling: iterator sizga sahifa-sahifa o'qishga (faqat bitta sahifani xotirada ushlab turishga) yordam beradi. 1000 sahifani birdan yodlab olish shart emas.

1. Iterators

Iterator — sanab bo'ladigan qiymatlarni ketma-ket beradigan obyekt. U ustidan iteratsiya qilish, ya'ni qiymatlarni navbatma-navbat ko'rib chiqish mumkin.

Texnik jihatdan, Python'da iterator — iterator protokolini amalga oshiradigan obyekt bo'lib, __iter__() va __next__() metodlariga ega bo'ladi.

Iterator yaratish misoli

Quyida 1 dan boshlab berilgan limitgacha son qaytaradigan oddiy iterator:

class MyNumbers:
    def __init__(self, limit):
        self.limit = limit
        self.num = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.num <= self.limit:
            x = self.num
            self.num += 1
            return x
        else:
            raise StopIteration

myclass = MyNumbers(3)
myiter = iter(myclass)

print(next(myiter)) # Natija: 1
print(next(myiter)) # Natija: 2
print(next(myiter)) # Natija: 3
# print(next(myiter)) # StopIteration xatosi chiqaradi

for siklidan foydalanganda Python __iter__() va StopIteration exception'ini avtomatik boshqaradi.

for x in MyNumbers(3):
    print(x)

2. Generators

Generator — iterator yaratishning sodda usuli. __iter__() va __next__() bilan katta sinf yozish o'rniga, oddiy funksiya yozasiz va qiymat qaytariladigan joyda yield ishlatasiz.

Har safar yield chaqirilganda funksiya "pauza" qiladi, o'zgaruvchilar holatini saqlab qoladi va keyingi chaqiriqda o'sha joydan davom etadi.

Oddiy generator misoli

def number_generator(limit):
    num = 1
    while num <= limit:
        yield num
        num += 1

gen = number_generator(3)
# Generator ham iterator!
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3

Generator afzalligi: xotirani tejash

Masalan, 1 million sonni qayta ishlashingiz kerak.

Ro'yxat bilan (xotira ko'p sarflanadi):

def get_list():
    result = []
    for i in range(1000000):
        result.append(i)
    return result

# Bu butun sonlar ro'yxati uchun taxminan 40MB+ xotira sarflaydi

Generator bilan (xotirani tejaydi):

def get_generator():
    for i in range(1000000):
        yield i

# Bu deyarli qo'shimcha xotira sarflamaydi, chunki sonlar so'ralganda bittadan hosil qilinadi.

3. Generator expression

List comprehensionga o'xshaydi, lekin () ishlatiladi. Natija ro'yxat emas, generator obyekti bo'ladi.

# List comprehension (to'liq list'ni xotirada yaratadi)
squares_list = [x**2 for x in range(10)]
print(squares_list) # [0, 1, 4, ..., 81]

# Generator expression (lazy evaluation)
squares_gen = (x**2 for x in range(10))
print(squares_gen) # <generator object ...>

# Tarkibini ko'rish uchun iteratsiya qilish kerak
for i in squares_gen:
    print(i, end=" ")

4. Amaliy holat: katta fayllarni o'qish

Masalan, 10GB server log faylini qayta ishlash kerak bo'lsa:

Noto'g'ri (bunday qilmang):

Bu kod parchasi konseptual anti-pattern'ni ko'rsatadi va katta server.log fayli mavjud deb hisoblaydi. Fayl bo'lmasa, bajarilganda xato yuzaga keladi.
def read_file_wrong(filename):
    file = open(filename)
    content = file.read() # Xavfli! Butun 10GB ni RAM ga yuklaydi.
    return content.split("\n")

To'g'ri (generator ishlating):

Bu to'g'ri yondashuv, lekin server.log papkangizda mavjud bo'lmasa, FileNotFoundError chiqaradi.
def read_file_right(filename):
    with open(filename, "r", encoding="utf-8") as handle:
        for line in handle:
            yield line

with open("server.log", "w", encoding="utf-8") as handle:
    handle.write("INFO Started\n")
    handle.write("ERROR Disk nearly full\n")
    handle.write("INFO Completed\n")

# 10GB faylni xotira muammolarsiz o'qishimiz mumkin
for line in read_file_right("server.log"):
    if "ERROR" in line:
        print(line)

5. Cheksiz generatorlar

Qiymatlar talab bo'yicha hosil qilinganligi sababli, generator cheksiz ketma-ketlikni ifodalashi mumkin. Qachon to'xtashni chaqiruvchi hal qiladi:

def count_up(start=0):
    num = start
    while True:           # o'zi hech qachon tugamaydi
        yield num
        num += 1

gen = count_up(10)
print(next(gen))   # 10
print(next(gen))   # 11
print(next(gen))   # 12

Cheksiz generator bo'ylab for sikli o'zi break qilishi shart, aks holda u cheksiz ishlaydi:

def count_up(start=0):
    num = start
    while True:
        yield num
        num += 1

for n in count_up():
    if n > 3:
        break
    print(n)   # 0 1 2 3

6. yield from bilan delegatsiya

Bitta generator boshqa iterable'ning barcha qiymatlarini berishi kerak bo'lganda, yield from buni qo'lda sikl o'rniga bitta qatorda bajaradi:

def letters():
    yield from "AB"

def numbers():
    yield from range(1, 3)

def combined():
    yield from letters()
    yield from numbers()

print(list(combined()))   # ['A', 'B', 1, 2]

Bu generator quvurlarini tekis va o'qilishi qulay saqlaydi hamda ayniqsa ichma-ich ma'lumotni tekislashda foydali.

Xulosa