ლექცია 13 · XIII კვირაLecture 13 · Week XIII

იტერატორები და
კონტეინერები
Iterators and
containers

როგორ მუშაობს for სინამდვილეში · iter() და next() · გენერატორები How for really works · iter() and next() · generators

დღეს ასევე: პრაქტიკული დავალება #4 Also today: practical assignment #4

გამეორება · ლექციები 11–12Review · lectures 11–12

შემოწმება — 9 კითხვაCheck yourself — 9 questions

1. როგორ შევქმნათ ცარიელი set და რატომ არა {}-ით? 1. How do you create an empty set, and why not with {}?
set()-ით. {} ქმნის ცარიელ ლექსიკონს. With set(). {} creates an empty dictionary.
2. რას აბრუნებს {1,2,3} & {2,3,4} და {1,2,3} - {2,3,4}? 2. What do {1,2,3} & {2,3,4} and {1,2,3} - {2,3,4} return?
{2, 3} (თანაკვეთა) და {1} (სხვაობა). {2, 3} (intersection) and {1} (difference).
3. რა განსხვავებაა d["x"]-სა და d.get("x")-ს შორის? 3. What is the difference between d["x"] and d.get("x")?
[] გასაღების არარსებობისას ისვრის KeyError-ს; get() აბრუნებს None-ს (ან მითითებულ ნაგულისხმევს). [] raises KeyError if the key is missing; get() returns None (or a default you supply).
4. როგორ გავიაროთ ლექსიკონზე გასაღებთან და მნიშვნელობასთან ერთად? 4. How do you loop a dictionary with both key and value?
for k, v in d.items():
5. დაწერე ერთი ხაზი, რომელიც სიიდან დუბლიკატებს აშორებს. 5. Write one line that removes duplicates from a list.
unique = list(set(data)). ⚠ თავდაპირველი რიგი არ შენარჩუნდება — თუ საჭიროა, list(dict.fromkeys(data)). unique = list(set(data)). ⚠ The original order is lost — if you need it, use list(dict.fromkeys(data)).
6. რომელი ბლოკი სრულდება ყოველთვის — else თუ finally? 6. Which block always runs — else or finally?
finally. else სრულდება მხოლოდ მაშინ, თუ try-ში შეცდომა არ მოხდა. finally. The else runs only if no error occurred in the try.
7. რატომ არის ცუდი except: pass? 7. Why is except: pass bad?
„შიშველი“ except ყველაფერს იჭერს (Ctrl+C-საც), pass კი პრობლემას ჩუმად მალავს — შეცდომა მოგვიანებით სულ სხვა ადგილას გამოჩნდება. A bare except catches everything (including Ctrl+C), and pass hides the problem silently — the error resurfaces somewhere else entirely.
8. რომელი გამონაკლისი მოხდება? int("abc") · [1,2][9] · "5" + 5 8. Which exception is raised by int("abc") · [1,2][9] · "5" + 5?
ValueError · IndexError · TypeError.
9. რას აკეთებს raise? 9. What does raise do?
განზრახ „ისვრის“ გამონაკლისს — გამოიყენება არასწორი მონაცემის მაშინვე უარსაყოფად: raise ValueError("ასაკი უარყოფითი ვერ იქნება"). It deliberately throws an exception — used to reject invalid data immediately: raise ValueError("age cannot be negative").

ცნებაConcept

კონტეინერი — რა არისWhat a container is

ობიექტი, რომელიც სხვა ობიექტებს იტევს და შეუძლია პასუხი გასცეს კითხვას „ეს შენში არის?“

An object that holds other objects and can answer the question “is this one of yours?”

print(3 in [1, 2, 3])           # list
print("a" in ("a", "b"))        # tuple
print(1 in {1, 2})              # set
print("key" in {"key": 1})      # dict — searches the keys
print("th" in "python")         # str

# every one of them supports len()
print(len([1,2,3]), len("abc"), len({1,2}))

დალაგებულიOrdered

list · tuple · str · range — ინდექსით მუშაობს

list · tuple · str · range — support indexing

დაულაგებელიUnordered

set · dict — ინდექსი არ აქვს, სამაგიეროდ ძებნა სწრაფია

set · dict — no indexing, but lookups are fast

მთავარიThe core idea

რა ხდება for-ის შიგნითWhat happens inside a for

for x in [10, 20, 30]:
    print(x)
it = iter([10, 20, 30])   # get an iterator
while True:
    try:
        x = next(it)       # the next element
    except StopIteration:  # elements exhausted
        break
    print(x)
ორი ცნება Iterable (გასავლელი) — ობიექტი, რომელზეც for-ით გავლა შეიძლება: list, str, dict, ფაილი… Iterator (მიმდევრობის მკითხველი) — ობიექტი, რომელსაც ახსოვს, სად გაჩერდა, და next()-ზე შემდეგს აბრუნებს Two concepts An iterable is anything you can loop over with for: list, str, dict, a file… An iterator is the object that remembers where it stopped and hands back the next item on next()
ეს ლექციის გასაღები სლაიდია. სტუდენტებს აქამდე for „ჯადოსნური“ ეგონათ — აქ ხდება ცხადი, რომ ისიც ჩვეულებრივი მექანიზმია. The key slide of the lecture. Until now for felt like magic — here it becomes an ordinary mechanism.

პრაქტიკაPractice

iter() და next() ხელითiter() and next() by hand

numbers = [10, 20, 30]
it = iter(numbers)

print(next(it))    # 10
print(next(it))    # 20
print(next(it))    # 30
print(next(it))    # StopIteration!

# with a default value — no error
it = iter(numbers)
print(next(it, "დასრულდა"))   # 10
print(next(it, "დასრულდა"))   # 20
print(next(it, "დასრულდა"))   # 30
print(next(it, "დასრულდა"))   # დასრულდა
იტერატორი ერთჯერადია An iterator is single-use
it = iter([1, 2, 3])
print(list(it))    # [1, 2, 3]
print(list(it))    # []  — already exhausted!
სია ხელახლა გავლადია, იტერატორი — არა. A list can be traversed again; an iterator cannot.

შექმნაBuilding one

საკუთარი იტერატორიYour own iterator

class Countdown:
    """უკუთვლა n-დან 1-მდე. / Counts down from n to 1."""

    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self                # it is its own iterator

    def __next__(self):
        if self.current <= 0:
            raise StopIteration    # the signal: we are done
        value = self.current
        self.current -= 1
        return value


for n in Countdown(5):
    print(n, end=" ")              # 5 4 3 2 1
ორი მეთოდი — ესაა მთელი „კონტრაქტი“__iter__() აბრუნებს იტერატორს, __next__() — შემდეგ ელემენტს ან ისვრის StopIteration-ს. ვინც ამ ორ მეთოდს ატარებს, მასზე for იმუშავებს. Two methods — that is the whole contract__iter__() returns an iterator, __next__() returns the next item or raises StopIteration. Anything implementing both works with for.

კლასები კურსის პროგრამაში არაა — აქ ის მხოლოდ მექანიზმის საჩვენებლადაა. ქვემოთ ბევრად მარტივი გზაა. Classes are not part of this course — this is only to show the mechanism. A much simpler route follows.

მარტივი გზაThe simple route

გენერატორი — yieldGenerators — yield

class Countdown:
    def __init__(self, start):
        self.current = start
    def __iter__(self):
        return self
    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        v = self.current
        self.current -= 1
        return v
def countdown(start):
    while start > 0:
        yield start
        start -= 1


for n in countdown(5):
    print(n, end=" ")     # 5 4 3 2 1
yield vs returnreturn ფუნქციას ამთავრებს. yield მნიშვნელობას აბრუნებს და ფუნქციას „აპაუზებს“ — შემდეგი next() სწორედ იმ ადგილიდან გააგრძელებს. yield vs returnreturn ends the function. yield hands back a value and pauses it — the next next() resumes from exactly that point.
ეს დემონსტრაცია დებაგერით შესანიშნავად მუშაობს: breakpoint yield-ზე და ნახავენ, როგორ „ბრუნდება“ შესრულება იმავე ხაზზე. This demos beautifully in the debugger: a breakpoint on the yield shows execution returning to the same line.

უპირატესობაThe advantage

„ზარმაცი“ გამოთვლა (lazy evaluation)Lazy evaluation

import sys

# a list — every element is in memory at once
squares_list = [n ** 2 for n in range(1_000_000)]
print(sys.getsizeof(squares_list))     # ~8,400,000 bytes

# a generator — each element is produced on demand
squares_gen = (n ** 2 for n in range(1_000_000))
print(sys.getsizeof(squares_gen))      # ~200 bytes

print(sum(squares_gen))                # works, without the memory

ფრჩხილები მნიშვნელოვანიაThe brackets matter

[x for x in ...] → სია (list comprehension)
(x for x in ...) → გენერატორი

[x for x in ...] → a list
(x for x in ...) → a generator

უსასრულო მიმდევრობაAn endless sequence

def naturals():
    n = 1
    while True:
        yield n
        n += 1

სიად ეს შეუძლებელიაImpossible as a list

სად ხვდები ამას ყოველდღიურადrange(), enumerate(), zip(), map(), filter() და ფაილი — ყველა ზარმაცია. ამიტომაც კითხულობს for line in f გიგაბაიტიან ფაილს პრობლემის გარეშე. Where you already meet thisrange(), enumerate(), zip(), map(), filter() and files are all lazy. That is precisely why for line in f handles a gigabyte file without trouble.

პრაქტიკაPractice

გენერატორები რეალურ ამოცანაშიGenerators in a real task

def read_lines(path):
    """ფაილს ხაზ-ხაზ კითხულობს, ცარიელებს ტოვებს.
    Reads the file line by line, skipping blanks."""
    with open(path, encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if line:
                yield line


def only_errors(lines):
    """მხოლოდ შეცდომების ხაზები. / Only the error lines."""
    for line in lines:
        if "ERROR" in line:
            yield line


def first_words(lines):
    for line in lines:
        yield line.split()[0]


# a chain — not a single intermediate list is built
for word in first_words(only_errors(read_lines("app.log"))):
    print(word)
ეს არის „მილსადენი“ (pipeline)თითოეული ფუნქცია პატარაა, ცალკე იტესტება, და მონაცემი მათ შორის ერთ ელემენტად მიედინება — არა მთელი სიით. This is a pipelineEach function is small and independently testable, and the data flows through one item at a time rather than as a whole list.

ბიბლიოთეკაLibrary

itertools — მზა იტერატორებიitertools — ready-made iterators

from itertools import count, cycle, islice, chain, product, combinations

# an infinite counter
for n in islice(count(10, 5), 4):
    print(n, end=" ")            # 10 15 20 25

# repeating in a cycle
colors = cycle(["წითელი", "მწვანე"])
print(list(islice(colors, 5)))

# joining sequences
print(list(chain([1, 2], [3, 4])))       # [1, 2, 3, 4]

# the cartesian product
print(list(product("AB", [1, 2])))
# [('A',1), ('A',2), ('B',1), ('B',2)]

# combinations
print(list(combinations([1, 2, 3], 2)))
# [(1,2), (1,3), (2,3)]
პრინციპისანამ საკუთარს დაწერ — შეამოწმე, ხომ არ არსებობს მზა. itertools სტანდარტული ბიბლიოთეკის ნაწილია, დაყენება არ სჭირდება. The principleBefore writing your own, check whether it already exists. itertools ships with Python — nothing to install.

შეფასებაAssessment

პრაქტიკული დავალება #4Practical assignment #4

8 ქულა · სრულდება აუდიტორიაში, დამოუკიდებლად 8 points · done in class, independently

  1. iter/next ხელითსიაზე გაიარე while True-თი, next()-ითა და StopIteration-ის დაჭერით
  2. გენერატორი even_numbers(limit)აბრუნებს ლუწ რიცხვებს yield-ით. შეადარე იმავე ლოგიკის სიისეულ ვარიანტს sys.getsizeof()-ით
  3. ფიბონაჩის გენერატორიუსასრულო; პირველი 15 წევრი ამოიღე islice-ით
  4. ფაილის მილსადენისამი გენერატორი: ხაზების კითხვა → გაფილტვრა → გარდაქმნა. ჩააერთე ერთ ჯაჭვში
  5. ახსნადაწერე კომენტარად: რით განსხვავდება [x for x in ...] და (x for x in ...)
  1. iter/next by handWalk a list with while True, next() and a caught StopIteration
  2. A generator even_numbers(limit)Yields even numbers with yield. Compare it to the list version using sys.getsizeof()
  3. A Fibonacci generatorInfinite; take the first 15 terms with islice
  4. A file pipelineThree generators: read lines → filter → transform. Chain them together
  5. ExplainWrite as a comment: how [x for x in ...] differs from (x for x in ...)

შემაჯამებელიWrap-up

რა უნდა დაგამახსოვრდესWhat to remember

შემდეგი — ლექცია 14PEP 8, კოდის სტილი, style checker-ები და PyPI პაკეტების რეპოზიტორი. Next — lecture 14PEP 8, code style, style checkers and the PyPI package index.