ლექცია 12 · XII კვირაLecture 12 · Week XII

შეცდომები და
გამონაკლისები
Errors and
exceptions

Exceptions Python-ში · try-except-else-finally · with-as Exceptions in Python · try-except-else-finally · with-as

კლასიფიკაციაClassification

შეცდომების სამი ჯგუფიThree families of error

1 · სინტაქსური1 · Syntax

კოდი საერთოდ არ ეშვება.

The code does not run at all.

if x > 5
    print(x)
# SyntaxError

ვასწორებთ წერისას. დამუშავება შეუძლებელია. Fixed while writing. It cannot be handled.

2 · გამონაკლისი2 · Exceptions

კოდი გაეშვა, შუაში ჩავარდა.

The code started, then failed midway.

print(10 / 0)
# ZeroDivisionError

ეს ლექცია სწორედ ამაზეა. This lecture is about exactly these.

3 · ლოგიკური3 · Logic

იმუშავა, მაგრამ არასწორად.

It ran, but produced the wrong answer.

avg = a + b / 2
# the parentheses are missing

ყველაზე მზაკვრული — Python შეცდომას ვერ ხედავს. The most treacherous — Python sees nothing wrong.

მესამე ჯგუფი გამოცდაზე ხშირად ავიწყდებათ. სწორედ მისთვისაა დებაგერი და ტესტები. The third family is often forgotten in exams. It is exactly what debuggers and tests exist for.

კითხვის უნარიA reading skill

Traceback — როგორ წავიკითხოთHow to read a traceback

Traceback (most recent call last):
  File "main.py", line 12, in <module>
    result = divide(10, 0)
             ^^^^^^^^^^^^^
  File "main.py", line 8, in divide
    return a / b
           ~~^~~
ZeroDivisionError: division by zero
  1. წაიკითხე ბოლოდანუკანასკნელი ხაზი ამბობს რა მოხდა: ZeroDivisionError: division by zero
  2. ზემოთ — სადline 8, in divide — ზუსტი ფაილი და ხაზი
  3. ზევით-ქვევით — გამოძახებათა ჯაჭვივინ ვის დაუძახა. „most recent call last“ — ბოლო გამოძახება ბოლოშია
  1. Read it bottom-upThe last line says what happened: ZeroDivisionError: division by zero
  2. Above it — whereline 8, in divide — the exact file and line
  3. The whole stack — who called whom“most recent call last” — the newest call is at the bottom
ეს უნარი დაზოგავს საათებსდამწყები ხედავს „წითელ ტექსტს“ და პანიკობს. გამოცდილი კითხულობს ორ ხაზს და მაშინვე იცის, სად წავიდეს. This skill saves hoursA beginner sees “red text” and panics. An experienced developer reads two lines and knows exactly where to go.

ლექსიკაVocabulary

ხშირი გამონაკლისებიCommon exceptions

გამონაკლისიException როდის ჩნდებაWhen it appears მაგალითიExample
ZeroDivisionErrorნულზე გაყოფაDivision by zero10 / 0
ValueErrorტიპი სწორია, მნიშვნელობა — არაRight type, wrong valueint("abc")
TypeErrorოპერაცია არასწორ ტიპზეAn operation on the wrong type"5" + 5
IndexErrorინდექსი დიაპაზონს გარეთIndex out of range[1,2][5]
KeyErrorგასაღები ლექსიკონში არააThe key is not in the dictionaryd["x"]
NameErrorცვლადი არ არსებობსThe variable does not existprint(undefined)
AttributeErrorობიექტს ასეთი მეთოდი არ აქვსThe object has no such method"abc".push()
FileNotFoundErrorფაილი ვერ მოიძებნაThe file was not foundopen("no.txt")
ImportErrorმოდული ვერ ჩაიტვირთაThe module could not be loadedimport nomodule
ყველა მათგანი Exception-ის შვილიაამიტომ except Exception: ყველას იჭერს — მაგრამ, როგორც ვნახავთ, ეს ხშირად ცუდი იდეაა. All of them descend from Exceptionwhich is why except Exception: catches them all — and, as we will see, that is usually a bad idea.

კონსტრუქციაConstruct

try / except

age = int(input("ასაკი: "))
print(age + 1)

# input: "ოცი"
# ValueError → the program crashes
try:
    age = int(input("ასაკი: "))
    print(age + 1)
except ValueError:
    print("ეს რიცხვი არ არის")

print("პროგრამა გრძელდება")
try:
    risky code
except SpecificError:
    what to do in that case
ლოგიკასცადე ეს კოდი; თუ ჩავარდა ამ შეცდომით — ავარიის ნაცვლად აი ეს გააკეთე.“ The logicTry this code; if it fails with this error, do the following instead of crashing.”

კონსტრუქციაConstruct

რამდენიმე except და asMultiple except blocks and as

try:
    a = int(input("გასაყოფი: "))
    b = int(input("გამყოფი: "))
    print(a / b)

except ValueError:
    print("რიცხვი შეიყვანე")

except ZeroDivisionError:
    print("ნულზე გაყოფა შეუძლებელია")

except (TypeError, KeyError):        # several at once
    print("ტიპის ან გასაღების პრობლემა")

except Exception as e:               # last — everything else
    print(f"მოულოდნელი შეცდომა: {e}")
    print(type(e).__name__)          # ZeroDivisionError
თანმიმდევრობა მნიშვნელოვანიაPython პირველივე შესაფერის except-ს იყენებს. Exception ყველას იჭერს, ამიტომ ის ყოველთვის ბოლოშია — წინააღმდეგ შემთხვევაში დანარჩენები არასდროს იმუშავებს. Order mattersPython uses the first matching except. Exception catches everything, so it always goes last — otherwise the others would never run.

კონსტრუქციაConstruct

else დაand finally

try:
    f = open("data.txt", encoding="utf-8")
    data = f.read()

except FileNotFoundError:
    print("ფაილი ვერ მოიძებნა")

else:
    # runs only if no error occurred
    print(f"წაკითხულია {len(data)} სიმბოლო")

finally:
    # always runs — with or without an error
    print("ოპერაცია დასრულდა")
ბლოკიBlock როდის სრულდებაWhen it runs
tryყოველთვის — სანამ შეცდომა არ მოხდებაAlways — until an error occurs
exceptმხოლოდ შესაბამისი შეცდომისასOnly for the matching error
elseმხოლოდ თუ არცერთი შეცდომა არ მოხდაOnly if no error occurred
finallyყოველთვის — რესურსების დახურვისთვისAlways — for releasing resources

კონსტრუქციაConstruct

raise — შეცდომის დაგდებაraise — throwing an error yourself

def set_age(age):
    if not isinstance(age, int):
        raise TypeError("ასაკი მთელი რიცხვი უნდა იყოს")
    if age < 0:
        raise ValueError("ასაკი უარყოფითი ვერ იქნება")
    if age > 150:
        raise ValueError(f"ასაკი {age} არარეალურია")
    return age


try:
    set_age(-5)
except ValueError as e:
    print("შეცდომა:", e)
რატომ ღირსფუნქცია არასწორ მონაცემს მაშინვე უნდა უარყოფდეს, არა ჩუმად აგრძელებდეს. ეს პრობლემას წყაროსთან ჩერდება, არა 50 ხაზის შემდეგ. Why it pays offA function should reject bad data immediately rather than quietly carrying on. That stops the problem at its source, not 50 lines later.
კარგი შედარებაა: raise არის „განგაშის ღილაკი“, except კი — „ვინც განგაშს პასუხობს“. A useful image: raise is the alarm button, except is whoever answers the alarm.

ინსტრუქციაStatement

with ... as — კონტექსტის მენეჯერიwith ... as — the context manager

f = open("data.txt", encoding="utf-8")
try:
    data = f.read()
finally:
    f.close()      # always closed
with open("data.txt", encoding="utf-8") as f:
    data = f.read()

# closed automatically
რას აკეთებს with სინამდვილეშიბლოკში შესვლისას რესურსს „ხსნის“, გამოსვლისას — „ხურავს“. მაშინაც კი, თუ შიგნით return, break ან შეცდომა მოხდა. What with actually doesIt acquires the resource on entering the block and releases it on leaving — even if a return, a break or an error happens inside.
# several resources at once
with open("in.txt", encoding="utf-8") as src, \
     open("out.txt", "w", encoding="utf-8") as dst:
    for line in src:
        dst.write(line.upper())

with მუშაობს არა მხოლოდ ფაილებთან — ბაზის კავშირებთან, ქსელურ სოკეტებთან, დროებით საქაღალდეებთან. with works beyond files — database connections, network sockets, temporary directories.

ფილოსოფიაPhilosophy

ორი მიდგომა: LBYL და EAFPTwo approaches: LBYL and EAFP

LBYL

Look Before You Leap — ჯერ შეამოწმე

Look Before You Leap — check first

if os.path.exists(path):
    with open(path) as f:
        data = f.read()
else:
    print("ფაილი არაა")

პრობლემა: შემოწმებასა და გახსნას შორის ფაილი შეიძლება წაიშალოს. The catch: the file can vanish between the check and the open.

EAFP

Easier to Ask Forgiveness than Permission — სცადე

Easier to Ask Forgiveness than Permission — just try it

try:
    with open(path) as f:
        data = f.read()
except FileNotFoundError:
    print("ფაილი არაა")

Python-ში ეს უპირატესი სტილია. In Python this is the preferred style.

პრაქტიკული წესიროცა შეცდომა იშვიათიაtry/except. როცა მოსალოდნელია (მომხმარებლის შეყვანა) — შემოწმებაც გამოდგება. ხშირად ორივე ერთად გამოიყენება. A practical ruleWhen the error is raretry/except. When it is expected (user input) — a check works too. Often both are used together.

შაბლონიPattern

საიმედო შეყვანა — ციკლი + tryRobust input — a loop plus try

def read_int(prompt, low=None, high=None):
    """კითხულობს მთელ რიცხვს, სანამ სწორს არ შეიყვანენ.
    Keeps asking until a valid integer is entered."""
    while True:
        try:
            value = int(input(prompt))
        except ValueError:
            print("  ⚠ მთელი რიცხვი შეიყვანე")
            continue

        if low is not None and value < low:
            print(f"  ⚠ არანაკლებ {low}")
            continue
        if high is not None and value > high:
            print(f"  ⚠ არაუმეტეს {high}")
            continue

        return value


age = read_int("ასაკი: ", low=0, high=120)
score = read_int("ქულა: ", low=0, high=100)
print(f"ასაკი {age}, ქულა {score}")
ეს ფუნქცია შეინახეის ყველა შემდეგ დავალებაში გამოგადგება. სწორედ ასე იზრდება საკუთარი „ინსტრუმენტების ყუთი“. Keep this functionYou will reuse it in every later assignment. This is how your own toolbox grows.

ანტი-შაბლონებიAnti-patterns

როგორ არ დავწეროთHow not to write it

try:
    do_everything()
except:
    pass
try:
    value = int(user_input)
except ValueError:
    print(f"'{user_input}' რიცხვი არ არის")
    value = 0
წესიდაიჭირე კონკრეტული შეცდომა, კონკრეტულ ხაზზე, და რეაგირე მასზე — არა ჩაახშო. RuleCatch a specific error, on a specific line, and respond to it — do not silence it.

სრული მაგალითიFull example

მდგრადი კალკულატორიA resilient calculator

def divide(a, b):
    if b == 0:
        raise ZeroDivisionError("გამყოფი ნული ვერ იქნება")
    return a / b


def load_history(path):
    try:
        with open(path, encoding="utf-8") as f:
            return [line.strip() for line in f if line.strip()]
    except FileNotFoundError:
        return []          # first run — perfectly normal


def save(path, record):
    try:
        with open(path, "a", encoding="utf-8") as f:
            f.write(record + "\n")
    except OSError as e:
        print(f"ჩაწერა ვერ მოხერხდა: {e}")


history = load_history("calc.log")
print(f"ისტორიაში {len(history)} ჩანაწერია")

while True:
    raw = input("\nგამოსახულება (a / b) ან 'q': ").strip()
    if raw == "q":
        break

    try:
        left, right = raw.split("/")
        result = divide(float(left), float(right))
    except ValueError:
        print("ფორმატი: რიცხვი / რიცხვი")
    except ZeroDivisionError as e:
        print(e)
    else:
        print(f"= {result:.4f}")
        save("calc.log", f"{raw} = {result:.4f}")
    finally:
        print("—" * 20)

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

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

სახლში1) გადაწერე ყველა წინა დავალება try/except-ით. 2) ფუნქცია safe_divide(a, b). 3) ფაილის მკითხველი, რომელიც არარსებობისას ქმნის მას. 4) ვალიდატორი: ელფოსტა უნდა შეიცავდეს @-ს — თუ არა, raise ValueError. At home1) Rewrite your earlier assignments with try/except. 2) A safe_divide(a, b) function. 3) A file reader that creates the file if it is missing. 4) A validator: an email must contain @ — otherwise raise ValueError.
შემდეგი — ლექცია 13იტერატორები და კონტეინერები + პრაქტიკული დავალება #4. Next — lecture 13Iterators and containers + practical assignment #4.