pre up next title end end

10. Entice a sety

  1. Vytvoření entice
  2. Složené entice
  3. Pojmenované entice
  4. Entice jako výstupní hodnoty
  5. Funkce čisté a nečisté II
  6. Set a frozenset
  7. Glosář
  8. Cvičení

10.1 Vytvoření entice

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

>>> tup = "a", 52, 3.8, [2, 8]

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

>>> tup
('a', 52, 3.8, [2, 8])

Formát entice má i výraz s jedinou položkou se závěrečnou čárkou:

>>> t1 = 'a',            
>>> t1; type(t1)
('a',)                      # závorky si doplnil interpret
<class 'tuple'>

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

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

>>> tup = "a", 52, 3.8, [2, 8]
>>> tuple[0]
'a'

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

>>> tup[1:3]
(52, 3.8, [2, 8])

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

>>> tup[0] = 'A'
TypeError: 'tup' 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',) + tup[1:]
>>> tuple
('A', 52, 3.8, [2, 8])

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

10.2 Složené entice

Entice je neměnitelná kolekce (neměnitelných) prvků. Pokud však entice obsahuje měnitelný element typu list a dict, lze tento prvek změnit obvyklým způsobem a kopírovat metodou copy a deepcopy z modulu copy:

# metoda copy nemění identitu ani u změněného objektu:
>>> import copy
>>> lt = ("a", [2.5, "b"])            # vložený seznam
>>> slt = copy.copy(lt)               # závislá kopie 
>>> id(lt), id(slt)               --> # (3027328, 3027328)
>>> slt[1][1] = "joj"; slt
('a', [2.5, 'joj'])                   # změněná entice 
>>> id(lt), id(slt)               --> # (3027328, 3027328)

# metoda deep copy mění identitu i u změněného objektu: 
>>> lt = ("a", [2.5, "b"])
>>> dlt = copy.deepcopy(lt)           # nezávislá kopie 
>>> id(lt), id(dlt)               --> # (3027328, 3039104)
>>> dlt[1][1] = "hoj"; dlt
('a', [2.5, 'hoj'])
>>> id(lt), id(dlt)               --> # (3027328, 3039104)

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 kopclr-ii 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 entice, opatřená jménem a názvy položek. Prvky entice jsou tedy kromě indexů přístupné také přes jejich 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é produkční (factory) 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í')
EmpRec = namedtuple('EmpRec', 'name, department, salary')
 
# Vytvoření pojmenovaných entic:    
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)
>>> fred

# 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)
Přerušeno, aby bylo vidět na posuvník. Text skriptu však nepřerušovat.
# 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\python\ntEmpRec.py ====

Invokace >>> označených řádků:
>>> fred
EmpRec(name='Fred Flint', department='purchasing', salary=42350)
>>> print(bob._asdict())
{'name': 'Bob Zimmer', 'department': 'finance', 'salary': 77123}
>>> print(bob._fields)
('name', 'department', 'salary')
>>> print(EmpRec._fields)
('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. Předchozí skript doplníme o následující kód a uložíme jako listEmpRec.py.

# ntEmpRec.py částečně upravené na listEmpRec.py
...
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, jak od počátku tvrzeno

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.
Pěkně je také named tuple popsán v Real Python.


10.4 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.5 Funkce čisté a nečisté II

V kapitole 4.3 jsme si povídali o čistých funkcích a modifikátorech v souvislosti se seznamy.

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í změnu 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.6 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 jeho prvků 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 (object is not subscriptable).

>>> basket = {'apple','orange','apple','pear','orange','banana'}
>>> basket
{'orange', 'apple', 'banana', 'pear'}           # žádné duplikáty!
>>> song = set("Okolo Hradce"); song
{'o', 'O', 'c', 'd', 'l', 'e', 'a', 'k', ' ', 'r', 'H'}
>>> tup = (1,2,3,"alpha",2)
>>> st = set(tup); st
{1, 2, 3, 'alpha'}                             
>>> fr = frozenset('alcatraz')
>>> fr 
frozenset({'a', 'c', 'l', 'r', 't', 'z'})       

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'}

Šikovná je redukce hromady čísel na jedinečnou řadu a posléze přeměna na posloupnost čísel:

>>> e = (1,2,3,2,1,2,3,4,5,6,5,6,4,7,8,3,2,1,3,4,5,6)
>>> l = list(set(e)); l
[1, 2, 3, 4, 5, 6, 7, 8]

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.7 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.
factory
Adjektiv pro označení objektů (funcí, metod), určených k vytváření dalších objektů - viz 10.3.
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.8 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)'.

pre up next title end end