Loeng 9: Erisused, erisuste haldamine, erisuste hierarhia, konstruktsioon: try/except/else/finally, lausend raise, silumine, süntaksiviga, täitmisaegne viga, loogikaviga, versioonikontroll¶

Viimati uuendatud 26.10.2025.

Koodinäited¶

1  Süntaksivead, Pythoni erisused ja pinu jälg (traceback)¶

Kui sa oled proovinud iseseisvalt selle kursuse ülesandeid lahendada siis oled kindlasti veateadetega tuttav. Lihtsustatult öeldes saame Pythonis eristada kahte tüüpi vigu: süntaksivigu (syntax errors) ja muid erisusi või erindeid (exceptions). Koodi kirjutava võib tekitada ka mitu viga sellisel juhul püüab interpretaator need dokumenteerida ja väljastada detailses aruandes. Erisuse tõstatamisega peatatakse programmi töö.

1.1  Süntaksiviga (syntax error)¶

Sünkatsiviga tekib kui programmeerija eksib koodisüntaksi reeglite vastu. Kasutaja unustab kooloni, kirjutab lausendi valesti, eksib bloki taande vastu, jne., jne. Veateadet loe alt ülesse. Tõstatatud teate viimasel real on tekst mis kirjeldab viga. Sellest ülevalpool kasutab interpretaator caret'it (^) vea asukoha näitamiseks. Lõpuks antakse info vea asukoha kohta: moodul, koodirakk, rida jm.:

In [1]:
while True
    print('Hello world')
  Cell In[1], line 1
    while True
              ^
SyntaxError: expected ':'
In [2]:
print max([1, 2])
  Cell In[2], line 1
    print max([1, 2])
    ^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?

1.2  Erisus (exception)¶

Erisus või erind tõstatakse kui kood on süntaktiliselt õige. Kasutaja pole eksinud süntaksireeglite vastu, kuid sellegipoolest programmi töö üldjuhul peatatakse.

Programmid töötlevad andmeid (andmetüüpid ja nenede objektid) kui sisendandmed ei vasta programmi eeldustele tõstatakse erisus. Kui pole teisiti öeldud siis erisuse tõstmisel programmi interpreteerimine ja töö peatatakse.

Erisus on üksik sündmus, mis juhtub siis, kui Pythoni koodi käivitamisel läheb midagi valesti. Erisustel on nimed:

In [3]:
12 / 0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[3], line 1
----> 1 12 / 0

ZeroDivisionError: division by zero

Nii nagu oli süntaksiveaga püüab teade näidata vea asukohta kasutades pikka noolt (---->) ja/või alumisel real oleva caret'i (^):

In [4]:
def jaga(a, b):
    result = a / b
    return result

jaga(1, 2)  # --> Erisus puudub, kõik on korras.
Out[4]:
0.5
In [5]:
jaga(1, 0)  # --> ZeroDivisionError <-- Erisuse nimi.
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[5], line 1
----> 1 jaga(1, 0)

Cell In[4], line 2, in jaga(a, b)
      1 def jaga(a, b):
----> 2     result = a / b
      3     return result

ZeroDivisionError: division by zero

Erisuste nimed peaksid olema iseenesest mõistetavad:

In [6]:
jaga(1, '0')  # --> Erisus: TypeError.
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[6], line 1
----> 1 jaga(1, '0')

Cell In[4], line 2, in jaga(a, b)
      1 def jaga(a, b):
----> 2     result = a / b
      3     return result

TypeError: unsupported operand type(s) for /: 'int' and 'str'

1.3  Pinu jälg (traceback)¶

Kui programmeerija kirjutab pikemat koodi siis tõuseb võimalike vigade ja erisuste esinemise tõenäosus. Programmi koodis võib esineda mitu viga, kusjuures vead võivad üksteisest sõltuda.

Juba eelmises lõigus nägime, et erisue tõstatamisel genereeritakse nn. Traceback (eesti keeles "pinu jälg" või "jälitus") mis sisaldab muuseas infot erisuse asukoha (moodul, koodirakk, rea numbri) kohta.
Märkus: Kuvatud vea asukoht ei pruugi alati õige olla.
Lisaks tekstile näidatakse erisuse asukohta koodis koodist vasakul pool asetseva pika noole (---->) ja/või alumisel real asuva caret'i (^) abil.

Tracebacki loeme alt ülesse selline lugemine on eriti kasulik kui traceback on pikk ehk sisaldab mitmeid vigu ja erisusi.
Töövoog: Peale altpoolt esimese vea parandamist teade kaob ja ülevalt poolt kukub seda asendama järgmine erisus jne. Viimane rida sisaldab tõstatatud erisuse nime ja kirjeldust.

Traceback on detailne aruanne milles näidatakse kus viga juhtus ja millised tegevused ja/või funktsioonide väljakutsed selleni viisid. Piltlikult öeldes on Traceback rada, mis viis krahhini ja erisus on krahh ise.
NB! Üksteisega seotud väljakutseid kuvatakse ülevalt alla.

Näiteid: Funktsioonid mis kutsuvad teisi funktsioone milledest viimane on vigane.

In [7]:
def a():
    b()

def b():
    c()

def c():
    return 1 / 0  # --> ZeroDivisionError.

a()
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[7], line 10
      7 def c():
      8     return 1 / 0  # --> ZeroDivisionError.
---> 10 a()

Cell In[7], line 2, in a()
      1 def a():
----> 2     b()

Cell In[7], line 5, in b()
      4 def b():
----> 5     c()

Cell In[7], line 8, in c()
      7 def c():
----> 8     return 1 / 0

ZeroDivisionError: division by zero

Natukene keerukam näide;

In [8]:
def esimene_funk():
    a = "Juulius "
    b = "'Traceback' "
    c = "Tipikas"
    teine_funk(a, b, c)  # Funktsiooni teine_funk väljakutse.

def teine_funk(a, b, c):
    d = a + b + c   # Sõnede konkatenatsioon.
    print(d)        # --> Juulius 'Traceback' Tipikas
    kolmas_funk(d)  # Funktsiooni kolmas_funk väljakutse.

def kolmas_funk(a):
    print(int(a))   # --> ValueError.

esimene_funk()
Juulius 'Traceback' Tipikas
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[8], line 15
     12 def kolmas_funk(a):
     13     print(int(a))   # --> ValueError.
---> 15 esimene_funk()

Cell In[8], line 5, in esimene_funk()
      3 b = "'Traceback' "
      4 c = "Tipikas"
----> 5 teine_funk(a, b, c)

Cell In[8], line 10, in teine_funk(a, b, c)
      8 d = a + b + c   # Sõnede konkatenatsioon.
      9 print(d)        # --> Juulius 'Traceback' Tipikas
---> 10 kolmas_funk(d)

Cell In[8], line 13, in kolmas_funk(a)
     12 def kolmas_funk(a):
---> 13     print(int(a))

ValueError: invalid literal for int() with base 10: "Juulius 'Traceback' Tipikas"

NB! Kuna Python on väga-kõrgetasemeline skriptimiskeel siis võivad pealtnäha sarnased vead tõstatada erinevaid erisuse kommentaare. Olukord paraned iga uuema Pythoni interpretaatori versiooniga. Algajale on taoline olukord potensiaalselt segadust tekitav.

2  Valik olulisemaid vigu ja erisusi¶

2.1  Vead¶

2.1.1  Viga SyntaxError¶

Süntaksiviga SyntaxError tõstatakse näiteks kui kasutaja unustab lõpetada sõne või muu andmetüübi defineerimise. Sõne puhul võime unustada jutumärgid. Sarnaselt võime unustada listi lõpetavad sulud, jmt. Eksimine süntaksireeglite vastu.

In [9]:
print("Hello world!)  # NB! Jutumärgid.
  Cell In[9], line 1
    print("Hello world!)  # NB! Jutumärgid.
          ^
SyntaxError: unterminated string literal (detected at line 1)
In [10]:
a = [1, 2, 3  # NB! Sulud puudu.
  Cell In[10], line 1
    a = [1, 2, 3  # NB! Sulud puudu.
                                    ^
SyntaxError: incomplete input

2.1.2  Süntaksivead IndentationError ja TabError¶

Kasutaja eksib koodibloki taande vastu. Pythoni def, if, for ja muudes plokkides peab bloki sisu olema tähistatud taandega. Taane peab terve plokki ja ka ülejäänud mooduli piires olema sama. Lisaks veale IndentationError on olemas ka viga/erisus TabError mis tõstatakse kui taandamisel kasutatakse tühikuid ja tabulatsioone segamini. Pythonis peab taane olema tehtud alati ühetaoliselt terve mooduli (.py faili) ulatuses.

In [11]:
def func(a, b):
return a + b
  Cell In[11], line 2
    return a + b
    ^
IndentationError: expected an indented block after function definition on line 1
In [12]:
def func(a, b, c, d):
    var1 = a + b
     var2 = b + c
    return var1 + var2
  Cell In[12], line 3
    var2 = b + c
    ^
IndentationError: unexpected indent
In [13]:
def hello():
    print("Hello!")  # Neli tühikut.
	print("You!")    # Üks tabulatsioon.

hello()
  Cell In[13], line 3
    print("You!")    # Üks tabulatsioon.
    ^
TabError: inconsistent use of tabs and spaces in indentation

2.2  Erisused¶

2.2.1  Erisus IndexError¶

Kasutaja püüab viidata indekseeritud andmetüübi elemendile kasutades liiga suurt või valet indeksi väärtust.

In [14]:
lst = ["Juulius", "on", "suur", "viga."]

print(lst.__len__())  # Sama mis len(lst).
print(lst[4])
4
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[14], line 4
      1 lst = ["Juulius", "on", "suur", "viga."]
      3 print(lst.__len__())  # Sama mis len(lst).
----> 4 print(lst[4])

IndexError: list index out of range
In [15]:
i = 0
while i < 4:
    i += 1
    print(lst[i])
on
suur
viga.
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[15], line 4
      2 while i < 4:
      3     i += 1
----> 4     print(lst[i])

IndexError: list index out of range

2.2.2  Erisus KeyError¶

Kasutaja viitab sõnaraamatu väärtusele kasutades vigast või puuduvat võtit.

In [16]:
hinded = {
    "eesti keel": 4,
    "kehaline": 3,
    "mata": 2
    }

print(hinded["geograafia"])
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[16], line 7
      1 hinded = {
      2     "eesti keel": 4,
      3     "kehaline": 3,
      4     "mata": 2
      5     }
----> 7 print(hinded["geograafia"])

KeyError: 'geograafia'

Meeldetuletus: Sõnastike puhul on olemas viis kuidas eelnevas olukorras vältida erisuse ilmnemist. Selleks, et vältida KeyErrorit kasuta meetodit get.

In [17]:
hinded = {
    "eesti keel": 4,
    "kehaline": 3,
    "mata": 2
    }

print(hinded.get("geograafia"))
None

2.2.3  Erisus NameError¶

Deklareerimata/defineerimata muutuja või muu objekti nime kasutamine.

In [18]:
variable = 10
print(undeclared_variable + variable)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[18], line 2
      1 variable = 10
----> 2 print(undeclared_variable + variable)

NameError: name 'undeclared_variable' is not defined
In [19]:
def func_x(x):
    return x

func_y(2)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[19], line 4
      1 def func_x(x):
      2     return x
----> 4 func_y(2)

NameError: name 'func_y' is not defined

2.2.4  Erisus TypeError¶

Kasutaja üritab teha tegevusi kahe erineva andmetüübi esindaja vahel, mis kuidagi kokku ei käi. Eksitakse andmetüübi vastu.

In [20]:
var = "10"
print(var + 20)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[20], line 2
      1 var = "10"
----> 2 print(var + 20)

TypeError: can only concatenate str (not "int") to str
In [21]:
myStr = "100"
myResult = myStr >> 2
print("Result is:", myResult)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[21], line 2
      1 myStr = "100"
----> 2 myResult = myStr >> 2
      3 print("Result is:", myResult)

TypeError: unsupported operand type(s) for >>: 'str' and 'int'

Sama erisus tõstatub ka siis kui püüad teha funktsiooni väljakutset objektiga mis pole funktsioon.

In [22]:
myInt = 100
myInt()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[22], line 2
      1 myInt = 100
----> 2 myInt()

TypeError: 'int' object is not callable

2.2.5  Erisus AttributeError¶

Objektile rakendatakse meetodit mis selle puudub või küsitakse objekti muutujat mis sellel puudub.

In [23]:
'Hello'.append('0')
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[23], line 1
----> 1 'Hello'.append('0')

AttributeError: 'str' object has no attribute 'append'
In [24]:
var = 2
var.__call__()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[24], line 2
      1 var = 2
----> 2 var.__call__()

AttributeError: 'int' object has no attribute '__call__'
In [25]:
x = 'string'
x.imag
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[25], line 2
      1 x = 'string'
----> 2 x.imag

AttributeError: 'str' object has no attribute 'imag'

2.2.6  Erisus OverflowError¶

Numbriline operatsioon liiga keeruline, arvu absoluutväärtus liiga suur.

In [26]:
def my_exp(x):
    e = 2.718281828459045  # Euleri arv.
    return e ** x

print(my_exp(709.7827))
print(my_exp(710))
1.7976699566637335e+308
---------------------------------------------------------------------------
OverflowError                             Traceback (most recent call last)
Cell In[26], line 6
      3     return e ** x
      5 print(my_exp(709.7827))
----> 6 print(my_exp(710))

Cell In[26], line 3, in my_exp(x)
      1 def my_exp(x):
      2     e = 2.718281828459045  # Euleri arv.
----> 3     return e ** x

OverflowError: (34, 'Result too large')

Python kasutab ujuvkomakohaga arvude puhul nn. topelttäpsust. Väikseim ja suurim arvu on:

In [54]:
import sys
print(sys.float_info.min)  # Väikseim arv.
print(sys.float_info.max)  # Suurim arv.
2.2250738585072014e-308
1.7976931348623157e+308

Märkus: Nende suuruste väärtused võivad sõltuda arvuti raudvarast.

3  Kuidas suhtuda ja mida teha erisustega?¶

3.1  Strateegiaid¶

Erisustega töötamiseks ja nende haldamiseks on mitmeid strateegiaid:

  • Ei tee midagi, programm jookseb kokku.
  • Programm edastab kasutajale veateate ja lõpetab töö.
  • Programm analüüsib ja muudab/parandab sisendeid, millega välditakse erisust.
  • Programm edastab teate ja annab kasutajale võimaluse vead parandada.
  • Programm üldistatakse nii, et erisust ei tekiks.
  • Erisuste haldamine.

3.1.1  Erisuste haldamine¶

Pythonis saab hallata nii vigu kui ka erisusi kasutades sama lähenemist. See on ka põhjus miks tihtipeale praktikas ei eristata vigu erisustest. Erisuste haldamise eesmärgiks on soovimatu programmi töö peatuse vältimine. Erisusi haldame plokkis try või pesastatud try plokkides kasutades konstruktsiooni try/except/else/finally.

Süntaks:

try:
    <tegevus>
except Exception as <nimi>:       # Üldine/baas erisus.
    <Tegevus kui tõstatub erisus.>
    print(<nimi>)                 # Teavita kasutajat mis erisus tõstatati.
else:
    <Tegevus kui erisusi polnud.>
finally:
    <Tegevus juhuks kui ilmnes või ei ilmnend erisust.>

või

try:
    <tegevus>
except <erisus 1>:
    <Tegevus erisuse 1 jaoks.>
except <erisus 2>:
    <Tegevus erisuse 2 jaoks.>
except Exception as <nimi>:       # Üldine/baas erisus.
    <Tegevus kui tõstatus erisus.>
else:
    <Tegevus juhuks kui erisusi polnud.>
finally:
    <Tegevus juhuks kui ilmnes või ei ilmnend erisusi.>

Näide: Täiendame, üldistame ja analüüsime eelnevalt defineeritud funktsiooni jaga ehk haldame selle võimalike erisusi.

In [27]:
def jaga(a, b):
    try:
        return a / b
    except Exception as e:   # Püüab kinni kõik erisused.
        print('Erisus:', e)  # Väljastan erisuse kirjelduse.

jaga(1, '0')
Erisus: unsupported operand type(s) for /: 'int' and 'str'
In [28]:
jaga(1, 0)
Erisus: division by zero
In [29]:
jaga(2, 2)
Out[29]:
1.0

Funktsiooni jaga üldistus. Haldame kõiki erisusi eraldi:

In [30]:
import math

def jaga(a, b):
    try:
        return a / b
    except TypeError:
        try:
            a, b = float(a), float(b)  # Proovin numbriks teisendada.
            return jaga(a, b)  # Rekursiivne väljakutse.
        except ValueError as e:
            print('Ei teisene numbriks:', e)
            return None
        except TypeError as e2:
            print('Sellised sisendid pole lubatud:', e2)
            return None
    except ZeroDivisionError:
        print('Teade: Läheneb väljastatud suurusele või võimatu.')
        return a * math.inf  # Argumenti a kasutame märgi jaoks.

jaga(-1, 0)
Teade: Läheneb väljastatud suurusele või võimatu.
Out[30]:
-inf
In [31]:
jaga(1, '2')
Out[31]:
0.5
In [32]:
jaga(1, 'a')
Ei teisene numbriks: could not convert string to float: 'a'
In [33]:
jaga(0, 0)
Teade: Läheneb väljastatud suurusele või võimatu.
Out[33]:
nan
In [34]:
jaga(1, [2])
Sellised sisendid pole lubatud: float() argument must be a string or a real number, not 'list'

3.1.2  Erisuse käsitsi tõstmine, lausend raise¶

Lausendi raise abil saame tõstatada erisusi. Programmi peatamine on vahest vajalik ja mõistlik tegevus.

Süntaksi näiteid:

In [35]:
raise FloatingPointError  # Süntaks: raise <erisuse nimi>
---------------------------------------------------------------------------
FloatingPointError                        Traceback (most recent call last)
Cell In[35], line 1
----> 1 raise FloatingPointError

FloatingPointError: 
In [36]:
raise FloatingPointError('Teade siia.')
---------------------------------------------------------------------------
FloatingPointError                        Traceback (most recent call last)
Cell In[36], line 1
----> 1 raise FloatingPointError('Teade siia.')

FloatingPointError: Teade siia.
In [37]:
if True:
    raise FloatingPointError('Teade.')
---------------------------------------------------------------------------
FloatingPointError                        Traceback (most recent call last)
Cell In[37], line 2
      1 if True:
----> 2     raise FloatingPointError('Teade.')

FloatingPointError: Teade.
In [38]:
try:
    prunt('Python')
except Exception:
    raise  # Erisust pole vaja eraldi täpsustada.
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[38], line 2
      1 try:
----> 2     prunt('Python')
      3 except Exception:
      4     raise

NameError: name 'prunt' is not defined

Näide: Tõstatame teadaoleva erisuse ZeroDivisionError funktsioonis jaga käsitsi.

In [39]:
import math

def jaga(a, b):
    try:
        if b == 0:
            raise ZeroDivisionError  # Tõstan käsitsi.
        else:
            return a / b
    except TypeError:
        try:
            a, b = float(a), float(b)
            return jaga(a, b)
        except ValueError as e:
            print('Ei teisene numbriks:', e)
            return None
        except TypeError as e2:
            print('Sellised sisendid pole lubatud:', e2)
            return None
    except ZeroDivisionError:  # Realt 6 liigume otse siia. 
        print('Realt 6 otse siia...')  # Defineerime siin käitumise mida soovime.
        return a * math.inf

jaga(-1, 0)
Realt 6 otse siia...
Out[39]:
-inf

3.1.2  Erisuse käsitis tõstmine ja lausend assert¶

Mäletatavasti saab lausend assert tõstatada erisuse AssertionError vt. eelmine loeng. Kasutame lausendit assert ja asendame funktsioonis jaga erisuse ZeroDivisionError erisusega AssertionError.

In [40]:
import math

def jaga(a, b):
    try:
        assert b != 0  # Tõstatab AssertionError'i, kui tingimuse bool on False.
        return a / b   # Kui b == 0 siis siia ritta ei jõutagi.
    except AssertionError:
        print('AssertionError Teade: Läheneb sellele suurusele või võimatu.')
        return a * math.inf
    except TypeError:
        try:
            a, b = float(a), float(b)
            return jaga(a, b)
        except ValueError as e:
            print('Ei teisene numbriks:', e)
            return None
        except TypeError as e2:
            print('Sellised sisendid pole lubatud:', e2)
            return None

jaga(-1, 0)
AssertionError Teade: Läheneb sellele suurusele või võimatu.
Out[40]:
-inf
In [41]:
jaga(1, '2')
Out[41]:
0.5
In [42]:
jaga(1, 'a')
Ei teisene numbriks: could not convert string to float: 'a'
In [43]:
jaga(0, 0)
AssertionError Teade: Läheneb sellele suurusele või võimatu.
Out[43]:
nan

3.1.3  Erisusete haldamise kasutamisest¶

Üldised soovitused erisustega töötamisel:

  • Oht $-$ erisused peidavad programmi vigu.
  • Kui võimalik ära kasuta, eelista programmi üldistamist.
  • try-plokk peab olema minimaalne.
  • Näita olemasolevat erisuse infot (inimloetav sisu kirjeldus) või kirjuta see ise.

Näide: Inimloetavate teadete loomine.

In [44]:
try:
    0 / 0
except Exception as info:
    print('Minu inimloetav teade siia.')
    print(info, type(info))  # Midagi taolist.
Minu inimloetav teade siia.
division by zero <class 'ZeroDivisionError'>
  • Väljasta erisus käsitsi kasutades lausendit raise, programmi peatamine ei pruugi olla halb mõte.

Näide: Inimloetava teate genereermine kasutades lausendit raise blokis except.

In [45]:
try:
    0 / 0
except Exception as info:
    print('Tekkis erisus:', type(info).__name__,':', info) # Kasuta erisuse nime.
    raise  # Siin pole vaja erisust täpsustada.
Tekkis erisus: ZeroDivisionError : division by zero
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[45], line 2
      1 try:
----> 2     0 / 0
      3 except Exception as info:
      4     print('Tekkis erisus:', type(info).__name__,':', info) # Kasuta erisuse nime.

ZeroDivisionError: division by zero

3.1.4  Sisseehitatud erisuste hierarhia¶

Sisseehitatud erisused on defineeritud klassides, nende hierarhia on näidatud allpool. Lisainfo: https://docs.python.org/3/library/exceptions.html#exception-hierarchy

In [46]:
#help(Exception)

Erisuste hierarhia:

BaseException
 ├── BaseExceptionGroup
 ├── GeneratorExit
 ├── KeyboardInterrupt
 ├── SystemExit
 └── Exception                 <--- Üldine erisus Exception elab siin...
      ├── ArithmeticError
      │    ├── FloatingPointError
      │    ├── OverflowError
      │    └── ZeroDivisionError
      ├── AssertionError
      ├── AttributeError
      ├── BufferError
      ├── EOFError
      ├── ExceptionGroup [BaseExceptionGroup]
      ├── ImportError
      │    └── ModuleNotFoundError
      ├── LookupError
      │    ├── IndexError
      │    └── KeyError
      ├── MemoryError
      ├── NameError
      │    └── UnboundLocalError
      ├── OSError
      │    ├── BlockingIOError
      │    ├── ChildProcessError
      │    ├── ConnectionError
      │    │    ├── BrokenPipeError
      │    │    ├── ConnectionAbortedError
      │    │    ├── ConnectionRefusedError
      │    │    └── ConnectionResetError
      │    ├── FileExistsError
      │    ├── FileNotFoundError
      │    ├── InterruptedError
      │    ├── IsADirectoryError
      │    ├── NotADirectoryError
      │    ├── PermissionError
      │    ├── ProcessLookupError
      │    └── TimeoutError
      ├── ReferenceError
      ├── RuntimeError
      │    ├── NotImplementedError
      │    └── RecursionError
      ├── StopAsyncIteration
      ├── StopIteration
      ├── SyntaxError
      │    └── IndentationError
      │         └── TabError
      ├── SystemError
      ├── TypeError
      ├── ValueError
      │    └── UnicodeError
      │         ├── UnicodeDecodeError
      │         ├── UnicodeEncodeError
      │         └── UnicodeTranslateError
      └── Warning
           ├── BytesWarning
           ├── DeprecationWarning
           ├── EncodingWarning
           ├── FutureWarning
           ├── ImportWarning
           ├── PendingDeprecationWarning
           ├── ResourceWarning
           ├── RuntimeWarning
           ├── SyntaxWarning
           ├── UnicodeWarning
           └── UserWarning

4  Silumine (debugging)¶

4.1  Mõiste¶

Silumine on tarkvaraarenduse protsess, mille käigus leitakse ja parandatakse programmikoodivead, mis takistavad programmi korrektset toimimist. Silumist tuleb eristada silurist, mis on tarkvaraprogramm või -moodul, mida kasutatakse silumiseks. Allpool arutame silumist (täpsemalt käsitsi silumist ehk avastuslik silumist) tarkvara dünaamilise prototüüpimise kontekstis.

Prototüüpimine $-$ (meie kursuses siiamaani Pythoni lähtekoodi kirjutamist saab kategoriseerida nii) väiksemate programmide loomine, üksikute programmi osade loomine, loodud programmiosad erinevad lõpp-produktist, jne. Kood on pidevas muutumises $-$ kirjutad koodi, interpreteerid koodi, uurid tulemust ja kordad eelnevaid tegevusi mitmeid kordi.

Millest, silumise kontekstis, siin kursusel ei räägita:

  • Ühiktestimine (unit testing, automated testing). Uuri teemat iseseisvalt.
  • Kõikvõimalikud integratsiooni- ja süsteemitestid.
  • Koodi refaktoriseerimine (code refactoring) $-$ koodi mittefunktsionaalse osa silumine.
  • Süntaksi süvaanalüüs (lintering: PyLint/pyflakes) sisseehitatud Pycharm ja Spyder IDE'sse.
  • Fuzzing $-$ testimine kasutades juhuslikult genereeritud sisendandmeid.
  • Delta silumine (delta debugging).
  • Post mortem silumine (post mortem debugging mode: pdb toetab seda).

4.1.1  Ühiktestimine (unit testing)¶

Uuri teemat iseseisvalt. See pole keeruline kui oskad kasutada lausendit assert, vt. eespool ja Loeng 8.

Ühiktestimise puhul testitakse ühte konkreetset üksust (alamprotseduuri) koodist. Üksus võib olla nii funktsioon kui ka terve funktsioonidest (meetoditest) koosnev objekt. Klassis defineeritud objektide puhul testitakse seal olevaid meetodeid ja vaadatakse, kas need rakenduvad objektile õigesti. Funktsioonide puhul kontrollitakse kas need annavad õige väljundi erinevate argumendi väärtuste korral.

Selleks, et programmeerija ei peaks kogu aeg funktsioone välja kutsuma ja tulemust vaatama (näiteks kasutades funktsiooni print), kasutatakse spetsiaalseid testimise tööriistu: moodulid unittest (Rohkem võimalusi. Anaconda installatsiooniga kaasas.) ja pytest (lihtsam).

Arenduskeskkonnas Spyder kasuta plugin't spider-unittest, mis pole arenduskeskkonda vaikimisi lisatud. Installeeri plugin Anaconda konsoolis kasutades käsku:

conda install spider-unittest

4.2  Vigade ja erisuste tüübid¶

Üldiselt rääkides eristatakse programmeerimises kolme tüüpi vigu:

  • Süntaksiviga (compile time error) $-$ Pythoni interpreteerija ei saa koodist aru ja seetõttu ei hakkata programmi üldse käivitama, eksitakse keele kirjutamise reeglite ehk süntaksi vastu.
  • Täitmisaegne viga (runtime error) $-$ programm käivitati, aga mingi konkreetse käsu täitmine ebaõnnestub. Vigaseks käsuks võis olla näiteks nulliga jagamine, valesti kirjutatud funktsiooninime kasutamine, olematu faili lugemine, vale andmetüübiga sisendandmed, vms.
  • Semantikaviga (semantic error) või lihtsalt loogikaviga $-$ kõik on Pythoni interpretaatori seisukohast korrektne (st. mingit veateadet või erisust ei tõstatata), aga programm ei tee seda, mida programmeerija silmas peab. Näiteks oled unustanud segatehete tehete prioriteetsused (Loeng 3) või LEGB reegli (Loeng 6).

Eelloetletud vead on järjestatud silumise raskuse järjekorras.

4.3  Lihtsamad tööriistad ja abimaterjalid¶

Üldised abistavad tööriistad mida ei tasu alahinnata:

  • Pythoni erisuse traceback.
  • IDE'sse sisseehitatud toed (Python Docs, DocString'id, jne.). Dokumentatsiooni ja muu abiinfo järgivaatamine: Ctrl + I.
  • IPythoni konsooli kasutamine:

Meeldetuletus: IPythoni konsoolis dokumentatsiooni ja abimaterjalide järelevaatamise viisid.

In [47]:
#print?      # iPythoni konsoolis.
In [48]:
#help('if')  # Definitsioonide kuvamine.
In [49]:
#help(str)   # Meetodid, funktsioonid ja nenede kirjeldused.
In [50]:
#dir(str)    # Meetodite ja atribuutide loetelu.
In [51]:
#locals()    # Lokaalne nimeruum.
In [52]:
#globals()   # Globaalne nimeruum.
  • Internet, Googeldamine ja juturobotid. Näiteks: kolmandate osapoolte teekige kodulehed https://matplotlib.org/ jne.
  • PythonDocs server veebilehitsejas. Sisesta konsooli python -m pydoc -b.
  • Loe imporditud kolmandate osapoolte lähtekoodi ennast, võib sisaldada vihjeid probleemide lahendustele. Spyder'is: Kursor objekti kohale ja Ctrl + G või Ctrl + hiire klikk (lugemine võimalik ainult siis kui lähtekood pole ettekompileeritud ja seega binaarsel kujul).

4.4  Silurid¶

  • pdb $-$ Python debugger (Pythoni standardteegis alati olemas: ei vaja graafilise kasutajaliidesega IDE'd), ei käsitle selles kursuses.
    • Käsurida jupyter: debug, %debug, pdb, või %pdb, sama käsk suleb siluja.
    • Moodulis: import pdb; pdb.set_trace().
  • IPdb Interactive Python debugger (Spyder'sse sisseehitatud).
  • Spyder (graafiline) töötab koos IPdbga.
  • PyCharm'i, Visual Studio ja muude IDE'de graafilised silurid.

4.4.1  Silumine Spyder'i IPythoni konsoolis¶

Silur käivitub käsuga ipdb.
Valik käske IDE Spyder konsoolis kasutamiseks:

  • n(ext), teosta käesolev rida ja liigu järgmisesse ritta.
  • s(tep), sisene funktsiooni.
  • l(ist), näita kolm rida enne ja peale käesolevat rida.
  • b(reakpoint) <rida>
  • b(reakpoint) <rida>, <tingimus>
  • b(reakpoint) <funktsiooni nimi>
  • c(obtinue), kuni järgmise breakpoint'ni või lõpuni.
  • q(uit), välju silurist.
  • r(un), kuni funktsiooni return lausendini.

5  Kuidas siluda ja leida vigu¶

5.1  Koodi kirjutamisest¶

Tea kodeerimise põhitõdesid ja süntaksireegleid ning lihtsalt kirjuta veavabalt. Veavabalt aitab kirjutada enda probleemi süviti mõistmine ja oskus jagada enda probleemi väiksemateks alamprobleemideka. Väiksema alamprobleemiga töötamine aitab ülevaadet hoida. Kui veavabalt kirjutamine ei õnnestu või töötad kellegi teise poolt kirjutatud vigase koodiga, siis silu.

Abiks vigade ennetamisele:

Soovitus: Dokumenteeri koodi ehk kirjuta korralikke DocString'e kus kirjeldad mooduli, programmi ja/või alamprotseduuride tööd. Kommeteeri enda kirjutataud koodi (Kasulik ka tulevikus kui oled unustanud mida sa programmis täpselt tegid, vt. Joonis 1).

No description has been provided for this image
Joonis 1. Meem enda koodi kommenteerimisest.

5.2  Soovitusi silumiseks¶

Järgnevaid soovitusi rakenda esitatud järjekorras.

5.2.1  Süntaksi- ja täitmisaegsed vead¶

Soovitus 0: Interpreteeri programmi lähtekood $-$ jooksuta programm läbi.

Soovitus 1: Loe erisuse teadet (traceback), tegutse vastavalt (tea kuidas).

Soovitus 2: Loe lähtekoodi õigelt reanumbrilt, avasta sealt viga või erisus.

Soovitus 3: Teosta andmetüübi kontrolle, kasuta nt. lausendit assert jne.

Korrates Soovitusi 0 kuni 3 avastad ja parandad enamuse süntaksi ja täitmisaegsetest vigadest.

5.2.2  Funktsioon print¶

Soovitus 4: Funktsioon print $-$ pole hea tööriist aga kuidagi suudab 90% juhtudest sul vead avastada ja ka ära parandada.

  • Kasuta muutujate ja objektide väärtuste järgivaatamiseks. (Kasuta ka muutujate lehitseja akent.)
  • Kontrolli kas teatud real juhtub see mis seal peab juhtuma.
  • Kas teatud real olev funktsiooni print väljakutse teostub enne erisuse tõstatumist.

   Kasuta ettevaatlikult:

  • Liiga palju print lauseid risustavad konsooli väljundit ja segavad selle lugemist.
  • Liiga palju print lauseid segavad lähtekoodi enda lugemist.

5.2.3  Semantika- ehk loogikavead¶

Järgisena tulevad semantika- ehk loogikavead. Nüüd alles algab tõsine silumine:

No description has been provided for this image
Joonis 2. Teadusliku meetodi diagramm.

Soovitus 5: Rakenda "teaduslikku meetodit", mille diagramm on kujutatud Joonisel 2, me tahame ju midagi teada saada $-$ vea olemust.

  • Hüpotees ja selle ümber lükkamine.
  • Testi hüpoteese teadlikult, muutes koodi ja ennustades tulemust.
  • Dokumenteeri juba tehtut (või vähemalt ära unusta juba tehtut), ole sihikindel $-$ "teaduslik".

Soovitus 6: Lihtsusta probleemi püstitust, minimeeri kood, minimeeri sisendid, rakenda Soovitust 5 uuesti (käsitsi ühiktestimine).

  • Minimal Reproducible Example (MRE): https://en.wikipedia.org/wiki/Minimal_reproducible_example
No description has been provided for this image   No description has been provided for this image   No description has been provided for this image
Joonis 3. Vasak: Kummist part ehk ujuv skulptuur "Spreading Joy Around the World", autor Florentijn Hofman.
Keskmine, Parem: Programmeerija seletab enda koodi kummist pardile.

Soovitus 7: Programmeerijate seas levinud meem: seleta probleem kummist pardile (rubber duck debugging), vt. Joonis 3: Lisainfo siit: https://en.wikipedia.org/wiki/Rubber_duck_debugging
Mitte segi ajada mõistetega duck typing (https://en.wikipedia.org/wiki/Duck_typing) või duck test (https://en.wikipedia.org/wiki/Duck_test).

  • Seleta probleem kummist pardile, lapsele või koerale.
  • Lihtsustatult (ümber)seletatud probleem kipub ennast ise lahendama.
  • Seleta koodi reakaupa. Kirjelda lahti iga rea sisu, -sisendid ja oodatav väljund.
  • Vali hea töögrupp ja kolleegid kellega koos töötada... Ideede vahetamine, suhtlemine, juhendamine ja üksteise toetamine.

Soovitus 8: Korrasta lähtekood, eemalda surnud kood, korrasta väljundi esitamise viis, minimeeri väljundid, jne.

  • Aitab ülevaadet hoida.

Soovitus 9: Kasuta plokke assert ja try/except konstruktsioone (käsitsi ühiktestimine, vrd. Soovitus 5).

5.2.4  Silurid¶

Alles nüüd kasutame silurit.

Soovitus 10: Silurid (vt. eelolevat teksti.)

  • Python debugger, pdb või IPdb silurid.
  • Graafilise kasutajaliidesega silurid (Spyder, PyCharm, Visual Studio, jt.)

Soovitus 11: Logide loomine ja nende analüüs.

  • Moodul logging, (meie kursusel seda ei käsitleta).

Soovitus 12: Puhka ja alusta uuesti.

5.2.5  Muu¶

Vähetõenäoline:

Soovitus 13: Kui kõik eelnev ei aita: Probleem võib olla IDE's, operatsiooni süsteemis, arvuti raudvaras, distributsiooni installatsioonis, jms.

  • Taaskäivita Python'i kernel.
  • Taaskäivita masin.

6  Versioonikontroll ja koostöö teistega¶

Versioonikontrolli tarkvara võimaldab arendajatel salvestada koodis tehtud muudatused. Versioonikontrollisüsteemid võimaldavad mitmel arendajal töötada ühe projekti kallal samaaegselt. Salvestada tehtud muudatusi viisil mis võimaldab arendajatel projekti arengut kontrollida ja jälgida. Versioonikontrollisüsteeme (VCS) on kolme tüüpi: kohalik, tsentraliseeritud ja hajutatud.

No description has been provided for this image
Joonis 4. Git'i logo.

Git (https://git-scm.com) on üks hajutatud versioonikontrollisüsteemi tarkvaradest, mis võimaldab arendajatel jälgida oma arvutifailide muudatusi ja töötada koostöös teiste arendajatega. Selle lõi operatsioonisüsteemi Linux looja Linus Torvalds 2005. aastal, et võimaldada teistel arendajatel anda oma panus Linuxi operatsioonisüsteemi tuuma arendamisesse. Joonis 4 kujutab Git'i logo.

No description has been provided for this image   No description has been provided for this image
Joonis 5. GitHub'i logod.

GitHub (https://github.com) on veebirepositoorium, mis pakub kõiki Git'is leiduvaid hajutatud versioonikontrolli ja lähtekoodihalduse (SCM) funktsioone. Seda kasutatakse tavaliselt koostöös Git'ga muuhulgas võimaldab tarkvara arendajatel oma koodi veebis teistega jagada. Joonis 5 kujutab GitHub'ga seotud logosid.

Info kasutamise kohta: https://realpython.com/python-git-github-intro/

















☻   ☻   ☻