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 - bu sanab bo'ladigan qiymatlarni o'z ichiga oladigan obyekt. U ustidan iteratsiya qilish mumkin, ya'ni barcha qiymatlarni ketma-ket 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 ma'lum limit'gacha 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 loop ishlatganda, Python __iter__() va StopIteration exception'ni avtomatik boshqaradi.

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

2. Generators

Generator - iterator yaratishning sodda usuli. __iter__() va __next__() bilan katta class yozish o'rniga, oddiy funksiya yozasiz va qiymat qaytaradigan 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.

List bilan (xotira yeydi):

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 Comprehension ga o'xshaydi, lekin () ishlatiladi. Natija list emas, generator obyekt 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):

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):

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)

Xulosa