previous up next hi end end

10. Entice a sety

  1. Vytvoření entice
  2. Složené entice
  3. Pojmenované entice
  4. Hromadné přiřazení
  5. Entice jako výstupní hodnoty
  6. Čisté funkce a modifikátory III
  7. Set a frozenset
  8. Funce zip
  9. Aplikace SeqTools
  10. Glosář
  11. Cvičení

10.1 Vytvoření entice

Entice (tuple) je neměnitelný indexovaný seznam hodnot, oddělených čárkou:

>>> tuple = 'a', 'b', 'c', 'd', 'e'

Zápis na pravé straně vyhodnotí interpret jako entici (tuple) a jako takovou ji vrátí:

>>> tuple
('a', 'b', 'c', 'd', 'e')

Při tvorbě entice s jedinou položkou musíme připojit závěrečnou čárku:

>>> t1 = 'a',            lépe t1 =("a",)
>>> t1; type(t1)
('a',)
<class 'tuple'>

Bez čárky by byl výraz 'a' považován překladačem Pythonu za řetězec.

>>> t2 = ('a')
>>> t2; type(t2)
'a' 
<class 'str'>

Odhlédneme-li od skladby, jsou operace s enticemi stejné jako operace se seznamy. Indexový operátor vybere položku z entice:

>>> tuple = ('a', 'b', 'c', 'd', 'e')
>>> tuple[0]
'a'

Úsekový operátor vybere rozsah položek:

>>> tuple[1:3]
('b', 'c')

Pokusíme-li se změnit některou z indexem vybraných položek, dostaneme chybové hlášení:

>>> tuple[0] = 'A'
TypeError: 'tuple' object doesn't support item assignment

Neměnitelnost entice znamená, že ji nelze změnit beze změny identity (ID). Pokud nám nevadí změna ID, můžeme entici - jako přiřazenou hodnotu k proměnné, upravit pomocí součtu entic a úsekového operátoru:

>>> tuple = ('A',) + tuple[1:]
>>> tuple
'A', 'b', 'c', 'd', 'e')

>>> ruple = ('a', 'b', 'c', 'd', 'e'); id(ruple)
56396448
>>> ruple = ruple[:2] + ("C",) + ruple[3:]
>>> ruple; id(ruple)
('a', 'b', 'C', 'd', 'e')
63672720

10.2 Složené entice

Pokud entice obsahuje vložený měnitelný element typu list a dict (nikoliv tuple a set), lze tento prvek změnit obvyklým způsobem a kopírovat metodou copy a deepcopy z modulu copy:

>>> import copy
>>> lt = ("a", [2.5, "b"])            # vložený seznam
# id(lt)     --> 60715336
>>> slt = copy.copy(lt)               # závislá kopie 
>>> slt[1][1] = "joj";  lt, slt
(('a', [2.5, 'joj']), ('a', [2.5, 'joj']))
# id(lt), id(slt)     --> (60715336, 60715336)
 
>>> lt = ("a", [2.5, "b"])
# id(lt)     --> 67797224
>>> dlt = copy.deepcopy(lt)           # nezávislá kopie 
>>> dlt[1][1] = "hoj";  lt, dlt
(('a', [2.5, 'b']), ('a', [2.5, 'hoj']))
# id(lt), id(dlt)     --> (67797224, 67797384)

Při úpravě vloženého slovníku musíme zadávat nikoliv index, nýbrž klíč:

>>> import copy
>>> dt = ("a", {2: "b"})              # vložený slovník
# id(dt)     --> 54882600
>>> sdt = copy.copy(dt)               # závislá kopie
>>> sdt[1][2] = "číč";  dt, sdt
(('a', {2: 'číč'}), ('a', {2: 'číč'}))
# id(dt), id(sdt)     --> (54882600 54882600)

>>> dt = ("a", {2: "b"})
# id(dt)     --> 58810760)
>>> ddt = copy.deepcopy(dt)           # nezávislá kopie
>>> ddt[1][2] = "rýč";  dt, ddt
>>> (('a', {2: 'b'}), ('a', {2: 'rýč'}))
# id(dt), id(ddt)     --> (58810760 58810920)

Závislou kopii jak prosté, tak složené entice lze rovněž vytvořit úsekovým operátorem [:]:

>>> tup = ('a', 'b', 'c')
>>> rup = tup[:]; rup
('a', 'b', 'c')
>>> id(tup), id(rup)
(46444232, 46444232)

dt = ("a", {2: "b"})
>>> sdt = dt[:]; sdt
('a', {2: 'b'})
>>> id(dt), id(sdt)
(46497384, 46497384)

Vestavěná metoda tpl.copy() pro entice nechodí.


10.3 Pojmenované entice

Pojmenovaná entice (named tuple) je normální entice, opatřená jménem a názvy položek. Prvky entice jsou tedy kromě indexů přístupné také přes slovní označení.
Práce s pojmenovanou enticí připomíná práci s deklarovanou třídou a jejími instancemi - viz Kap.12. Třídou je zde "matriční" entice, vytvořená pomocí importované funkce namedtuple() z modulu collections:

Název_typu = namedtuple('Název_typu', 'výpis polí' [, rename=False] [, defaults=None] [, module=None])

Z takto obecně deklarované entice (třídy) vytvoříme jednotlivé entice (instance) s konkrétními hodnotami příslušných položek. Užitečnost pojmenované entice spočívá v tom, že si nemusíme pamatovat význam jednotlivých polí.

Práci s pojmenovanou enticí si nejlépe ukážeme na konkrétním příkladě, ve kterém si vytvoříme kartotéku se jmény zaměstnanců, jejich obory a platy (převzato ze stránky DaniWeb s laskavým svolením autora):

# ntEmpRec.py

from collections import namedtuple

Vytvoření vzorové entice s určením polí (atributů):
<název_NT> = namedtuple(<název_NT>, <názvy polí>) jen dva argumenty EmpRec = namedtuple('EmpRec', 'name department salary') Vytvoření konkrétních entic (lze zadat poziční i pojmenované argumenty) <jméno_pole> = <název_NT> (hodnoty jednotlivých atributů) bob = EmpRec('Bob Zimmer', 'finance', salary = 77123) tim = EmpRec('Tim Bauer', 'shipping', 34231)
Jiný způsob s použitím seznamu a metody _make:
fred_list = ['Fred Flint', 'purchasing', 42350] fred = EmpRec._make(fred_list)
Jiný způsob s použitím slovníku a dvojhvězdičkového operátoru:
fred_dict = {'name': 'Fred Flint', 'department': 'purchasing', 'salary': 42350} fred = EmpRec(**fred_dict) --> EmpRec{'name': 'Fred Flint', 'department': 'purchasing', 'salary': 42350}
Vytvoření entice z existující entice metodou _replace:
john = fred._replace(name='John Ward', salary=49200) Vytvoření implicitní entice pro dělníky s hodinovou mzdou default = EmpRec('addname', 'manufacturing', 26000) a její aplikace pro tvorbu konkretních entic mike = default._replace(name='Mike Holz') gary = default._replace(name='Gary Wood') carl = default._replace(name='Carl Boor')
Převedení entice na slovník metodou _asdict():
print(bob._asdict())
Výpis polí entice přes název entice či vzorové entice:
print(bob._fields) print(EmpRec._fields)
=== RESTART: F:\Codetest\HowTo\ch-10\in-text\ntEmpRec.py ===
{'name': 'Bob Zimmer', 'department': 'finance', 'salary': 77123}
('name', 'department', 'salary')
('name', 'department', 'salary')

Přístup k polím přes jméno entice a atributu
>>> print(bob.name, bob.salary)
Bob Zimmer 77123

Přístup k polím přes jméno entice a index atributu
>>> print(tim[0], tim[2])
Tim Bauer 34231

Vytvoření seznamu pojmenovaných entic:

# ntEmpRec.py selektivně upravené
...
print('-'*40)
emp_list = [bob, fred, tim, john, mike, gary, carl]
for emp in emp_list:
    print( "%-15s works in %s" % (emp.name, emp.department) )
print('-'*40)
== RESTART: F:/Codetest/HowTo/ch-10/in-text/listEmpRec.py ==
----------------------------------------
Bob Zimmer      works in finance
Fred Flint      works in purchasing
Tim Bauer       works in shipping
John Ward       works in purchasing
Mike Holz       works in manufacturing
Gary Wood       works in manufacturing
Carl Boor       works in manufacturing
----------------------------------------

Pro výpis polí používá Python interně metodu split():

>>> "name department salary".split()
['name', 'department', 'salary']

Objekt EmpRec lze tedy alternativně deklarovat takto:

>>> EmpRec = namedtuple('EmpRec', ['name', 'department', 'salary'])
Případně:
>>> EmpRec = namedtuple('EmpRec', ('name department salary'))
>>> type(EmpRec)
<class 'type'>                    # vida, ona je to třída

Podporované metody

Kromě prezentovaných metod _replace(), _make(), _asdict(), _fields podporuje named tuple stejné metody jako regulérní typ tuple - min(), max(), len(), in, not in, concatenation, index, slice, atd.

Aktuálně platné parametry funkce namedtuple - rename, defaults a module viz namedtuple() v anglické dokumentaci.


10.4 Hromadné přiřazení

Hromadné přiřazení (multiple assignment) umožňuje přiřadit několika proměnným příslušné hodnoty na jediném řádku.

Víme již, že entici bez závorek akceptuje interpret jako entici se závorkami:

>>> x, y = 10, 20        # --> (x, y) = (10, 20)
>>> x, y
(10, 20)

Popsaný způsob přiřazení lze použít nejen u entic ale i u dalších 'iteráblů' - slovníků [], setů {} a řetězců " ".

>>> a, b = [12, 22]
>>> c, d = {14, 24}
>>> e, f = "Hi"
>>> a,b, c,d, e,f
(12, 22, 24, 14, 'H', 'i')

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

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

Při rozbalování entice lze použít pomocnou proměnnou:

>>> ent = "m", 2, False           
>>> x,y,z = ent
>>> z,y,x
(False, 2, 'm')
Počet prvků po obou stranách přiřazení musí být shodný.  

10.5 Entice jako výstupní hodnoty

Funkce mohou vracet entici jako výstupní hodnotu. Můžeme například napsat funkci, která zamění dva parametry:

def swap(x, y): 
    return y, x

Při používání tété funkce je zapotřebí jisté ostražitosti:

>>> a, b = 10,5
>>> swap(a, b) 
(5,10)                 # dostali jsme co jsme chtěli
>>> a,b
(10,5)                 # vstupní hodnoty jsme ale nezměnili
>>> a,b = swap(a, b)   # musíme o to výslovně požádat
>>> a,b
(5,10)

Enticové přiřazení uvnitř funce swap se v prostoru __main__ neprojeví.

Tato funkce případně neprovede to, co jsme si přáli. To je příklad sémantické (významové) chyby.

Přehlednější ukázkou použití entice pro výstupní hodnoty funkce je tento příklad:

from math import pi
def kruh(r):
    c = 2 * pi * r          # obvod kruhu
    a = pi * r * r          # plocha kruhu
    return (r, c, a)

10.6 Čisté funkce a modifikátory III

V kapitole 3.5 a 3.7 jsme si povídali o čistých funkcích a modifikátorech v souvislosti se seznamy. Pro entice modifikátory psát nemůžeme, protože entice jsou samy o sobě neměnitelné.

Zde máme modifikátor, který vloží novou hodnotu val do středu seznamu lst. Text kódu si uložíme do souboru pureTriTools.py a spustíme si ho v IDLE, abychom viděli, jak chodí:

# pureTriTools.py

def insert_in_middle_lst(val, lst):
    middle = int(len(lst)/2)
    lst[middle:middle] = [val]

>>> my_list = ['a', 'b', 'd', 'e']
>>> insert_in_middle_lst('c', my_list)
>>> my_list
['a', 'b', 'c', 'd', 'e']

Zkusíme-li to s enticí, dostaneme chybu. Problém je ten, že entice jsou neměnitelné a nepodporují úsekové přiřazení. Jednoduchým řešením je udělat z insert_in_middle_lst čistou funkci:

def insert_in_middle_tup(val, tup):
    middle = int(len(tup)/2)
    return tup[:middle] + (val,) + tup[middle:]

Tato verze nyní chodí pro entice, ne však pro seznamy a řetězce.

>>> my_tuple = ('a', 'b', 'd', 'e')
>>> insert_in_middle_tup('c', my_tuple)
('a', 'b', 'c', 'd', 'e')

Chceme-li verzi pro všechny sekvenční typy, potřebujeme zapouzdřit naši hodnotu do správného sekvenčního typu. Malá pomocná funkce vykoná div:

def encapsulate(val, seq):
    if type(seq) == type(""):
        return str(val)
    if type(seq) == type([]):
        return [val]
    return (val,)

Nyní můžeme napsat insert_in_middle tak, aby pracovala s každým vestavěným typem sekvence:

def insert_in_middle(val, seq):
    middle = int(len(seq)/2)
    return seq[:middle] + encapsulate(val, seq) + seq[middle:]

Poslední dvě verze insert_in_middle jsou čisté funkce. Nemají žádné postranní účinky. Přidáme-li encapsulate a poslední verzi insert_in_middle do modulu pureTriTools.py, můžeme to vyzkoušet:

>>> from pureTriTools import *

>>> my_string = 'abde'
>>> insert_in_middle('c', my_string)
'abcde'

>>> my_list = ['a', 'b', 'd', 'e']
>>> insert_in_middle('c', my_list)
['a', 'b', 'c', 'd', 'e']

>>> my_tuple = ('a', 'b', 'd', 'e')
>>> insert_in_middle('c', my_tuple)
('a', 'b', 'c', 'd', 'e')

>>> my_string
'abde'

Hodnoty my_string, my_list, a my_tuple se nezměnily.


10.7 Set a frozenset

Set (množina) je neuspořádaná měnitelná kolekce odlišných prvků, frozenset je neměnitelná kolekce odlišných prvků. Používají se při testování "členství" a při eliminaci duplikátních zápisů.

Kolekce set a frozenset podporují množinové operace sjednocení (union), průnik (intersection), rozdíl (difference) a doplněk (symmetric_difference), funkci len(s) a idiomy x in s, x not in s.

Set vytvoříme buď výčtem nebo pomocí funkce set(), frozenset vytvoříme pouze funkcí frozenset(). Obě funkce přijímají pouze jeden argument, jímž může být seznam, řetězec, entice, částečně i slovník. Pozice jednotlivých prvků není indexována.

>>> basket = {'apple','orange','apple','pear','orange','banana'}
>>> basket
{'orange', 'apple', 'banana', 'pear'}      # žádné duplikáty!
>>> tup = (1,2,3,"alpha",2)
>>> st = set(tup)
>>> st
{1, 2, 3, 'alpha'}                         # žádné duplikáty!
>>> fr = frozenset('alcatraz')
>>> fr 
frozenset({'a', 'c', 'l', 'r', 't', 'z'})  # žádné duplikáty!

Použijeme-li jako argument pro funkce set a frozenset slovník, použijí se pouze hodnoty jeho klíčů:

>>> dic = {"a": 1, "b": 2, "c": 3}
>>> set_dic = set(dic)
>>> set_dic
{'b', 'c', 'a'}

Set i frozenset mají definovanou částečně společnou řadu metod:

set_meth = add, clear, copy, difference, difference_update, 
discard, intersection, intersection_update, isdisjoint, issubset, 
issuperset, pop, remove, symmetric_difference,  union, update,
symmetric_difference_update

frozenset_meth = copy, difference, intersection, isdisjoint, 
issubset, issuperset, symmetric_difference,  union 

Objekt typu set nepodporuje změnu položky. Obsah setu lze nicméně měnit metodami add, clear, discard, pop, remove a update. Set i frozenset jsou rovněž vybaveny funkcí next(~) a metodou .__next__(), jsou to tedy iterábly.

Ukážeme si některé operace na setech:

>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a, b                 # zápis entice
({'a', 'c', 'b', 'r', 'd'}, {'a', 'c', 'z', 'm', 'l'})
>>> a - b                # rozdíl
{'r', 'd', 'b'}          # b-a --> {'z', 'm', 'l'}
>>> a | b                # sjednocení
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
>>> a & b                # průnik
{'a', 'c'}
>>> a ^ b                # doplněk
{'r', 'd', 'b', 'm', 'z', 'l'}

Stejně jako pro seznam lze i pro set definovat komprehenci setu:

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

Zápisy (), [], {} označují práznou entici, seznam a slovník; zápisy set() a frozenset() (nikoliv {}) označují prázdný set a frozenset.


10.8 Funkce zip

Funkce zip(*iterables) přijímá jednu či více sekvencí a kombinuje jejich položky do řady entic uvnitř interního iterátorového objektu.
Tento výsledný seznam či entice je iterátorem, jenž je po použití prázdný a před případným opakovaným použitím se musí opětovně deklarovat.

>>> izip = zip([1, 2, 3, 4], "postel")    #  sekvencí může být i více
>>> izip
<zip object at 0x000002194D5E0B40>        # interní iterátorový objekt

>>> list(izip)                                #  použití iterátoru
[(1, 'p'), (2, 'o'), (3, 's'), (4, 't')]
>>> set(izip)                                 #  iterátor je prázdný!
set()                                        
>>> set(zip([1, 2, 3, 4], "postel"))          #  použití s deklarací               
{(4, 't'), (3, 's'), (2, 'o'), (1, 'p')}
>>> tuple(zip([1, 2, 3, 4], "postel"))        #  použití s deklarací
((1, 'p'), (2, 'o'), (3, 's'), (4, 't'))
>>> dict(zip([1, 2, 3, 4], "postel"))         #  použití s deklarací
{1: 'p', 2: 'o', 3: 's', 4: 't'}

Počet entic ve výsledném iterátoru je dán délkou nejkratšího argumentu funkce zip. Zazipované sekvence lze opět odzipovat:

nl = [1, 2, 3]
sl = ['one', 'two']
nt = ('ONE', 'TWO', 'THREE', 'FOUR')

izip = zip(nl, sl, nt)
result_list = (list(izip))
print("result_list =",result_list)

nl, sl, nt = zip(*result_list)                  #  sběrný parametr

print("nl =",nl, "sl =",sl, "nt =",nt)

========== RESTART: F:/Codetest/python/zip_test.py =========
result_list = [(1, 'one', 'ONE'), (2, 'two', 'TWO')]
nl = (1, 2) sl = ('one', 'two') nt = ('ONE', 'TWO') 

Proměnné   nl, sl, nt  změnily trvale svou hodnotu.


10.9 Aplikace SeqTools

SeqTools je sada knihoven pro manipulaci se sekvenčními daty typu list, tuple, range, bytes a bytearray . Velice pěkné seznámení s tímto nástrojem nalezneme na stránce SeqTools.
Zde se seznámíme s řadou metod, které lze s příslušným importem využívat.

Předtím je nutné si tento nástroj instalovat:

pip install seqtools          Jak prosté, milý Watsone!

Oprašte svou angličtinu a naučte se pracovat s nástrojem SeqTools.


10.10 Glosář

entice (tuple)
Typ s uspořádanými položkami jako u seznamu, zde však jsou položky neměnitelné. Entice mohou být použity všude tam, kde je požadován neměnitelný typ, jako v klíči u slovníku.
enticové přiřazení (tuple assignment)
Přiřazení všem elementům entice jediným příkazem k přiřazení. Enticové přiřazení se realizuje paralelně, takže je vhodné pro záměnu hodnot.
set
Set je neuspořádaná měnitelná kolekce nestejných prvků s neměnitelnou hodnou.

10.11 Cvičení

  1. Generujte náhodné číslo mezi low a high.
  2. Generujte náhodné celé číslo mezi low a high včetně.
  3. Předpokládá se, že čísla, generovaná systémovou funkcí random jsou rovnoměrně rozdělena, to znamená, že výskyt každé hodnoty má stejnou pravděpodobnost.
    Zapište upravenou funkci countB1(n,b) z konce odstavce 9.9 do souboru histogram.py a vyzkoušejte volání pro n = 2000, 4000, 8000, b = 8. Ověřte, zda četnosti spějí k rovnoměrnénu výskytu.
  4. Do stejného souboru připište upravenou funkci countB2(n,b) z konce odstavce 9.10. Volejte ji pro stejné hodnoty jako v předchozí úloze.
  5. Vytvořte soubor lists_one.py (kap. 8), vložte do něj následující doctesty a doplňte vhodnými definicemi funkcí:
    def make_empty(seq):
        """
        >>> make_empty([1, 2, 3, 4])
        []
        >>> make_empty(('a', 'b', 'c'))
        ()
        >>> make_empty("No, not me!")
        ''
        """
    
    def insert_at_end(val, seq):
        """
        >>> insert_at_end(5, [1, 3, 4, 6])
        [1, 3, 4, 6, 5]
        >>> insert_at_end('x', 'abc')
        'abcx'
        >>> insert_at_end(5, (1, 3, 4, 6))
        (1, 3, 4, 6, 5)
        """
    
    def insert_in_front(val, seq):
        """
        >>> insert_in_front(5, [1, 3, 4, 6])
        [5, 1, 3, 4, 6]
        >>> insert_in_front(5, (1, 3, 4, 6))
        (5, 1, 3, 4, 6)
        >>> insert_in_front('x', 'abc')
        'xabc'
        """
    
    def index_of(val, seq, start=0): """ >>> index_of(9, [1, 7, 11, 9, 10]) 3 >>> index_of(5, (1, 2, 4, 5, 6, 10, 5, 5)) 3 >>> index_of(5, (1, 2, 4, 5, 6, 10, 5, 5), 4) 6 >>> index_of('y', 'happy birthday') 4 >>> index_of('banana', ['apple', 'banana', 'cherry', 'date']) 1 >>> index_of(5, [2, 3, 4]) -1 >>> index_of('b', ['apple', 'banana', 'cherry', 'date']) -1 """
    def remove_at(index, seq): """ >>> remove_at(3, [1, 7, 11, 9, 10]) [1, 7, 11, 10] >>> remove_at(5, (1, 4, 6, 7, 0, 9, 3, 5)) (1, 4, 6, 7, 0, 3, 5) >>> remove_at(2, "Yomrktown") 'Yorktown' """
    def remove_val(val, seq): """ >>> remove_val(11, [1, 7, 11, 9, 10]) [1, 7, 9, 10] >>> remove_val(15, (1, 15, 11, 4, 9)) (1, 11, 4, 9) >>> remove_val('what', ('who', 'what', 'when', 'where', 'why', 'how')) ('who', 'when', 'where', 'why', 'how') """
    def remove_all(val, seq): """ >>> remove_all(11, [1, 7, 11, 9, 11, 10, 2, 11]) [1, 7, 9, 10, 2] >>> remove_all('i', 'Mississippi') 'Msssspp' """ def count(val, seq): """ >>> count(5, (1, 5, 3, 7, 5, 8, 5)) 3 >>> count('s', 'Mississippi') 4 >>> count((1, 2), [1, 5, (1, 2), 7, (1, 2), 8, 5]) 2 """ def reverse(seq): """ >>> reverse([1, 2, 3, 4, 5]) [5, 4, 3, 2, 1] >>> reverse(('shoe', 'my', 'buckle', 2, 1)) (1, 2, 'buckle', 'my', 'shoe') >>> reverse('Python') 'nohtyP' """ def sort_sequence(seq): """ >>> sort_sequence([3, 4, 6, 7, 8, 2]) [2, 3, 4, 6, 7, 8] >>> sort_sequence((3, 4, 6, 7, 8, 2)) (2, 3, 4, 6, 7, 8) >>> sort_sequence("nothappy") 'ahnoppty' """ if __name__ == "__main__": import doctest doctest.testmod()
  6. Odjíždíte na báječnou dovolenou ve středu, to je ve 3. dnu v týdnu. Vrátíte se po 137 nocích. Napište program, který se zeptá na pořadové číslo dne vašeho odjezdu, délku pobytu a vrátí vám název dne, ve kterém se vrátíte. Bude to neděle?
    Použijete vstup z klávesnice, operátor modulo, den návratu vyberete indexem.
    Případně můžete napsat funkci 'návrat(odjezd, délka)'.

previous up next hi end end