pre up next title end end

3. Postupy a techniky

  1. Iterace a iterátory
  2. Interní index elementu
  3. Úseky sekvencí
    1. Úsekový operátor
    2. Funkce slice()
  4. Pomocné techniky
    1. Hromadné přiřazení
    2. Rozbalení sekvence
    3. Rozbalení slovníku
  5. Komprehence
  6. Generátory
    1. Generátorový výraz
    2. Generátorová funkce
    3. Líný výpočet
  7. Jmenné prostory
    1. Všechno je objekt
    2. Globální a lokální proměnná
    3. Jmenný registr
    4. Scope jména
  8. Proměnná   __name__
  9. Introspekce Pythonu
  10. Cvičení

3.1  Iterace a iterátory

Iterace
je opakovaný výpočet pro hodnoty jednotlivých členů iterovatelného objektu (iteráblu), jímž jsou sekvence typu string, list, tuple, range, bytes, bytearray, memoryview a kolekce typu set, frozenset, dict.

Iterábl vlastní magickou (dunder) metodu __iter__() a/nebo __getitem__(). Přítomnost těchto metod v objektu lze zjistit funkcí dir(iterable).

Iterace iteráblu produkuje 'dychtivý' (eager) výstup, iterace iterátoru produkuje 'líný' (lazy) výstup, viz odst. 3.6.3.

Iterátor
je objekt, vlastnící vestavěnou funkci iter() a next(). Explicitně jej vytvoříme z iteráblu funkcí iter(iterable) nebo metodou iterable.__iter__(); samotný iterátor je rovněž iteráblem.
Implicitně si iterátor pro zadaný iterábl vytváří smyčka for... a funkce list(), open(), min(), max(), map(), filter(), zip(), enumerate() a reversed(). Implicitně je iterátorem rovněž objekt typu file.

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. 10 .7.

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]            # iterábl

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

>>> next(ili)                 # první krok iterace
 2
>>> ili.__next__()            
 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 =", ")
 2, 5, 0,	

3.2  Interní index elementu

Jednotlivé elementy sekvencí 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).
Rozdah indexů vychází ze zadané sekvence, jak ilustruje následující obrázek:

indexing

Indexování zleva začíná nulou, indexování zprava doleva se provádní pomocí záporných čísel. Výběr začíná prvním indexem a končí posledním:

>>> pozdrav = "Hello World"
>>> pozdrav[5]
 ' '                              # zde mezera mezi slovy
>>> 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.3  Úseky sekvencí

Vymezená část sekvence typu string, list, tuple, set, bytes, range se nazývá úsek (slice) nebo subsekvence. Výběr úseku se provádí jednak úsekovým operátorem (např. [n:m]) nebo funkcí slice(). Operátor vrací úsek řetězce od znaku n včetně (inclusive), před znak m (exclusive).

3.3.1  Výběr úsekovým operátorem

Skladba příkazu pro seq = sekvence:  seq[start : end : krok] - uvedené parametry jsou obecně vzato - nepovinné.

Není-li krok zadán, má implicitně hodnotu +1 a skladbu příkazu lze zjednodušit na seq[start : end]. Hodnota parametru end je index prvního elementu, který do zamýšleného úseku již nepatří.

V následující tabulce jsou jsou uvedeny různé variace úseků zadané sekvence seq. U některých příkazů jsou rozepsány variantní hodnoty explicitních i implicitních parametrů:

           0  1  2  3  4  5  6        # indexy 0 ÷ 6
>>> seq = [1, 2, 3, 4, 5, 6, 7]       # upravovaná sekvence
          -7 -6 -5 -4 -3 -2 -1        # indexy -1 ÷ -7

>>> a =   seq[::]; a   # dtto seq[:] nebo seq[0:(len(seq)+1):1]
 [1, 2, 3, 4, 5, 6,7]                          
      	
>>> b = seq[2::]; b    # dtto seq[2:] nebo seq[2:(len(seq)+1):1]
 [3, 4, 5, 6, 7]        
                   
>>> c = seq[:5:]; c    # dtto seq[:5] nebo seq[0:5:1]                  
 [1, 2, 3, 4, 5]       
       
>>> d = seq[2:6:]; d   # dtto seq[2:6] nebo seq[2:6:1]                 
 [3, 4, 5, 6]              
       
>>> e = seq[::2]; e    # nebo seq[0 : (len(seq)+1) : 2]                  
 [1, 3, 5, 7]          
       
>>> f = seq[::-2]; f                 
 [7, 5, 3, 1]

>>> g = seq[2:6:2]; g                 
 [3, 5]

>>> h = seq[2:6:-2]; h                
 []                          # prázdná subsekvence

>>> i = seq[6:2:2]; i 
 []                          # prázdná subsekvence

>>>  j = seq[3:-5]; j
 []                          # prázdná subsekvence

>>>  k = seq[-5:3]; k
[]                           # prázdná subsekvence

>>> l = seq[2::2]; l         # nebo seq[2:(len(seq)+1):2]                
[3, 5, 7] 

>>> m = seq[8:1:-2]; m               
[7, 5, 3]

>>> n = seq[-4:-2]; n        # nebo seq[-4:-2:1]               
[4, 5]                 

Poznámka:

Úsek je jako orientovaná úsečka (-->) - má počátek a konec. Není-li směr úseku vyjádřen hodnotou počátku a konce (např. jen :: krok - viz případ e, f), je jeho orientace dána kladnou či zápornou hodnotou kroku (orientací kroku).
Pokud mají úsek a krok opačnou orientaci, je výsledkem prázdný úsek - viz případ h, i.
Dva příkazy na jednom řádku (oddělené středníkem) lze zadat pouze v REPL.

3.3.2  Výběr funkcí slice():

Funkce slice() má formálně tři parametry: start (inkluzivní index počátku), end (exkluzivní index konce) a krok (viz). Parametry start a krok jsou nepovinné. Nejsou-li zadány, znamená to, že start = 0, krok = 1.
Podle znaménka indexů start, end může být úsek orientován zleva doprava nebo zprava doleva. Stejně tak krok může směřovat vpravo či vlevo - opět v závislosti na kladné či záporné hodnotě kroku.

Působení vestavěné funkce slice() je zajímavé. Tato funkce vytvoří objekt bez ohledu na zamýšlenou sekvenci:

>>> seq = "Nazdar Pythone!"
>>> slice_obj = slice(3,11); slice_obj
 slice(3, 11, None)             # objekt funkce slice()

# Tento objekt lze uplatnit na sekvence různých typů:
>>> sub_seq = seq[slice_obj]; sub_seq         
 'dar Pyth'            

# Případně lze pro jedno použití slice(3,11) dosadit přímo :
>>> sub_seq = seq[slice(3,11)]; sub_seq
 'dar Pyth'


Dobrou zvláštní okolností při zadávání kladně i záporně orientovného úseku je skutečnost, že jeho deklarovaná délka může být delší než len(seq)+1 a nemůže zde dojít k chybě typu IndexError: end index out of range:

>>> seq_op = [1, 2, 3, 4, 5, 6, 7]
>>> seq_sl = "Nazdar Pythone!"

>>> d = seq_op[2:9]; d                    
 [3, 4, 5, 6, 7]
>>> f = seq_sl[slice(-10, -20, -1)]; f    
 radzaN

3.4  Pomocné techniky

3.4.1  Hromadné přiřazení

Hromadné přiřazení (multiple assignment) umožňuje přiřadit jediným příkazem výčet hodnot na pravé straně k výčtu jmen na straně levé. Tento příkaz lze zadat dvojím způsobem:

# V jednom řádku:
>>> x,y,z = 10,20,30; x,y,z    # 3 jména --> 3 hodnoty
 (10, 20, 30)
                
# Ve dvou řádcích:
>>> seq = 1,2,3                # 1 jméno <-- 3 hodnoty              
>>> a,b,c = seq                # 3 jména <-- 1 entice
>>> a*b*c       
 6        # Při přiřazení došlo k rozbalení hodnot z entice.

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 = tuple 
 (12, 22)
>>> 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 pomocí vestavěné methody items():

>>> dic = {'a':2, 'b':4, 'c':10}     # deklarace slovníku
>>> 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     # fce vrací 3 hodnoty
		
# hromadné přiřazení k výstupu volané funkce	
>>> num, sqr, cub = mocniny(2)         
>>> num, sqr, cub                      # invokace funkce
 (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
# hromadné přiřazení k hodnotám objektu "body":
>>> (x1,y1), (x2,y2) = body            
>>> 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.4.2  Rozbalení sekvence

Pokud je na levé straně počet členů <= 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 'rozbaluje' do listu
>>> [*a] = 1,2,3,4,5; a        # [*a] je seznam (list)
 [1,2,3,4,5]                   # list 'rozbaluje' do listu

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 = 7,8; a,b,c         # OK: 3 <= 1 + 2
 (7,[],8)   # hodnoty 1,2 měly přednost, na b* nic nezbylo    

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"Prostřední položka je {middle}, poslední je {last}")
 Prostřední položka je [25, True], poslední je 7

3.4.3  Rozbalení slovníku dekorací **

Dekorace **, uvedená před názvem slovníku, přikazuje rozbalení jeho klíčů (jmen) 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ý, druhý = {"A": 1, "B": 2}, {"C": 3, "D": 4}    # deklarace
>>> 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 | (sjednocení):

>>> prvý, druhý = {"A": 1, "B": 2}, {"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.5  Komprehence

Komprehence je skladebný předpis pro vytvoření modifikovaného objektu z poskytnutého iteráblu typu list, tuple, range(), set, frozenset (neměnitelná verze setu), dict a string (formálně také bytes, bytearray a memoryview) 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
  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>              # bez podmínky
S = <f(x) for x in <objekt> if <podmínka>

Závorky kolem deklarované komprehence jsou povinné a mají vliv na výsledný typ modifikovaného objektu. Používají se závorky: [], { } a (). Použijí-li se kulaté závorky, nutno je použít s funkcí tuple.

Idiom f(x) for x in <objekt> je zároveň generátorovým výrazem viz odst. 3.6.1, umožňujícím iteraci.

Ukažme si konkretní příklad komprehence bez podmínky u iteráblů list, tuple, range(), string, set, frozenset, dictionary, bytes, bytarray a memoryview.
Nutno poznamenat, že pro řádné provedení transformací je nutné aby objekty typu set a klíče slovníku neodkazovaly na typ string:

# Výpis výchozích iteráblů:
li, tu = [1, 2, 3, 4], (1, 2, 3, 4)       # list, tuple
rg, sg = range(2,6), "růže"               # range, string  
st = {4, 7, 4, True}                      # set 
fs = frozenset(st)                        # frozenset
dc = {4:"size", 5:"Karel"}                # dictionary
by = bytes(sg, 'utf-8')                   # bytes from string
ba, mv = bytearray(by), memoryview(by)    # bytearray, memoryview
   
# Použité závorky (<> = [], (), {}) mají vliv na výsledný typ
modifikovaného objektu. Pokusíme-li se o obecný záznam zamýšlené komprehence, mohli   bychom pro některý z "výchozích" iteráblů napsat třeba toto:
mod_obj = <x*2 for x in iterable> # kde <> = [], {} # Kulaté závorky () použijeme s funkcí tuple(): mod_obj = tuple(x*2 for x in iterable) # Bez funkce tuple získáme místo upraveného objektu generátorový
objekt, například:
gen_obj = (x*2 for x in iterable); gen_obj <generator object <genexpr> at 0x00000124DACB2810>

Jak uvedeno, iterábly lze komprehovat 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().

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í              
>>>
>>> terms = ('bin', 'Data', 'Desktop', '.bashrc', '.ssh', '.vimrc')
>>> [name for name in terms 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), (3, 9, 27), (2, 4, 8),  (5, 25, 125) ...]

Dostali jsme výpis entic pro každý (i opakovaný) prvek zadaného seznamu . Výpis bez opakování získáme komprehencí setu místo seznamu.

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-mm', 'cc', 'aaa', 'clr-mmb',
 'ccc', 'aaaa', 'clr-mmclr-mm', 'cccc']

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

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

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

>>> 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 podmínkou:

>>> 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.8) 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'}

Jiný slovník vytvoříme z objektu typu range:

>>> {x: x**2 for x in range(1, 6)}
 {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

Zajímavé je použití pojmenovaného výrazu (mroží operátor - 2.6.4) 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 - viz vestavěné funkce
>>> 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átorový objekt (neboli generátor) vznikne přiřazením generátorového výrazu nebo generátrové funkce ke jménu proměnné.
Tento objekt je iterátorem, který neobsahuje žádná data, pouze je evokuje v okamžiku invokace.

3.6.1   Generátorový výraz

Generátorový výraz má formát idiomu f(x) for x in <objekt>, neboli komprehence (viz 3.5).
Iterace iteráblu provádí normální (eager) iteraci, iterace iterátoru provádí tak zvanou línou (lazy) iteraci. Líná iterace je méně náročná na paměť
V následující ukázce jsou obě iterace realizovány smyčkou for i in ~.

>>> st = "abakus"           # iterovatelný objekt - iterábl 
>>> stv = (i for i in st)   # generátorový objekt - iterátor
>>> type(stv)
 <class 'generator'>
>>> 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=" ")  # dychtivá (eager) iterace iteráblu
 a b a k u s 

Generátorový výraz lze vytvořit pro traverzování všech iteráblů. Zde vytvoříme iterátor pro iterábl typu range():

>>> gv = (i*i for i in range(5))       # 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__(), případně lze použít smyčku for ... jako v předchozím případě:

# count_to.py
def count_to(m):            # vrací iterátor
    n = 0
    while n <= m:
        yield n             # poskytuje elementy iterátoru
        n += 1
>>> %Run count_to.py
>>> 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, end=" ")  
 2 3 4 5              # pokračování iterace 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:

# squares.py
def squares(start, stop): 
    for i in range(start, stop):
        yield i * i

Přiřazením invokace generátorové funkce ke jménu vytvoříme iterovatelný generátorový objekt (typu generátor).
Iteraci provedeme buď opakovaným použitím fce next(sqr) nebo najednou prostřednictvím smyčky for ... či pomocí funkce list(sqr), která v tomto případě působí jako iterátor:

%Run squares.py
>>> sqr = squares(1,7)            # lazy iterátor

>>> next(sqr), next(sqr)          # opakovaná iterace
 (1, 4)
>>> for i in sqr:                 # smyčkou for ... vyčerpáme                 
        print(i, end=" ")                   zbytek najednou               
 9 16 25 36

>>> sqr = squares(1,7)            # obnova iterátoru
>>> list(sqr)                     # najednou fcí list()                   
 [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:

fibo.py
def fibo():         # generátorová 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 buď po krocích nebo pro konkretní mez, určenou funkcí range():

%Run fibo.py
>>> f = fibo()                # generátorový objekt, iterátor
>>> f.__next__()              # -->  0  jiná verze fce next()
>>> next(f), next(f)          # -->  (1, 1)  

# Můžeme pokračovat do omrzení pomocí fce range():                         
>>> for i in range(4):        
       print(next(f), end=", ")    # --> 2, 3, 5, 8

>>> for i in range(5): 
        print(next(f),end=", ")   # --> 13, 21, 34, 55, 89

Funkce next(f) zde ve smyčce začíná tam, kde skončila předchozí invokace generátorového objektu f. Funkcí range() určujeme délku pokračující iterace po nekonečné řadě Fibonacciho čísel.

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 9.12 (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, vyskytujícího se v různých lexikálních (textových) oblastech kódu, zvaných jmenný prostor (namespace), JP.

Těmito oblastmi (jmennými prostory) jsou:

  1. Interní knihovna souborů Pythonu (builtins), ve které jsou uloženy deklarace vestavěných (built-in) funkcí, tříd a konstant. Tato knihovna reprezentuje oblast, zvanou built-in.
    Vestavěná oblast se automaticky aktivuje 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 (viz Kap.1.3; 3. odst.), případně také text, zapsaný ve skriptovacím prostoru IDLE či textového editoru aplikace Thonny. Tato oblast obsahuje uživatelem psaný text programu - deklarace proměnných, funkcí a tříd.
  3. Následujícími oblastmi uvnitř skriptu, modulu či skriptovací oblasti IDLE nebo Thonny 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) této 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, že jejich
     definice  se skládají ze záhlaví a z těla. Jejich lokální
     oblast je tvořena jejch  vnitřním prostorem (tělem).
    
  4. Pokud lokální funkce 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í.

Jmennými oblastmi jsou tedy tyto oblasti (anglicky): built-in, global, local a inner.

3.7.1  Všechno 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.

3.7.2  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.

# glob_lok.py                  # globální prostor skriptu

def fun():                     # globální fce s lok. prostorem
    s = "Toto je lokální 's'"  # lokální proměnná   
    print(s)1                  # fce built-in

Poznámka 1: Vestavěné funke lze volat odkudkoli - nemá tedy smysl zkoumat, zda se nachází v pozici funce globální, lokální či vnitřní.

>>> %Run glob_lok.py   # globální prostor interpreta Pythonu:

>>> s = "Toto je globální 's'"  # globální proměnná
>>> fun()                       # invokace glob. fce                                                                     
  Toto je lokální 's'           # prezentace lok. proměnné
>>> print(s)                      
  Toto je globální 's'          # prezentace glob. proměnné

3.7.3  Jmenný registr

Přítomnost jména ve jmenném prostoru je zapsána ve jmenném registru, jenž má formu slovníku (dict). 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() a locals() jimiž lze zobrazit jmenné registry příslušných jmenných prostorů:

# global.py                        # globální prostor skriptu

a = [1, 2, 3, 4, 5]                # globální proměnná skriptu
def foo():                         
    b = 11                         # lokální proměnná funkce
    print("Locals:" locals())      # lokální funkce (built-in)
    print("Alelujá")      
    print("Globals:" globals())    # lokální funkce (built-in)
	

Vestavěné funkce locals(), globals() vrací obsahy registrů jednotlivých jmenných prostorů ve formě slovníku:

>>> %Run global.py
>>> foo()                 # invokace globální funkce
 Locals: {'b': 11}
 Alelujá
 Globals: {'__name__': '__main__', '__doc__': None, '__package__': None,
 '__loader__': <   _frozen_importlib_external.SourceFileLoader object at  0x0000016539A047C0>,
 '__spec__': None, '__annotations__': {}, '__builtins__': , 
 '__file__': 'F:\\Howto-py\\ch-03\\a_folders\\3.7.3\\global.py',
 'a': [1, 2, 3, 4, 5], 'foo': <function foo at  0x000001653A325BD0>}

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

3.7.4  Scope jména

Jmennými oblastmi jsou oblasti (anglicky): Built-in , Global, Local a Inner. Tyto oblasti tvoří uspořádanou kompetenční hierarchii (BGLI) - od nejvýše k nejníže postavené.

Souhrn lexikálních oblastí, z nichž je jméno 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. Obecně lze řící, že z výše postavené oblasti nelze volat jméno v níže postavené oblasti.

Dostupnost jmen si ukážeme na příkladu funkce fun_lok() , jež obsahuje vnitřní funkci fun_inn().
Následující soubor glob_local.py také ilustruje výskyt invokace vestavěné (built-in) funkce print() v různých prostorech (local a inner):

# glob_local.py              # globální prostor

glob = "živijó"              # globální proměnná

def fun_glob():              # globální funkce
    lok = "lokální"          # lokální proměnná
    print("lok, glob =", glob, lok, end=" ")  # built-in funkce 
    
    def fun_lok():           # lokální funkce
        inn = "inner"        # vnitřní proměnná
        print("glob, lok, inn = ", glob, lok, inn, end=" ")
        print(viz 4.1)              # přechod na nový řádek ()      
        print("Locals:", locals())    
        print("-- kávička --")
        print("Globals:", globals())                            
       	
    fun_lok()                # invokace lokální funkce
fun_glob()                   # invokace globální funkce
>>> %Run glob_local.py
 lok, glob = živijó lokální 
 glob, lok, inn =  živijó lokální inner 
 Locals: {'inn': 'inner', 'lok': 'lokální'}
 -- kávička --
 Globals: {'__name__': '__main__', '__doc__': None, '__package__': None, ' ... 
 ... 'fun_glob': <function fun_glob at 0x000002097EBC5BD0>}

Na příkladu tedy vidíme, že:


  3.8 Proměnná __name__

Proměnná __name__ je speciální proměnná, která je při invokaci souboru vytvořena interpretem Pythonu a je jí přiřazen název dotčeného souboru.

Její hodnota se mění v závislosti na tom, zda je realizovaný skript spuštěn přímo, nebo byl importován jako modul.

Označení __main__ je název prostředí, v němž je program zpočátku spuštěn.

Idiom if __name__ == '__main__' ověřuje, zda dotčený skript je realizován (run) přímo jako hlavní (main) program nebo zda byl importován z jiného souboru (ergo modulu):

  1. byl-li spuštěn přímo v otevřené konzole Pythonu nebo příkazem v systémové konzole ( > python my_file.py), potom __name__ == __main__
  2. byl-li importován jako modul (viz kap. 7.5), potom __name__ == název souboru (bez přípony .py)

Případ si ilustrujme na podrobnější ukázce. Mějme dva soubory, prvnímu budeme říkat modul, protože jej chceme importovat do jiného souboru:

# cosi_name.py

def cosi():  
    print('Hodnota proměnné __name__  jest: ', __name__)
	
if __name__ == "cosi_name":
   print("Nekapišto.")	

Provedením modulu v Thonny zjistíme, že je ticho po pěšině - deklarovaná funkce nebyla volána, idiom if __name__ == 'cosi_name' je nepravdivý, tudíž se příkaz print('Nekapišto.') nerealizuje. Můžeme ale v Thonny volat funkci cosi():

%Run cosi_name.py
>>> cosi()
 Hodnota proměnné __name__  jest:  __main__

Napíšeme si další soubor ...

# other_name.py

import cosi_name                          # import modulu
print('Pro cosi_name je __name__ =', cosi_name.__name__)
print('Pro other_name je __name__ =', __name__)

if __name__ == "cosi_name":
    print("Kapišto?")	

... a po jeho spuštění zjistíme, že nám na prvním místě figuruje výstup z importovaného souboru cosi_name.py:

>>> %Run other_name.py
 Nekapišto.                                  # naše záhada
 Pro cosi_name je __name__ = cosi_name       # jasná páka
 Pro other_name je __name__ = __main__       # jasná páka
Rozuzlení:

Importovaný soubor cosi_name.py změnil status. Jeho atribut __name__ má nyní hodnotu __cosi_name__ - proto mohlo dojít k vytištění řetězce 'Nekapišto.' a to ještě před výstupy ze souboru other_name.py.


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.


Dalším introspekčním nástrojem je t.zv. docstring (dokumentační řetězec), což je krátká informace ve formátu řetězce (''' ~ '''), uvedená v záhlaví souborů, funkcí a tříd - viz kap. 6.11.


3.10  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 Kap. 5.11 tak, že vytiskne hodnotu better ve smyčce po každé aproximaci. Volejte upravenou funkci pro argument 25 a zapište výsledky.

  3. 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 tn(n) = (n)*(n+1)/2). Voláním funkce dostaneme následující výstupy pro n=5:

     1     1
     2     3
     3     6
     4     10
     5     15
    
  4. 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()
    
  5. 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
        """
    
  6. 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
        """
    
  7. 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í.
  8. 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