pre up next title end end

3. Postupy a techniky

  1. Interní index elementu
  2. Iterábly a iterátory
  3. Pomocné techniky
    1. Hromadné přiřazení
    2. Rozbalení sekvence
    3. Rozbalení slovníku
  4. Newtonova metoda
  5. Komprehence
  6. Generátory
    1. Generátorový výraz
    2. Generátorová funkce
    3. Líný výpočet
  7. Jmenné prostory
  8. Prostředí   " main "
  9. Introspekce Pythonu
  10. Testování s doctestem
  11. Glosář
  12. Cvičení

3.1 Interní index elementu

Elementy sekvencí a kolekcí jsou interně indexovány a pomocí těchto indexů jsou přímo dostupné závorkovým operátorem [i] nebo prostřednictvím metody __getitem__(i) - odst. 3.2.
Názornou ilustraci indexování těchto elementů představuje obrázek:

indexing

Indexování zleva začíná nulou, indexování zprava doleva se provádní pomocí záporných čísel.:

>>> pozdrav = "Hello World"
>>> pozdrav[5]
' '
>>> pozdrav[-5]
'W'
>>> pozdrav[11]
IndexError: string index out of range
>>> pozdrav.__getitem__(0)
'H'

Hodnoty elementů objektu typu dictionary (slovník) jsou přístupné přes klíče slovníku:

>>> dc = {"m": 5, "n": 6}   # objekt s klíči "m", "n"
>>> dc["m"]                 # použití závorkového operátoru
5
>>> dc.__getitem__("n")     # použití metody
6
>>> for i in dc:            # použití indexu ve smyčce
        print(dc[i], end =" ")
5 6		

3.2 Iterábly a iterátory

Pro zvídavé

Iterace
je opakovaný výpočet pro hodnoty jednotlivých členů iterovatelného objektu, neboli iteráblu.
Iterovatelným objektem (iteráblem) jsou sekvence typu string, list, tuple, range, bytes, bytearray, memoryview a kolekce typu set, frozenset, dict.
Iterace iteráblu produkuje 'dychtivý' (eager) výstup, iterace iterátoru produkuje 'líný' (lazy) výstup, viz odst. 3.6.3.

Iterábl
je iterovatelný objekt, který vlastní magickou (dunder) metodu iterable.__iter__() a iterable.__getitem__(i). Přítomnost těchto metod v objektu lze zjistit funkcí dir(iterable).

Iterátor
je interní objekt, vytvořený pro iterábl explicitně funkcí iter(iterable) nebo metodou iterable.__iter__(), který provádí vlastní iteraci. Tento objekt je rovněž iteráblem!
Implicitně si iterátor ze zadaného iteráblu vytváří smyčka for... a funkce open(), min(), max(), map(), filter(), zip(), enumerate() a reversed().

Iterátory jsou výhodné tím, že snižují spotřebu paměti; vyprázdněný iterátor je "garbage collected".

Iterátor lze generovat i generátorovou funkcí a generátorovým výrazem - viz odst. 3.6.
Iterátory, definované třídou, jsou popsány v kap. 13.5.

Provádění iterace

Funkce next(iterator) postupně vrací aktuálně dosažené prvky iterátoru a vypouští je z kopie. Vypuštění posledního elementu kopie je indikováno sdělením StopIteration:

>>> li = [2, 5, 0]            # iterable

>>> ili = iter(li)            # vytvoření iterátoru
>>> ili = li.__iter__()       # alternativní způsob

>>> next(ili)                 # první krok iterace
2
>>> ili.__next__()            # alternativně další krok iterace
5
>>> next(ili)                 # poslední krok iterace
0
>>> next(ili)       
StopIteration                 # info o konci iterace

Iterátor je v této chvíli prázdný. Opětovně použitelný iterátor vytvoříme opětovným použitím funkce iter(), případně metody  __iter__().
To platí i při aplikaci smyčky for .. :

>>> ili = iter(li)
>>> next(ili) 
2
>>> for i in ili:
        print(i, end =" ")
5 0
>>> next(ili)
StopIteration

Nutno poznamenat, že při aplikaci smyčky for ..
pro iterábl li žádné "StopIteration" nehrozí,
neboť si smyčka vytvoří svůj vlastní interní iterátor:

>>> for i in li:
    print(i, end =" ")
250	

3.3 Pomocné techniky

3.3.1   Hromadné přiřazení

Hromadné přiřazení (multiple assignment, tuple- or iterable unpacking) umožňuje přiřadit jediným příkazem výčet hodnot na pravé straně k výčtu jmen na straně levé. Výčet jmen na levé straně má formát entice (tuple) nebo seznamu (list). Tento příkaz lze zadat dvojím způsobem:

# V jednom řádku:
>>> x,y,z = 10,20,30; x,z              
(10, 30)
# Ve dvou řádcích:
>>> seq = 1,2,3
>>> a,b,c = seq; a,c
(1, 3)
# Entici (tuple) bez závorek akceptuje interpret jako 
entici se závorkami.

Při stejném počtu prvků na obou stranách proběhne jejich propojení v zadaném pořadí.

Výčet hodnot na pravé straně může mít formát entice (tuple), seznamu (list), řetězce (string), setu (set), slovníku (dictionary) a rozsahu (range).

>>> a,b = [12, 22]; a,b           # tuple = list 
(12, 22)             
>>> c,d,e = "Haf"; c,d,e          # tuple = string
('H', 'a', 'f')
>>> f,g = {5, "pivo", 5}; f,g     # tuple = set
('pivo', 5)
>>> f,g = {"j":15, "k":25}; f,g   # tuple = dict
('j', 'k')                             (vrací jen klíče)
>>> b,f,l = range(3); b,f,l       # tuple = range
(0, 1, 2)

Hromadným přiřazením rozbalíme slovník ve smyčce for ...

>>> dic = {'a':2, 'b':4, 'c':10}
>>> for pol in dic.items():       # vestavěná methoda         
        key, value = pol          # hromadné přiřazení
        print(f"Klíč {key} má hodnotu {value}")
Klíč a má hodnotu 2
Klíč b má hodnotu 4
Klíč c má hodnotu 10

Hromadné přiřazení lze výhodně použít i ve spojení s funkcí, pokud její invokace produkuje stejný počet hodnot jako zadaný počet proměnných:

>>> def mocniny(num):
        return num, num**2, num**3
>>> num, sqr, cub = mocniny(2)         # hromadné přiřazení
>>> num, sqr, cub                      # invokace
(2, 4, 8)		

Hromadné přiřazení použijeme i ve spojení s komprehencí iteráblu (viz 3.5):

>>> x,y,z = (x**2 for x in (1,3,5))    # hromadné přiřazení
>>> x,y,z                              # invokace
(1, 9, 25)

Hromadným přiřazením lze rozbalit i vnořené iterábly:

>>> body = (1,2), (-1,-2)              # entice entic
>>> (x1,y1), (x2,y2) = body            # hromadné přiřazení
>>> print(f"x1,y1 ={x1,y1}, x2,y2 ={x2,y2}")
x1,y1 =(1, 2), x2,y2 =(-1, -2)

Hromadné přířazení použijeme výhodně při záměně přiřazení:

>>> x,y,z = 10,20,30
>>> z,y,x = x,y,z
>>> x,y,z
(30, 20, 10)            

Poznámka:
Alternativní označení hromadného přiřazení je rozbalení iteráblu (iterable unpacking).


3.3.2  Rozbalení sekvence dekorátorem *

Pokud je počet členů na levé straně   <=   1+ počet členů   na pravé straně, lze při hromadném přiřazení použít hvězdičkami dekorovaná jména (variadická jména), např:

>>> *a, = 1,2,3,4,5; a          # *a, je entice (tuple)
[1,2,3,4,5]                     # tuple balí do listu
>>> [*a] = 1,2,3,4,5; a
[1,2,3,4,5]                     # stejně jako list

Ve výčtu jmen na levé straně smí být jen jedna hvězdičková proměnná. Neohvězdičkovaná jména se nazývají povinné (mandatory) identifikátory a mají při přiřazování hodnot přednost před hvězdičkou dekorovanými identifikátory:

>>> a,*b,c = 1,2,3,4,5; a,b,c
(1,[2,3,4],5)
>>> a,*_,c = 1,2,3,4,5; a,_,c   # _ je 'anonymní' proměnná
(1,[2,3,4],5)
>>> a,*b,c = 1,2; a,b,c         # OK: 3 <= 1 + 2
(1,[],2)                        

Možné jsou i tyto (poněkud rozpustilé) kombinace:

>>> f,*m,(*ir,il) = 0,1,2,3,(4,5,6)       # přiřazení
>>> f; m; ir; il                          # invokace
0
[1, 2, 3]
[4, 5]
6

Hvězdičkové operátory jsou mnohdy nezbytné pro rozbalení některých iteráblů:

>>> *ran, = range(8); ran    # tuple či list na levé straně!
[0, 1, 2, 3, 4, 5, 6, 7]     
>>> r = list(ran); r         # alternativní invokace
[0, 1, 2, 3, 4, 5, 6, 7]
>>> *gen, = [2**x for x in range(8)]; gen     # komprehence
[1, 2, 4, 8, 16, 32, 64, 128]

Hromadné přiřazení s hvězdičkovým členem usnadňuje přístup k prvkům iteráblu bez použití indexů:

>>> items = "zdař", 25, True, 2+5
Výběr pomocí indexů:
>>> print(f"První položka je {items[0]}, poslední je {items[-1]}")
První položka je zdař, poslední je 7
Výběr hromadným přiřazením:
>>> first, *middle, last = items
>>> print(f"První položka je {first}, poslední je {last}")
První položka je zdař, poslední je 7

3.3.3  Rozbalení slovníku dekorátorem **

Dekorace **, uvedená před názvem slovníku, přikazuje jeho rozbalení před dalším použitím:

>>> dic = {'a':2, 'b':4, 'c':10}
>>> def fun(a,b,c):
        return a,b,c
>>> fun(**dic)
(2, 4, 10)

Jiný příklad se sloučením slovníků:

>>> prvý = {"A": 1, "B": 2}
>>> druhý = {"C": 3, "D": 4}
>>> spolu = {**prvý,**druhý,**{"e": 5, "f": 6}}  # deklarace
>>> spolu                                        # invokace
{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'e': 5, 'f': 6}

Jen tak mimochodem - stejně úsporně lze totéž provést operátorem | (Python 3.9):

>>> prvý = {"A": 1, "B": 2}
>>> druhý = {"C": 3, "D": 4}
>>> spolu = prvý | druhý | {"e": 5, "f": 6}
>>> spolu
{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'e': 5, 'f': 6}

3.4 Newtonova metoda

Smyčky jsou často užívány v programech, které spočítají výsledek tak, že začnou s přibližnou hodnotou a opakovaným výpočtem výsledek zpřesňují.

Například, jedním ze způsobů počítání druhé odmocniny čísla je Newtonova metoda. Při určení odmocniny čísla n začneme vpodstatě libovolnou aproximací (přibližnou hodnotou), kterou zpřesníme pomocí tohoto vzorce:

beclr-tter = (approx + n/approx)/2

Výpočet se opakuje tak dlouho, až se zpřesněná hodnota téměř neliší od předchozí. Pro provádění výpočtu napíšeme funkci:

def sqrt(n):
    approx = n/2.0
    better = (approx + n/approx)/2.0
    while better != approx:
        approx = better
        better = (approx + n/approx)/2.0  
    return approx

Volejme funkci pro argument 25, abychom se přesvědčili, že výsledek je 5.0.

Newtonova metoda je příkladem algoritmu - obecného řešení určitého problému (v našem případě počítání druhé odmocniny).


3.5 Komprehence

Komprehence je skladebný předpis pro vytvoření modifikovaného objektu z poskytnutého iteráblu typu list, tuple, set, string, bytes, dict, range() s použitím kompaktního předpisu, případně ještě při splnění jisté (avšak nepovinné) podmínky. Výsledným modifikovaným objektem může být:

  1. objekt jiného typu (list, tuple a set)
  2. výraz, pracující s prvky zadaného iteráblu.

Komprehenci ad b) lze ilustrovat matematickým výrazem, který pro všechna x z oboru přirozených čísel N, jejichž druhé mocniny jsou větší než 3, provede výraz 2*x:

  S = { 2*x | x € N, x² > 3 }

Obdobou výše uvedeného matematického výrazu může být toto schéma skladby v Pythonu:

  S = [f(x) for x in <objekt> if <podmínka>]

Vedlejším produktem idiomu f(x) for x in <objekt> je skutečnost, že vytvořený objekt (ad b) je zároveň generátorovým objektem (viz odst. 3.6.1), umožňujícím iteraci.

Ukažme si konkretní příklad (prozatím bez podmínky) komprehence iteráblů (list, tuple a set), produkujících generátorové objekty cli, ctu, cst:

>>> li, tu, st = [1, 2, 3, 4], (1, 2, 3, 4), {1, 2, 3, 4}

>>> cli=[x**2 for x in li]; cli   
[1, 4, 9, 16]                         #1 list z li
>>> cli={x**2 for x in li}; cli
{16, 1, 4, 9}                         #1 set z li
cli=(x**2 for x in li); cli
#<generator object <genexpr> at 0x0000020A9AED5630>                         
>>> cli=tuple(x**2 for x in li); cli
(1, 4, 9,16)                          #1 tuple z li
                          
>>> ctu=[x**2 for x in tu]; ctu      
[1, 4, 9, 16]                         #2 list z tu
>>> ctu={x**2 for x in li}; ctu
{16, 1, 4, 9}                         #2 set z tu
>>> ctu=(x**2 for x in tu); ctu 
#<generator object <genexpr> at 0x0000020A9AED5630>
>>> ctu=tuple(x**2 for x in tu); ctu   
(1, 4, 9, 16)                         #2 tuple z tu

cst=[x**2 for x in st]; cst            
[1, 4, 9, 16]                         #3 list z st
cst={x**2 for x in st}; cst
{16, 1, 4, 9}                         #3 set z st
cst=(x**2 for x in st); cst   
<generator object <genexpr> at 0x000001DA317B5AF0>
cst=tuple(x**2 for x in st); cst
(1, 4, 9, 16)                         #3 tuple z st

Jak uvedeno, komprehovat lze iterábly list, tuple, set, string, bytes, dict, range() ale pouze pro výstup ve formátu list, tuple, set, přičemž pro výstup ve formátu tuple (entice) musime použít funkci tuple(komprehence) nebo použít generátorovou verzi (jež je iterátorem) pro následnou iteraci - viz Kap. 3.2 Provádění iterace.

Komprehence pro set není to pravé ořechové, neboť generuje neuspořádané pořadí prvků - ledaže potřebujeme sestavit množinu jedinečných prvků.

Dále si ukážeme komprehenci s podmínkou:

>>> numbers = [1, 3, 2, 5, 3, 1, 4]
>>> {x**2 for x in numbers if x**2 > 3}
{16, 9, 4, 25}               # komprehence s podmínkou a redukcí
>>>
>>> files = ('bin', 'Data', 'Desktop', '.bashrc', '.ssh', '.vimrc')
>>> [name for name in files if name[0] != '.']
['bin', 'Data', 'Desktop']   # komprehence s podmínkou
Výrazových předpisů může být v jedné komprehenci více:
>>> [(x, x**2, x**3) for x in numbers]   # komprehence seznamu
[(1, 1, 1), (2, 4, 8), (3, 9, 27), (4, 16, 64)]
Může být více i poskytnutých objektů:
>>> numbers = (1, 2, 3, 4)
>>> letters = ['a', 'b', 'c']
>>> [n*letter for n in numbers for letter in letters]
['a', 'b', 'c', 'aa', 'clr-bb', 'cc', 'aaa', 'clr-bbb', 'ccc', 'aaaa', 'clr-bbclr-bb', 'cccc']

Zajímavá je úprava argumentu v této komprehenci:

>>> [v*2 for v in ("a", 5, True)]
['aa', 10, 2]                      # 2*True = 2*1 = 2

Za povšimnutí stojí i toto spojení komprehence s formátováním řetězce dle kap. 6.11:

>>> chlapci = ["Pavel", "Petr", "Jan"]
>>> synci = ["{} Novotný".format(boy) for boy in chlapci]
>>> synci
['Pavel Novotný', 'Petr Novotný', 'Jan Novotný']

Ukázka komprehence setu z řetězce:

>>> s = {v for v in "ABCDABCD" if v not in "CB"}; s
{'A', 'D'}

Komprehencí setu z řetězce lze s pomocí funkce enumerate (kap. 5.7) vytvořít výběrový slovník:

>>> d = {key:val for key,val in enumerate ('ABCD') if val not in "CB"}
>>> d
{0: 'A', 3: 'D'}

Zajímavé je použití pojmenovaného výrazu (mroží operátor - 2.6.6) při komprehenci seznamu:

>>> def f(x):
        return x + 2

>>> print([(y := f(x), x/y) for x in range(3)])
[[2, 0.0], [3, 0.3333333333333333], [4, 0.5]]

případně se zaokrouhlením des. čísla dle kap. 6.13:
>>> print([[y := f(x), round(x/y, 2)] for x in range(3)])
[[2, 0.0], [3, 0.33], [4, 0.5]]

případně s podmínkou a přehledněji:
>>> print([(x, y, round(x/y, 2)) for x in range(3) if (y := f(x)) > 0])
[(0, 2, 0.0), (1, 3, 0.33), (2, 4, 0.5)]

Následující komprehencí vybereme ze zadané množiny bodů ty, které leží uvnitř kružnice o poloměru r = 2:

>>> import math
>>> radius = 2
>>> [(x, y) for x in range(-2,3) for y in range(-2,3) if
...  math.sqrt(x**2 + y**2) < radius]
[(-1,-1), (-1,0), (-1,1), (0,-1), (0,0), (0,1), (1,-1), (1,0), (1,1)]

3.6 Generátory

Generátor je 'líný' iterátor, který místo vytváření hotových objektů ukládá instrukce pro jejich individuální použití při vlastní iteraci. Je to interní objekt (iterátor), vytvořený aplikací generátorového výrazu, případně voláním generátorové funkce.

3.6.1   Generátorový výraz

Generátorový výraz vytváří anonymní generátorovou funkci, neboli generátorový objekt, neboli iterátor. Skladba generátorového výrazu je totožná komprehenci entice (viz 3.5).

>>> st = "abakus"           # iterovatelný řetězec - iteráble 
>>> stv = (i for i in st)   # interní objekt - iterátor
>>> for i in stv: print (i,end=" ") # líná (lazy) iterace iterátoru
a b a k u s 
>>> for i in st: print (i,end=" ")  # normální, dychtivá (eager) iterace iteráblu
a b a k u s 

Generátorový výraz lze vytvořit i s použitím idiomu for pro funkci range(~). Zde funkce range() vytvoří iterovatelný objekt, který se komprehencí přemění na iterátor:

>>> gv = (i*i for i in range(5))        # interní objekt - iterátor 
>>> for i in gv: print (i, end=", ")    # líná (lazy) iterace 
0, 1, 4, 9, 16 

3.6.2   Generátorová funkce

Generátorová funkce je normální funkce, která místo příkazu return (jenž ukončí běh funkce) obsahuje příkaz yield (jenž pouze přeruší běh funkce). Volání generátorové funkce vrací generátorový objekt, jenž je iterátorem s přímo použitelnou funkcí next(~) či metodou __next__() - stejně jako v předchozím případě:

def count_to(m):           # vrací iterátor
    n = 0
    while n <= m:
        yield n            # poskytuje elementy iterátoru
        n += 1
>>> ct = count_to(5)          # gener. objekt, iterátor 
# Invokace iterátoru:
>>> next(ct)                  # 1. krok iterace (funkce)
0
>>> ct.__next__()             # 2. krok iterace (metoda)
1
>>> for i in ct: print (i, en" ")  # opakovaná iterace
2 3 4 5                                smyčkou for ...
>>> next(ct)
StopIteration                 # iterátor je prázdný
>>> ct = count_to(5)          # objekt nutno obnovit 
>>> print(list(ct))                 
[0, 1, 2, 3, 4, 5]            # výtisk generátor. objektu
>>> print(list(ct))
[]                            # iterátor je prázdný

Generátorový objekt (iterátor) vytvoří i tato generátorová funkce:

def squares(start, stop): 
    for i in range(start, stop):
        yield i * i
>>> sq = squares(1,7)            # lazy iterátor
print(list(sq))                   
1 4 9 16 25 36                   # výtisk generátor. objektu
>>> for i in sq:                 # opakovaná iterace
        print (i, end=" ")             smyčkou for ...
1 4 9 16 25 36

Výpočet řady Fibonacciho čísel můžeme provést pomocí následující generátorové funkce s nekonečnou smyčkou (!):

def fibonacci():         # generátor. funkce>
    a, b = 0, 1 
    while True:                   # nekonečná smyčka 
        yield a
        a, b = b, a+b 

Generátorový objekt (iterátor) s nekonečnou smyčkou voláme pro konkretní mez funkce range():

>> f = fibonacci()           # generátor. objekt, iterátor
>> type(f)                   # -->  <class 'generator'> 
>> f.__next__()              # -->  0
>> next(f)                   # -->  1  alternativní verze
>> next(f)                   # -->  1
>> for i in range(7): 
        print(next(f), end=", ")
 2, 3, 5, 8, 13, 21, 34,
>>> for i in range(7): 
        print(next(f), end=", ")
>>> 55, 89, 144, 233, 377, 610, 987,

Problém s touto funkcí je ten, že neobsahuje definici svého ukončení. Pro její invokaci musíme použít nástroj, který tuto definici obsahuje a tím je v tomto případě jednokrokový příkaz next(f) nebo smyčka for ... s funkcí range(7).

3.6.3   Líný výpočet

Generátorový výraz a generátorová funkce vrací generátorový objekt, jímž je pomalu (lazy) vyhodnocovaný iterátor.

Při líném výpočtu (lazy evaluation) se odkládá výpočet výrazu až do chvíle jeho potřeby. Důsledkem je zmenšená potřeba zdrojů (paměti) při zvýšeném výkonu procedury.

Lze sestavit potenciálně nekonečné struktury, např. smyčky - viz fibonacci().

Líný výpočet se obtížně kombinuje s imperativními strukturami jako je ošetření výjímek nebo vstup/výstup; může vytvářet "space leaks" (viz).

Příkladem procedury, uplatňující líný výpočet, je použití slovníku v kapitole 11.8 Switch case, kde u funkce kalkul(oprerátor, x,y) je řešeno částečné ošetření výjimek.


3.7 Jmenné prostory

Všechny aktivity spuštěného programu se odvíjejí od textu programu, vyskytujícího se v různých vymezených lexikálních oblastech kódu, zvaných jmenný prostor (namespace), JP.

Těmito oblastmi jsou:

  1. Interní soubor (knihovna souborů) Pythonu builtins, ve kterém jsou uloženy deklarace vestavěných (built-in) funkcí, tříd a konstant. Tento soubor reprezentuje oblast, zvanou built-in.
    Vestavěná oblast se automaticky načítá při spuštění interpreta a její objekty jsou vždy přímo dostupné z kterékoli další oblasti.
  2. Další oblastí, označovanou slovem globální, je text skriptu a modulu. Tato oblast obsahuje uživatelem psaný text programu - deklarace proměnných, funkcí a tříd. Globální oblastí je také text, zapsaný v otevřeném prostoru konzoly Pythonu (interpreta).
    Aktivní stav globálního prostoru vzniká či zaniká rovněž při spuštění nebo vypnutí interpreta Pythonu a jeho objekty jsou prakticky rovněž vždy přímo dostupné z globální a lokální oblasti.
  3. Následujícími oblastmi uvnitř skriptu či modulu jsou vnitřní prostory funkcí a tříd, nepřekvapivě označované slovem lokální.
    Aktivní stav lokálního prostoru vzniká invokací funkce a zaniká ukončením (výstupem z) funkce. Objekty lokálního prostoru jsou dostupné z lokální a vnitřní oblasti.
    Poznámka: O funkcích a třídách víme (viz 3.1, 12.2),
     že jejich definice se skládají ze záhlaví a z těla 
     (vnitřního prostoru) funkce či třídy. Jejich lokální
     oblast je tvořena jejch vnitřním prostorem (tělem).
    
  4. Pokud funkce sama obsahuje ještě vnitřní (vnořenou) funkci, je za další pojmenovanou oblast považován vnitřní prostor funkce vnořené - říkejme ji oblast vnitřní. Vnořená funkce může obsahovat svou další vnořenou funkci, případně může vnější funkce obsahovat více vnořených funkcí se stejnou úrovní zanoření.
    Vznik a ukončení aktivního stavu vnořeného prostoru je stejné jako u vnější (lokální) funkce. Objekty vnitřní oblasti jsou dostupné pouze z této oblasti.

Všechno v Pythonu je objekt

Objekty jsou přístupné prostřednictvím jmen, které na ně odkazují. Vázání objektu ke jménu je realizováno:

  1. aktem přiřazení hodnoty ke jménu:
    mamoj = "František"
    
  2. deklarací záhlaví a těla funkce (třídy), což představuje připojení objektu funkce (třídy) k jejímu jménu:
    def fun(m)                        # záhlaví
        print(m*m)                    # tělo
    

Jméno je na předním místě v pořadí důležitosti entit. Rozdělení zdrojového kódu do výše uvedených oblastí (vestavěná, globální, lokální, vnitřní) je důležité při rozlišování stejných jmen v programovém prostoru.
V jedné oblasti se nemohou současně vyskytovat dvě stejná jména, odkazující na různé hodnoty:

mam = "Alena"    # deklarace přiřazení
mam = "Petr"     # změna přiřazení, předchozí deklarace neplatí

Mohou se však bez kolize vyskytovat současně v různých oblastech - v různých jmenných prostorech.

Globální a lokální proměnná

Globální proměnná je tvořená v globální oblasti skriptu a je přímo dostupná z globální i z lokální oblasti (funkce) - interpret hledá jméno tam, kde je žádané, případně pokračuje v hierarchicky nadřazené oblasti.

def fun():                              # lokální prostor
    s = "Toto je lokální proměnná"
    print(s)
>>> s = "Zde je globální prostor"
>>> fun()
Toto je lokální proměnná
>>> print (s)                # příkaz v globálním prostoru
Zde je globální prostor

Vidíme, že jedinečnost jména je dána jeho umístěním. Příkaz print(s) v globálním prostoru ignoruje přiřazení jména v lokálním prostoru, neboť toto jméno je mu přímo nedostuné.

Jmenný registr

Přítomnost jména ve jmenném prostoru je zapsána ve jmenném registru (JR), jenž má formu slovníku. O existenci tohoto slovníku nás přesvědčí jednoduchá ukázka. Mějme soubor global.py v němž uplatníme vestavěné funkce globals(), locals():

# global.py                       # globální prostor

a = [1, 2, 3, 4, 5]               # globální proměnná
 
def foo():                        # lokální prostor
    b = 11                        # lokální proměnná
    print(locals())

print(globals())
foo()    
# registr globálního prostoru 'global.py'    
{'__name__': '__main__', '__doc__': None, '__package__': None, 
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__spec__':none, '__annotations__': {}, 
'__builtins__': <module 'builtins' (built-in)>,
'__file__': 'F:\\Codetest\\python\\Ukázky\\scopes\\glob_loc.py',
'a': [1, 2, 3, 4, 5], 'foo': <function foo at 0x0000023E6039CFE0>} 

 {'b': 11}                # registr lokálního prostoru foo()  

Všimněme si, že se proměnná b v globálním registru nevyskytuje, neboť patří do lokálního prostoru, potažmo do lokálního registru, kde ji vidíme.

Odchylka:
Zavedené označení výše popsaného slovníku je jmenný prostor. Nepodařilo se mi pochopit, proč má mít jeden termín dva významy (slovník a lexikální prostor) a proto jsem si dovolil zavést termín jmenný registr.

Scope jména

Souhrn oblastí, ze kterých je jméno přímo dostupné, se nazývá scope tohoto jména. Do tohoto souhrnu patří jmenný prostor, v němž je jméno deklarováno a všechny níže postavené jmenné prostory.

Dostupnost globální, lokální a vnitřní proměnné si ukážeme na příkladu funkce fun_lok(), jež obsahuje vnitřní funkci fun_inn():

glob_local.py                          # globální prostor
glo = "Živijó"                         # globální proměnná

def fun_lok():                         # globální proměnná
    lok = "lokální"                    # lokální proměnná
    print("glo, lok = ", glo, lok, sep=", ")    ## 
#   print("inn = ", inn, sep=", ")     # nemá přístup - error
    def fun_inn():                     # lokální proměnná
        inn = "inner"                  # vnitřní proměnná
        print("glo, lok, inn = ", glo, lok, inn, sep=", ")
        print(locals())      
    fun_inn()                     # invokace vnořené funkce
  
fun_lok()                         # invokace vnější funkce

Poznámka ad ##: sep=", " je atribut funkce print() .

==== RESTART: E:/ALangdoc/Python/scopes/glob_local.py ====

glo, lok = Živijó, lokální
glo, lok, inn = Živijó, lokální, inner
{'inn': 'inner', 'lok': 'lokální'}

Na příkladu vidíme, že

  1.   Z lokálního prostoru vnější funkce lze volat globální proměnnou "glo" a lokální proměnnou "lok". Nelze volat vnořenou proměnnou "inn".
  2.   Z vnořeného prostoru funkce lze volat jak globální proměnnou "glo" a lokální proměnnou "lok", tak vnitřní proměnnou "inn".

Z obou prostorů funkce můžeme volat i vestavěná jména z modulu "builtins", jak ukázáno na příkladu funkce locals().


3.8 Prostředí __main__

Prostředí __main__ je účelové označení prostředí spuštěné konzoly nebo spuštěného souboru (např. >> python mojeVoje.py).

Proměnná __name__ je speciální (dunder) proměnná, jejíž hodnota závisí na prostředí (konzoly, souboru či modulu), v němž je aktivována.
Je-li soubor spuštěn přímo (viz) nebo je otevřena konzola Pythonu, je proměnné __name__ přiřazena hodnota __main__.

Je-li realizovaný soubor importován jako modul, je hodnotou proměnné jméno importovaného souboru (bez přípony .py), o čemž se přesvědčíme v konzole Pythonu po importu modulu sys:

>>> __name__             # --> '__main__'
>>> import sys
>>> sys.__name__         # --> 'sys'

Kromě prostředí __main__ existuje ještě funkce main().

Proměnná __name__ se dvěma možnými hodnotami (__main a jméno modulu) umožňuje variabilně zapojovat další kód do aktuálně prováděného zdrojového kódu a to buď konkretním přikazem v těle smyčky

if __name__ == "__main__":
    text zdrojového kódu
nebo pomocí funkce main(), která takový příkaz obsahuje:
def krok_1():
   print("Provádí se první krok ...")
def krok_2():
    print("Provádí se druhý krok ...")
def main():
    krok_2()
if __name__ == "__main__":
    main()

3.9 Introspekce Pythonu

Introspekce Pythonu je akt zkoumání obsahu a vlastností jednotlivých jeho objektů. Za tímto účelem disponuje Python řadou vestavěných funkcí, z nichž některé již známe.

Docstring - dokumentační řetězec

Docstring je krátká informace ve formátu řetězce ("""Krátké info ..."""), uvedená v záhlaví souborů, funkcí a tříd - viz kap. 4.1, 12.2.1.


3.10 Testování s 'doctestem'

Při rozvoji programu se s oblibou provádí testování vybraných úseků zdrojového kódu. Pro toto testování poskytuje Python moduly doctest a unittest.

Popíšeme si práci s modulem doctest. Zkoumané vzorky kódu se umístí do dokumentačního řetězce pod záhlavím funkce. V každém vzorku je na prvním řádku kód, jakoby zadaný v interaktivním režimu, na druhém řádku je očekávaná odezva.

Modul doctest automaticky spustí příkaz začínající >>> a jeho výstup porovná s následujícím řádkem.

def is_divisible_by_2_or_5(n):
    """
    >>> is_divisible_by_2_or_5(8)
    True
    >>> is_divisible_by_2_or_5(7)
    False
    >>> is_divisible_by_2_or_5(5)
    True
    """ 

    return n % 2 == 0 or n % 5 == 0

if __name__ == '__main__':
    import doctest
    doctest.testmod()

Poslední tři řádky spouští celou parádu. Umisťujeme je na konec každého souboru, který obsahuje doctesty.

Pokud procedura zjistí shodu mezi zkoumanými vzorky a zadanými výsledky, reaguje "mlčením" nebo je-li v konzole evokována funkce "doctest.testmod()", vrátí stručný komentář o průběhu doctestu.

>>> doctest.testmod()
TestResults(failed=0, aclr-ttempted=3)

Pokud narazí na rozpor, spustí rozsáhlé chybové hlášení.

Spuštění neúspěšného skriptu vyprodukuje například následující výstup:

Failed example:
    is_divisible_by_2_or_5(7)
Expected:
    True
Got:
    False
***********************************************************
1 items had failures:
   1 of   3 in __main__.is_divisible_by_2_or_5
***Test Failed*** 1 failures.

3.11 Glosář

přírůstkový rozvoj (incremental development)
Postupné rozšiřování programu se záměrem testovat postupně jenom menší objem kódu.
brlení (scaffolding)
Kód, použitý při rozvoji programu, který není součástí konečné verze.
záznam (trace)
Sledování toku výpočtu, při kterém se zaznamenávají hodnoty proměnných a výstupů.
zpětný záznam (traceback)
Seznam prováděných funkcí, který se zobrazí na obrazovce, když se vyskytne chyba při běhu programu. Zpětný záznam je také uváděn jako záznam zásobníku, protože probírá funkce v pořadí, ve kterém byly do zásobníku běhu programu zařazeny.
zapouzdřit (encapsulate)
Zabalit část programu do funkce.
rozvojová metoda
Postup vývoje programu. V této kapitole jsme si předvedli způsob, založený na vytváření kódů pro jednoduché, specifické úkoly; kódy jsou potom zapouzdřeny a generalizovány.
 jednotkové testování (unit testing)
Automatická procedura používaná k ověření správné činnosti jednotlivých úseků kódu. V Pythonu se pro tento účel používá vestavěný modul doctest.

3.12 Cvičení

  1. Napište jediný řetězec, který
     stvoří
     tento 
     výstup
    
  2. Přidejte volání funkce print k fci sqrt definované v odstavci 5.9 tak, že vytiskne hodnotu better ve smyčce po každé aproximaci. Volejte upravenou funkci pro argument 25 a zapište výsledky.
  3. Zaznamenejte průběh výpočtu poslední verze fce print_mult_table (odstavec 5.8) a zamyslete se nad jeho postupem.
  4. Do souboru triangularNumbers.py napište funkci print_triangular_numbers(n), která vytiskne součet prvních n členů aritmetické posloupnosti (1 3 6 10 ... trojúhelníková čísla). Voláním funkce dostaneme následující výstupy:
     1     1
     2     3
     3     6
     4     10
     5     15
    
  5. Do souboru testPrime.py vložte funkci test_prime, která vyhodnotí zadané celé číslo a vrátí True pro argument, který je prvočíslem a False pro argument, který prvočíslem není. Při vývoji funkce používejte "doctesty".
    Soubor s doctesty končí tímto kódem:
    if __name__ == '__main__':
        import doctest
        doctest.testmod()
    
  6. Funkce num_digits(n) v odstavci 5.3 nepracuje správně pro n < 0. Doplňte ji tak, aby pracovala správně pro všechna celá čísla (včetně nuly).

    def num_digits(n): 
        """
        >>> num_digits(12345)
        5
        >>> num_digits(0)
        1
        >>> num_digits(-12345)
        5
        """
    
  7. Do souboru numEvenDigits.py přidejte následující kód a tělo příslušné funkce:
    def num_even_digits(n): 
        """
        >>> num_even_digits(123456)
        3
        >>> num_even_digits(2468)
        4
        >>> num_even_digits(1357)
        0
        >>> num_even_digits(2)
        1
        >>> num_even_digits(20)
        2
        """
    
  8. Do souboru printRevDigits.py přidejte následující kód a tělo přislušné funkce:
    def print_digits(n): 
        """
        >>> print_digits(13789)
        9 8 7 3 1
        >>> print_digits(39874613)
        3 1 6 4 7 8 9 3
        >>> print_digits(213141)
        1 4 1 3 1 2
        """
    
    Nutno zajistit, aby mezi číslicemi výstupu byly mezery a aby za poslední číslicí žádná mezera nebyla. Numerický argument funkce nutno před manipulací přetvořit na string (viz 2.6), jehož délku určíme funkcí len (viz 2.10).
    Pokud se vám nepodaří aby za poslední číslicí žádná mezera nebyla, nevyjádří doctest souhlas mlčením. Jednotlivá funkce ale může pracovat bez závady - protože mezera za poslední číslicí jinak nevadí.
  9. Do souboru sumofSquares.py přidejte funkci sum_of_squares(n), která spočítá součet čtverců číslic zadaného celého čísla. Například, sum_of_squares(72) by mělo vrátit 53, protože 7**2 + 2**2 == 49 + 4 == 53.
    def sum_of_squares_of_digits(n): 
        """
        >>> sum_of_squares(1)
        1
        >>> sum_of_squares(9)
        81
        >>> sum_of_squares(11)
        2
        >>> sum_of_squares(121)
        6
        >>> sum_of_squares(987)
        194
        """
    
    Svá řešení ověřte pomocí doctestů.

pre up next title end end