ლექცია 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.
კითხვის უნარი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
- წაიკითხე ბოლოდანუკანასკნელი ხაზი ამბობს რა მოხდა:
ZeroDivisionError: division by zero - ზემოთ — სად
line 8, in divide— ზუსტი ფაილი და ხაზი - ზევით-ქვევით — გამოძახებათა ჯაჭვივინ ვის დაუძახა. „most recent call last“ — ბოლო გამოძახება ბოლოშია
- Read it bottom-upThe last line says what happened:
ZeroDivisionError: division by zero - Above it — where
line 8, in divide— the exact file and line - The whole stack — who called whom“most recent call last” — the newest call is at the bottom
ლექსიკაVocabulary
ხშირი გამონაკლისებიCommon exceptions
| გამონაკლისიException | როდის ჩნდებაWhen it appears | მაგალითიExample |
|---|---|---|
ZeroDivisionError | ნულზე გაყოფაDivision by zero | 10 / 0 |
ValueError | ტიპი სწორია, მნიშვნელობა — არაRight type, wrong value | int("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 dictionary | d["x"] |
NameError | ცვლადი არ არსებობსThe variable does not exist | print(undefined) |
AttributeError | ობიექტს ასეთი მეთოდი არ აქვსThe object has no such method | "abc".push() |
FileNotFoundError | ფაილი ვერ მოიძებნაThe file was not found | open("no.txt") |
ImportError | მოდული ვერ ჩაიტვირთაThe module could not be loaded | import 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
კონსტრუქცია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
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)
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 rare — try/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}")
ანტი-შაბლონებიAnti-patterns
როგორ არ დავწეროთHow not to write it
try:
do_everything()
except:
pass
- „შიშველი“
except:იჭერს ყველაფერს — Ctrl+C-საც კი. პროგრამის გაჩერება შეუძლებელი ხდება passშეცდომის ნაცვლადპრობლემა ჩუმად იმალება. მოგვიანებით სრულიად სხვა ადგილას „ამოტივტივდება“- ძალიან დიდი
tryბლოკივერ იგებ, რომელმა ხაზმა გამოიწვია შეცდომა
- A bare
except:Catches everything — including Ctrl+C. The program becomes impossible to stop passinstead of handlingThe problem is silently hidden and resurfaces somewhere else entirely- An oversized
tryblockYou cannot tell which line actually failed
try:
value = int(user_input)
except ValueError:
print(f"'{user_input}' რიცხვი არ არის")
value = 0
სრული მაგალითი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
- სამი ჯგუფი: სინტაქსური · გამონაკლისი · ლოგიკური
- Traceback იკითხება ბოლოდან: ჯერ რა, მერე სად
try / except / else / finally—elseუშეცდომოდ,finallyყოველთვის- დაიჭირე კონკრეტული შეცდომა;
except Exception— ბოლოში; „შიშველი“except:— არასდროს raise— არასწორი მონაცემის მაშინვე უარყოფაwith ... asრესურსს ავტომატურად ხურავს- Python-ში EAFP სტილი უპირატესია
- Three families: syntax · exceptions · logic errors
- Read a traceback bottom-up: first what, then where
try / except / else / finally—elseon success,finallyalways- Catch a specific error;
except Exceptiongoes last; a bareexcept:— never raise— reject bad data at oncewith ... asreleases resources automatically- EAFP is the preferred style in Python
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.