... ale báli jste se zeptat na hodině ;)

Objekty

Programování v Pythonu spočívá v manipulaci s různými objekty -- čísli, řetězci (písmen), seznamy, funkcemi... Zastřešující pojem "objekt" naznačuje analogii s fyzickými předměty. Každý předmět slouží k něčemu jinému, něco s ním lze udělat jednoduše, něco hůř, něco vůbec; taky případně může obsahovat další předměty. Podobně v Pythonu: čísla se hodí na sčítání, ale nemůžeme změřit jejich délku; a seznam jakožto objekt je vlastně kolekcí dalších objektů, jeho prvků.

Když Python spustíte, načte se poměrně velké množství zabudovaných objektů (funkcí a konstant), se kterými můžete rovnou začít pracovat. Např. funkce sorted vytvoří z libovolného iterovatelného objektu (= objektu, ze kterého lze jeden po druhém tahat nějaké jeho dílčí prvky) seřazený seznam:

In [2]:
# cokoli za znakem # na jakémkoli řádku Python ignoruje,
# jsou to tzv. komentáře, které slouží jen lidem
sorted
Out[2]:
<function sorted(iterable, /, *, key=None, reverse=False)>

(Seznam všech zabudovaných funkcí -- built-in functions -- v Pythonu)

Konstanta True zas označuje "pravdu" (v logickém smyslu)...

In [3]:
True
Out[3]:
True

... takže ji Python vrátí jako výslednou hodnotu při vyhodnocení pravdivého výroku:

In [4]:
2 + 2 == 4
Out[4]:
True

Ale další objekty si můžeme libovolně vytvářet.

In [5]:
# čísla
42
Out[5]:
42
In [6]:
# řetězce
"Hello, world!"
Out[6]:
'Hello, world!'

Proměnné

Abychom s objekty mohli dál pracovat, je potřeba je nějak pojmenovat, jinak na ně nemůžeme odkazovat. Štítek se jménem můžeme na objekt pověsit pomocí přiřazovacího operátoru =.

In [7]:
vzdálenost = 10
In [8]:
čas = 20
In [9]:
rychlost = vzdálenost / čas
In [10]:
rychlost
Out[10]:
0.5

Těm štítkům se jmény se říká proměnné, protože je lze libovolně kdykoli pověsit na jiný objekt.

In [11]:
vzdálenost = 5
In [12]:
rychlost = vzdálenost / čas
In [13]:
rychlost
Out[13]:
0.25

Jsou i jiné způsoby, jak pověsit štítek se jménem na nějaký objekt. Např. když teď vytvoříme funkci a pojmenujeme ji rychlost, tak štítek rychlost strhneme z čísla 0.25, na které jsme ho naposledy navěsili, a nalepíme ho na tu funkci (která je z hlediska Pythonu zase jen dalším typem objektu, jako čísla, řetězce atp.).

In [14]:
def rychlost(vzdálenost, čas):
    return vzdálenost / čas
In [15]:
rychlost
Out[15]:
<function __main__.rychlost(vzdálenost, čas)>

Nyní tedy už proměnná rychlost neodkazuje na číslo 0.25, ale na funkci, kterou jsme právě vytvořili.

Funkci můžeme zavolat, tj. spustit "recept" (sérii kroků, malý dílčí program), který je v ní uložený:

In [16]:
rychlost(vzdálenost, čas)
Out[16]:
0.25

Nic nebrání tomu, abychom na existující objekty navěsili štítků víc, a můžeme k tomu použít stávající štítky:

In [17]:
distance = vzdálenost
time = čas
speed = rychlost
In [18]:
speed(distance, time)
Out[18]:
0.25

Jak to funguje? Python nejprve vyhodnotí výraz napravo od operátoru =, tj. v tomto případě jen dohledá objekty, na které odkazují původní proměnné, a pak na ty samé objekty navěsí další nové štítky (uvedené nalevo od operátoru =).

Všimněte si, že jsme tímto způsobem jednoduše přiřadili další jméno i funkci rychlost:

In [19]:
speed
Out[19]:
<function __main__.rychlost(vzdálenost, čas)>

To, zda dvě jména (dvě proměnné) odkazují na ten samý objekt, jednoduše zjistíme pomocí operátoru is:

In [20]:
distance is vzdálenost
Out[20]:
True
In [21]:
distance is čas
Out[21]:
False
In [22]:
distance = čas
In [23]:
distance is čas
Out[23]:
True
In [24]:
speed is rychlost
Out[24]:
True

POZN.: Rovnost (x == y) a identita (x is y) jsou dvě různé věci. Dva různé objekty (z hlediska identity) jsou si rovné, pokud reprezentují tu samou hodnotu. Např. dva různé seznamy čísel od jedné do tří:

In [25]:
x = [1, 2, 3]
y = [1, 2, 3]
In [26]:
x == y
Out[26]:
True
In [27]:
x is y
Out[27]:
False

Je to podobně jako s jednovaječnými dvojčaty: vypadají stejně, takže třeba pro účel focení jsou zaměnitelná, ale to neznamená, že je to ten samý člověk.

Ještě pár slov ke konvencím pojmenovávání proměnných v Pythonu:

  • u drtivé většiny proměnných (vč. funkcí) je zvykem používat jen malá písmena, např. proměnná
  • občas se používají i číslice, např. x1, ale jméno proměnné číslicí nesmí začínat
  • když pojmenování sestává z více slov, oddělují se pomocí podtržítka, např. víceslovná_pojmenování (tzv. snake case)
  • proměnné, které se používají napříč celým programem (často je definujeme na začátku programu a pak na ně odkazujeme v růzých jeho částech), se někdy odlišují pomocí velkých písmen, např. ABECEDA nebo RYCHLOST_SVĚTLA
  • jména tříd (viz níže) jsou většinou v tzv. camel case s velkým počátečním písmenem, např. Třída či JináTřída

Jméno proměnné by nemělo být moc dlouhé, ale zároveň by mělo být deskriptivní -- mělo by srozumitelně naznačovat, k čemu proměnná slouží, jak se používá. Hezky a trefně pojmenovat proměnnou je často těžší, než by se na první pohled mohlo zdát. Když píšete program na jedno použití, je to jedno, ale pokud po sobě budete program ještě někdy číst (nebo někdo jiný), budete za každou smysluplně pojmenovanou proměnnou (a každý vhodně umístěný komentář) vděční.

Typy a třídy

Každý objekt v Pythonu spadá do nějaké kategorie objektů -- např. číslo, řetězec, seznam apod. -- podobně jako předměty v reálném světě spadají do různých kategorií (všechny kočky do kategorie "kočka", všechny tužky do kategorie "tužka" apod.). Těmto kategoriím s v Pythonu říká typy a u libovolného objektu lze zjistit jeho typ pomocí funkce type:

In [28]:
obj = 1
type(obj)
Out[28]:
int
In [29]:
obj = "ahoj"
type(obj)
Out[29]:
str
In [30]:
obj = [1, "ahoj"]
type(obj)
Out[30]:
list
In [31]:
type(1) == type(2)
Out[31]:
True
In [32]:
type("a") == type("b")
Out[32]:
True
In [33]:
# ovšem pozor
type("1")
Out[33]:
str
In [34]:
type(1) == type("1")
Out[34]:
False

Typy jako int, str nebo list jsou v Pythonu přímo zabudované, jsou k dispozici vždycky. Ale každý programátor si může definovat vlastní typy a dále s nimi pracovat. Těmto uživatelsky definovaným typům se říká třídy, podle klíčového slovíčka class, pomocí něhož se definují:

In [35]:
class FooBar:
    # klíčové slovíčko pass je prázdný příkaz (Python na jeho základě nic
    # nevykoná)
    pass

Instanci třídy, tj. konkrétní objekt, jehož typem bude daná třída, vytvoříme tak, že zavoláme tzv. konstruktor, tj. funkci, která nám instanci "postaví", "zkonstruuje". Počáteční sestavení a nastavení objektu někdy nazýváme inicializací. Jako konstruktor většinou slouží třída samotná, kterou zavoláme jako funkci:

In [36]:
obj = FooBar()
type(obj)
Out[36]:
__main__.FooBar
In [37]:
type(obj) is FooBar
Out[37]:
True

Oproti tomu instance zabudovaných typů můžeme vytvářet i pomocí speciálních zápisů, tzv. literálů, nepotřebujeme k tomu nutně funkce. V konstruktoru je proces vytváření objektu svým způsobem skrytý, není na první pohled jasné, co se v něm odehrává (obecně může konstruktorem být libovolná funkce), kdežto v literálu doslova (angl. literally) prostě jen vypíšeme, jak má nějaký objekt vypadat, a Python nám ho podle toho poskládá:

In [38]:
# číselný literál
42
Out[38]:
42
In [39]:
# řetězcový literál
"Hello, world!"
Out[39]:
'Hello, world!'

Literály jsou součástí syntaxe Pythonu, tj. pravidel pro skládání programů tak, aby jim Python rozuměl a mohl je vykonávat. Důležité jsou ještě literály pro vytváření kolekcí (viz odd. Kolekce).

Chceme-li ověřit, zda je nějaký objekt instancí nějaké dané třídy či obecněji typu, můžeme použít funkci isinstance.

In [40]:
isinstance(obj, FooBar)
Out[40]:
True
In [41]:
isinstance(1, int)
Out[41]:
True

Třídy se hodí pro některé pokročilejší programátorské účely (viz např. notebook object_oriented.ipynb). V tomto semestru se jim blíže nevěnujeme.

Importování knihoven

Knihovna (též balíček, angl. package) je v programování soubor funkcí, tříd atp., které jsou často užitečné, takže nemá smysl, aby si je každý programátor implementoval znovu a znovu, když je potřebuje. Zároveň ale nejsou potřeba úplně pořád, takže taky nemá smysl, aby byly do Pythonu přímo zabudované (tj. aby byly k dispozici pokaždé, když Python spustíme, podobně třeba jako funkce sorted). Vždy jsou ale na dosah ruky, stačí je importovat.

Např. knihovna random obsahuje funkcionalitu související s generováním náhodných čísel:

In [42]:
import random

# následujícímu příkazu není potřeba věnovat přílišnou
# pozornost, jen zajistí, abychom pokaždé dostali stejnou
# sérii pseudonáhodných čísel; viz:
# https://docs.python.org/3/library/random.html#random.seed
random.seed(42)

K objektům, které knihovna definuje, se pak dostaneme přes tečku. Např. pokud chceme pomocí funkce randint vygenerovat náhodné číslo mezi jednou a deseti:

In [43]:
random.randint(1, 10)
Out[43]:
2

Při importu si lze knihovnu přejmenovat pomocí klíčového slovíčka as, např. pokud jméno random už v programu používáme pro něco jiného.

In [44]:
random = "random. angl. náhodný"

import random as rnd

rnd.randint(1, 10), random
Out[44]:
(1, 'random. angl. náhodný')

Taky je možné si z knihovny naimportovat přímo jeden konkrétní objekt (funkci, třídu atp.):

In [45]:
from random import randint

randint(1, 10)
Out[45]:
5

To se hodí zejm. v případě, kdy objekt budeme používat často a nechce se nám pokaždé zdlouhavě psát jméno_knihovny.jméno_objektu. Pokud chceme, můžeme si jich rovnou naimportovat i víc zároveň:

In [46]:
from random import random, randint

random(), randint(1, 10)
Out[46]:
(0.24489185380347622, 3)

Větší knihovny sestávají z dílčích modulů, které při importu oddělujeme tečkami.

In [47]:
import os.path

this_file = "python_crash_course.ipynb"

os.path.isfile(this_file)
Out[47]:
True
In [48]:
import os.path as osp

osp.realpath(this_file)
Out[48]:
'/home/david/src/dlukes.github.io/content/notebooks/python_crash_course.ipynb'
In [49]:
from os.path import splitext

splitext(this_file)
Out[49]:
('python_crash_course', '.ipynb')

Funkce

Funkce je jako recept -- série příkazů, malý dílčí program, který má jméno, pomocí něhož ho můžeme kdykoli spusit, tzv. zavolat. Funkci definujeme pomocí klíčového slovíčka def...

In [50]:
def hoď_kostkou():        # hlavička
    return randint(1, 6)  # tělo

... a voláme tak, že napíšeme její jméno a za ně kulaté závorky:

In [51]:
hoď_kostkou()
Out[51]:
6

Recept uložený ve funkci se vykonává krok po kroku (řádek po řádku), dokud Python nenarazí na klíčové slovíčko return. V tu chvíli funkci ukončí a hodnotu výrazu, který se nachází za return, vrátí jako výsledek celé funkce. Tento výsledek můžeme uložit do proměnné:

In [52]:
výsledek_hodu_kostkou = hoď_kostkou()
In [53]:
výsledek_hodu_kostkou
Out[53]:
1

Do těla funkce patří vše, co následuje po hlavičce a je odsazené alespoň o jednu úroveň dál než samotná hlavička:

In [54]:
def function():
    print("Tohle patří do těla funkce.")
    print("Tohle taky.")

print("Tohle už ne.")
Tohle už ne.
In [55]:
function()
Tohle patří do těla funkce.
Tohle taky.

Funkce často mají jeden nebo více parametrů či argumentů, tj. proměnných, které deklarujeme v hlavičce funkce. Např. následující funkce má parametry počet_stěn a násobek.

In [56]:
def hoď_kostkou_a_vynásob(počet_stěn, násobek):
    return randint(1, počet_stěn) * násobek

Když funkci voláme, je potřeba za parametry dosadit konkrétní objekty, které funkce pak v rámci receptu použije:

In [57]:
hoď_kostkou_a_vynásob(20, 100)
Out[57]:
1800

POZN.: Přísně vzato bychom měli takto rozlišovat mezi (formálními) parametry (= proměnnými, se kterými pracujeme při definici funkce a které v tu chvíli nereprezentují žádnou konkrétní hodnotu) a (konkrétnimi) argumenty (= konkrétními objekty, které funkci předáme ve chvíli, kdy ji voláme). Nicméně často tento rozdíl není příliš důležitý, takže v praxi se oba termíny používají zaměnitelně.

Parametry mohou mít defaultní hodnotu:

In [58]:
def hoď_kostkou_a_vynásob(počet_stěn, násobek=100):
    return randint(1, počet_stěn) * násobek

Pokud nám defaultní hodnota daného parametru vyhovuje, nemusíme ho pak při volání specifikovat:

In [59]:
hoď_kostkou_a_vynásob(20)
Out[59]:
300

Pokud nám naopak nevyhouje, nic nám v tom nebrání:

In [60]:
hoď_kostkou_a_vynásob(20, 0.01)
Out[60]:
0.19

Parametrům bez defaultní hodnoty se říká povinné (je potřeba je specifikovat vždy), parametrům s defaultní hodnotou pak volitelné.

Parametry můžeme funkci předávat buď na základě pořadí (tzv. pozičně)...

In [61]:
# počet_stěn = 4, násobek = 10
hoď_kostkou_a_vynásob(4, 10)
Out[61]:
40
In [62]:
# počet_stěn = 10, násobek = 4
hoď_kostkou_a_vynásob(10, 4)
Out[62]:
4

... nebo pomocí jejich jmen (pak hovoříme o pojmenovaných argumentech, angl. named nebo též keyword arguments):

In [63]:
hoď_kostkou_a_vynásob(počet_stěn=4, násobek=10)
Out[63]:
10
In [64]:
# v tomto případě na pořadí nezáleží
hoď_kostkou_a_vynásob(násobek=10, počet_stěn=4)
Out[64]:
10

Oba způsoby můžeme i kombinovat, dokonce je to velmi časté. V Pythonu je většinou zvykem, že se povinné parametry předávají pozičně (bývá jich málo a je potřeba je zadávat pokaždé, takže si člověk jejich pořadí zapamatuje) a volitelné parametry jsou pojmenované (může jich být mnoho a používají se příležitostně, takže si člověk jejich pořadí nezapamatuje; navíc často chceme specifikovat jen jeden a ostatním nechat defaultní hodnotu). V případě naší funkce by to vypadalo takto:

In [65]:
hoď_kostkou_a_vynásob(4, násobek=10)
Out[65]:
20

Lokální vs. globální proměnné

Parametry plus jakékoli další proměnné, které v rámci funkce vytvoříme, jsou soukromé, dostupné čistě jen funkci -- jsou to tzv. lokální proměnné. Též hovoříme o tom, že tyto proměnné mají lokální dosah (angl. local scope). To znamená, že s nimi můžeme pracovat pouze v rámci těla funkce.

In [66]:
def function(parameter):
    local_variable = 1
    print("My parameter:", parameter)
    print("My local variable:", local_variable)
In [67]:
function(0)
My parameter: 0
My local variable: 1

Mimo tělo funkce function nejsou proměnné parameter a local_variable definované:

In [68]:
parameter
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-68-cfbbb8ed4864> in <module>
----> 1 parameter

NameError: name 'parameter' is not defined
In [69]:
local_variable
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-69-ffb5b39c1381> in <module>
----> 1 local_variable

NameError: name 'local_variable' is not defined

TIP: Naučit se číst chybové hlášky je k nezaplacení, Python se vám s jejich pomocí snaží ze všech sil poradit, v čem je problém. Zkuste si každou chybovou hláškou pečlivě přečíst, zamyslet se nad ní, pochopit, v čem je problém a kde přesně nastal.

Funkce mohou taky pracovat s globálními proměnnými, tj. s proměnnými definovanými samostatně, mimo nějaké funkce či třídy.

In [70]:
global_variable = 2
In [71]:
def function(parameter):
    local_variable = 1
    print("My parameter:", parameter)
    print("My local variable:", local_variable)
    print("My global variable:", global_variable)
In [72]:
function(0)
My parameter: 0
My local variable: 1
My global variable: 2

Taková funkce se ale snadno může rozbít -- stačí dotyčnou globální proměnnou smazat...

In [73]:
del global_variable

... a když funkci příště zavoláme, tak zkolabuje:

In [74]:
function(0)
My parameter: 0
My local variable: 1
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-74-e85ea32b6f77> in <module>
----> 1 function(0)

<ipython-input-71-051e3887233a> in function(parameter)
      3     print("My parameter:", parameter)
      4     print("My local variable:", local_variable)
----> 5     print("My global variable:", global_variable)

NameError: name 'global_variable' is not defined

Snad ještě horší je to, že u takové funkce není možné jen na základě jejího volání odhadnout, co přesně udělá:

In [75]:
global_variable = 512
In [76]:
function(0)
My parameter: 0
My local variable: 1
My global variable: 512
In [77]:
global_variable = 1024
In [78]:
function(0)
My parameter: 0
My local variable: 1
My global variable: 1024

V obou případech funkci voláme jako function(0), ale výsledek je pokaždé trochu jiný.

Mnohem lepší je tedy psát funkce tak, aby používaly jen lokální proměnné a nebyly závislé na těch globálních. Nejde to vždycky, ale většinu času ano. Místo přímého odkazování na globální proměnné je lepší dát funkci více parametrů, přes něž jí můžeme globální proměnné zprostředkovaně předávat, což je mnohem bezpečnější a přehlednější.

In [79]:
def function(parameter1, parameter2):
    local_variable = 1
    print("My first parameter:", parameter1)
    print("My local variable:", local_variable)
    print("My other parameter:", parameter2)
In [80]:
global_variable = 2

function(0, global_variable)
My first parameter: 0
My local variable: 1
My other parameter: 2

Jaký je rozdíl v tom, když zavolám následující dvě funkce?

In [81]:
def funkce1():
    print(1)
In [82]:
def funkce2():
    return 1
In [83]:
funkce1()
1
In [84]:
funkce2()
Out[84]:
1

V obou případech se mi zobrazí číslo 1, v druhém je navíc vedle něj číslo v hranatých závorkách a dvojtečka. V čem se to liší?

Rozdíl se lépe projeví, když se pokusíme výsledek funkce uložit do proměnné:

In [85]:
výsledek1 = funkce1()
1
In [86]:
výsledek1
In [87]:
výsledek2 = funkce2()
In [88]:
výsledek2
Out[88]:
1

funkce1 číslo 1 jen v rámci svého běhu vytiskne na obrazovku, kdežto funkce2 ho vrátí jako svůj výsledek, takže ho můžeme uložit do proměnné a dál s ním pracovat. Možná pomůže, když se podíváme na funkci, která jedno čísle tiskne a jiné vrací:

In [89]:
def funkce3():
    print(1)
    return 2
In [90]:
výsledek3 = funkce3()
1
In [91]:
výsledek3
Out[91]:
2

Zbývá tedy jen otázka: co vrací funkce1? Nic, když vůbec ani neobsahuje klíčové slovíčko return? Svým způsobem ano, ale i nic je v Pythonu něco ;) Což zní krypticky, ale je to jednoduché: Python má speciální objekt (konstantu) None, která reprezentuje "nic". Protože None je nic, tak se vám po vyhodnocení ani neukáže:

In [92]:
None

A kdykoli nějaká funkce doběhne na konec svého receptu, aniž by potkala return, tak implicitně automaticky vrátí jako svůj výsledek None.

Vím, že je trochu záludné, že ono None není "vidět", ale v praxi si můžeme to, že funkce1 skutečně vrátila None, lehce ověřit:

In [93]:
výsledek1 is None
Out[93]:
True

Pokročilé způsoby předávání argumentů

V Pythonu lze také vytvořit funkce, které pracují s libovolným počtem pozičních nebo pojmenovaných argumentů. Slouží k tomu speciální operátory * a **:

In [94]:
def flexibilní_funkce(
    poziční_argument,
    *zbytek_pozičních_argumentů,
    pojmenovaný_argument,
    **zbytek_pojmenovaných_argumentů
):
    print("poziční argument:", poziční_argument)
    print("zbytek pozičních argumentů:", zbytek_pozičních_argumentů)
    print("pojmenovaný argument:", pojmenovaný_argument)
    print("zbytek pojmenovaných argumentů:", zbytek_pojmenovaných_argumentů)
In [95]:
flexibilní_funkce(1, 2, 3, 4, a=5, b=6, pojmenovaný_argument=7, c=8)
poziční argument: 1
zbytek pozičních argumentů: (2, 3, 4)
pojmenovaný argument: 7
zbytek pojmenovaných argumentů: {'a': 5, 'b': 6, 'c': 8}

Parametr *zbytek_pozičních_argumentů sesbírá zbylé poziční argumenty, sestaví z nich n-tici a tu uloží do proměnné zbytek_pozičních_argumentů; podobně parametr **zbytek_pojmenovaných_argumentů sesbírá zbylé pojmenované argumenty, sestaví z nich slovník a uloží ho do proměnné zbytek_pojmenovaných_argumentů (víc o n-ticích a slovnících viz odd. Kolekce).

Jak vidno, můžeme tyto speciální parametry pojmenovat libovolně, speciální chování jim propůjčuje operátor *, resp. **. Nicméně konvenčně se v Pythonu používají jména *args a **kwargs (jako keyword arguments).

A aby toho nebylo málo, funguje to i "naopak": máme-li seznam/n-tici (nebo slovník) a chceme jeho položky předat funkci jako poziční (nebo pojmenované) argumenty, můžeme taky využít operátoru * (nebo **):

In [96]:
def nudná_funkce(a, b):
    return a + b
In [97]:
poziční_argumenty = [1, 2]
In [98]:
nudná_funkce(*poziční_argumenty)
Out[98]:
3
In [99]:
pojmenované_argumenty = {"a": 1, "b": 2}
In [100]:
nudná_funkce(**pojmenované_argumenty)
Out[100]:
3

U pozičních argumentů musí pochopitelně sedět počet...

In [101]:
poziční_argumenty = [1, 2, 3]
In [102]:
nudná_funkce(*poziční_argumenty)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-102-1d551cc00f6d> in <module>
----> 1 nudná_funkce(*poziční_argumenty)

TypeError: nudná_funkce() takes 2 positional arguments but 3 were given

... a u pojmenovaných jména:

In [103]:
pojmenované_argumenty = {"a": 1, "c": 2}
In [104]:
nudná_funkce(**pojmenované_argumenty)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-104-75894a48cf5c> in <module>
----> 1 nudná_funkce(**pojmenované_argumenty)

TypeError: nudná_funkce() got an unexpected keyword argument 'c'

Metody

Metody jsou funkce úzce spjaté s objekty. Seznam metod, které daný objekt podporuje, získáme tak, že napíšeme jméno proměnné, která objekt obsahuje, za ně tečku a stiskneme tabulátor:

obj.<TAB>
In [105]:
obj = "ahoj"
obj.upper()
Out[105]:
'AHOJ'

Formálně je metoda jen funkce "navěšená" na třídě:

In [106]:
class FooBar:
    
    def get_my_type(self):
        return type(self)
In [107]:
obj = FooBar()
obj.get_my_type()
Out[107]:
__main__.FooBar

První argument metody (self) odkazuje na instanci třídy, na které jsme metodu zavolali, tj. v příkladu výše na ten samý objekt, na který odkazuje proměnná obj. Na rozdíl od případných dalších argumentů se metodě předává pomocí propojení přes tečku, ne v závorkách za metodou, nepíšeme tedy obj.get_my_type(obj).

Čísla

V Pythonu jsou dva základní typy čísel:

  • celá čísla (angl. integer) -- typ int
  • reálná čísla, reprezentovaná pomocí tzv. pohyblivé řádové čárky (angl. floating point) -- typ float

Literály celých čísel vypadají např. takhle:

In [108]:
3
Out[108]:
3
In [109]:
type(3)
Out[109]:
int
In [110]:
-5
Out[110]:
-5
In [111]:
9543761
Out[111]:
9543761

Pro lepší čitelnost mohou obsahovat i podtržítka:

In [112]:
9_543_761
Out[112]:
9543761

Literály reálných čísel se většinou poznají podle toho, že obsahují desetinnou čárku... tedy tečku protože je to podle angličtiny:

In [113]:
3.14
Out[113]:
3.14
In [114]:
type(3.14)
Out[114]:
float
In [115]:
-2.72
Out[115]:
-2.72
In [116]:
0.1
Out[116]:
0.1
In [117]:
# nulu můžeme vynechat
.1
Out[117]:
0.1

Ovšem ne nutně -- Python má speciální syntax pro tzv. vědecký zápis čísel, a čísla zapsaná tímto způsobem jsou vždycky typu float, i když desetinnou tečku neobsahují.

In [118]:
# 1 × 10³
1e3
Out[118]:
1000.0
In [119]:
# 1 × 10⁻³
1e-3
Out[119]:
0.001
In [120]:
# 2.34 × 10²
2.34e2
Out[120]:
234.0

Kromě celých a reálných čísel disponuje Python i komplexními čísly:

In [121]:
3 + 4j
Out[121]:
(3+4j)
In [122]:
type(3 + 4j)
Out[122]:
complex

Jestli jste o nich nikdy neslyšeli, tak je zase pusťte z hlavy :)

Kromě desítkové (decimální) soustavy, na kterou jsme všichni zvyklí, mají celá čísla v Pythonu literály i v soustavách jiných. Rozpoznáme je pomocí prefixů:

  • 0bbinární (dvojková) soustava -- používá číslice 0 a 1
  • 0ooktální (osmičková) soustava -- používá číslice 0–7
  • 0xhexadecimální (šestnáctková) soustava -- používá číslice 0–9 a a–f
In [123]:
0b11010
Out[123]:
26
In [124]:
0o32
Out[124]:
26
In [125]:
0x1a
Out[125]:
26

Jak vidíme, převod na desítkovou soustavu je jednoduchý -- stačí vyhodnotit literál v soustavě jiné a Python nám ho v odpovědi převede do desítkové. K převodu opačným směrem existují funkce, které nám vrátí řetězec obsahující zápis čísla v požadované soustavě:

In [126]:
bin(26)
Out[126]:
'0b11010'
In [127]:
oct(26)
Out[127]:
'0o32'
In [128]:
hex(26)
Out[128]:
'0x1a'

S čísly jdou provádět různé výpočty pomocí následujících operátorů, které snad díky zkušenosti s kalkulačkou budou působit převážně povědomě.

In [129]:
# sčítání
3 + 4
Out[129]:
7
In [130]:
# odečítání
3 - 4
Out[130]:
-1
In [131]:
# násobení
3 * 4
Out[131]:
12
In [132]:
# exponenciace
2**3
Out[132]:
8
In [133]:
# dělení
5 / 3
Out[133]:
1.6666666666666667

U dělení si všimněte, že výsledek je vždy float, i když dělíme dva inty a výsledkem je něco, co my lidé chápeme jako celé číslo:

In [134]:
4 / 2
Out[134]:
2.0
In [135]:
# celočíselné dělení
4 // 2
Out[135]:
2
In [136]:
5 // 3
Out[136]:
1
In [137]:
# modulo (= zbytek po celočíselném dělení)
5 % 3
Out[137]:
2

Když chceme zároveň celočíselné dělení i zbytek po něm, můžeme použít zabudovanou funkci divmod.

In [138]:
divmod(5, 3)
Out[138]:
(1, 2)
In [139]:
divmod(5, 3) == (5 // 3, 5 % 3)
Out[139]:
True

To se hodí např. při časových převodech -- kolik je 143 vteřin minut?

In [140]:
divmod(143, 60)
Out[140]:
(2, 23)

→ 2 minuty a 23 vteřin.

Zabudovaná funkce abs vrátí absolutní hodnotu čísla:

In [141]:
abs(-4.1)
Out[141]:
4.1

Pokud máme nějakou kolekci čísel (viz odd. Kolekce), můžeme identifikovat nejmenší, resp. největší z nich pomocí zabudovaných funkcí min a max.

In [142]:
from math import inf

# inf reprezentuje nekonečno
inf
Out[142]:
inf
In [143]:
čísla = [0, -inf, 3.14, -2.72, inf]
In [144]:
min(čísla)
Out[144]:
-inf
In [145]:
max(čísla)
Out[145]:
inf

Operátory mají různou prioritu, stejně jako v matematice:

In [146]:
2**3 + 4 * 5
Out[146]:
28

Pokud si nejsme pořadím operací jisti, můžeme ho pro jistotu specifikovat pomocí kulatých závorek:

In [147]:
(2**3) + (4 * 5)
Out[147]:
28

Nebo pochopitelně i změnit:

In [148]:
2**(3 + 4) * 5
Out[148]:
640

Všechny výše uvedené typy i zápisy čísel můžeme při výpočtech libovolně kombinovat, jen je potřeba mít na paměti, že jakmile se nám jako dílčí výsledek objeví float, i celkový výsledek bude float.

In [149]:
-0x1f + 165 * -0b1001 + 1.0
Out[149]:
-1515.0

Ke konverzi z intu na float slouží funkce float:

In [150]:
i = 3
In [151]:
float(i)
Out[151]:
3.0

Dokáže též převést textový zápis čísla (řetězec) na reálné číslo:

In [152]:
float("3.14")
Out[152]:
3.14
In [153]:
float("3")
Out[153]:
3.0

Analogicky funguje funkce int:

In [154]:
int(2.0)
Out[154]:
2
In [155]:
int("2")
Out[155]:
2
In [156]:
int(2.72)
Out[156]:
2

Všimněte si, že funkce int prostě jen uřízne desetinná místa. Pokud chceme zaokrouhlit podle matematických zvyklostí, je potřeba použít funkci round:

In [157]:
round(2.72)
Out[157]:
3

Při počítání s floaty je nutná jistá obezřetnost. Kvůli tomu, jak jsou v paměti počítače reprezentovány, není jejich zachycení úplně přesné, a některé výsledky tak můžou být překvapivé...

In [158]:
.1 + .2
Out[158]:
0.30000000000000004

... což vede až k tomu, že některé očekávané rovnosti neplatí:

In [159]:
.1 + .2 == .3
Out[159]:
False

Při porovnávání floatů je bezpečnější místo operátoru == (striktní rovnosti) používat funkci isclose z knihovny math (přibližná rovnost).

In [160]:
from math import isclose

isclose(.1 + .2, .3)
Out[160]:
True

S číselnými proměnnými často narážíme na následující situaci: vytvoříme proměnnou...

In [161]:
i = 0
i
Out[161]:
0

... a následně ji pak pomocí nějaké aritmetické operace potřebujeme aktualizovat na základě staré hodnoty -- např. přičíst k původnímu číslu nějaké nové a výsledek znovu uložit do té samé proměnné:

In [162]:
i = i + 2
i
Out[162]:
2

Python umožňuje zápis i = i + x zkrátit na i += x, abychom nemuseli psát i dvakrát:

In [163]:
i += 7
i
Out[163]:
9

Tento zkrácený zápis funguje s libovolným operátorem.

In [164]:
i **= 2
i
Out[164]:
81

Řetězce

Řetězce (angl. strings, v Pythonu typ str) nám umožňují reprezentovat text jako uspořádanou sérii (řetězec) znaků. Literály řetězců mají různé varianty, vždy jsou ale ohraničené jednoduchými nebo dvojitými uvozovkami.

In [165]:
"hello"
Out[165]:
'hello'
In [166]:
'hello'
Out[166]:
'hello'
In [167]:
type("hello")
Out[167]:
str

Chceme-li do řetězce uvozeného jednoduchými (dvojitými) uvozovkami vložit jednoduchou (dvojitou) uvozovku, je potřeba zrušit její speciální význam "ukončovač řetězce". K tomu slouží zpětné lomítko. Angl. se říká the backslash escapes the next character.

In [168]:
"\"Hello,\" I said."
Out[168]:
'"Hello," I said.'
In [169]:
'\'Hello,\' I said.'
Out[169]:
"'Hello,' I said."

Někdy je ale jednodušší prostě jen prostřídat druh uvozovek, které slouží k delimitaci řetězce, jak nám to ve svých odpovědích naznačuje sám Python.

Zpětné lomítko ale neslouží jen k rušení speciálního významu uvozovek -- jiným znakům speciální význam naopak dodává. Slouží tak spíš jako přepínač mezi doslovným a speciálním významem. Speciálním sekvencím znaků, které tvoří zpětné lomítko + jeden či více dalších znaků a mají jiný než doslovný význam, se angl. říká escape sequences.

Např. sekvence \n se promění ve znak nového řádku, \t pak ve znak tabulátoru:

In [170]:
"a\t1\naa\t11"
Out[170]:
'a\t1\naa\t11'

Na první pohled to tak nevypadá, ale to je jen kvůli tomu, že nás Python chce na tyto speciální znaky upozornit (což je dobře), takže když řetězec jen zobrazuje, reprezentuje tyto znaky pomocí dobře čitelných speciálních sekvencí. Aby se nové řádky a tabulátory projevily, musíme řetězec vytisknout:

In [171]:
print("a\t1\naa\t11")
a	1
aa	11

Což odpovídá zamýšlené podobě, ale je mnohem těžší poznat, co je v řetězci skutečně za znaky. Úplně jiný řetězec může totiž vytištěný vypadat zcela identicky:

In [172]:
print("a       1   \naa      11    ")
a       1   
aa      11    

Speciální sekvence existují též např. pro vložení libovolného unicodového znaku, ve formátu \uXXXX nebo \UXXXXXXXX, kde XXX... je hexadecimální zápis pořadového čísla znaku v unicodové tabulce:

In [173]:
"asi nějaký čínský znak...? \u4e3e"
Out[173]:
'asi nějaký čínský znak...? 举'

Pořadové číslo znaku lze v Pythonu získat pomocí funkce ord...

In [174]:
ord("č")
Out[174]:
269

... takto získáme jeho hexadecimální zápis...

In [175]:
hex(269)
Out[175]:
'0x10d'

... a ten pak můžeme použít ve speciální sekvenci:

In [176]:
"tohle by mělo být č: \u010d"
Out[176]:
'tohle by mělo být č: č'

Inverzní funkcí k funkci ord je funkce chr -- dáme jí pořadové číslo znaku a ona nám vrátí odpovídající znak.

In [177]:
chr(269)
Out[177]:
'č'
In [178]:
chr(0x10d)
Out[178]:
'č'

Více o znacích, Unicodu a kódování textu obecně viz samostatný notebook unicode.ipynb.

Python pozná, když zpětné lomítko nepředchází znaku či znakům, s nímž/nimiž by tvořilo speciální sekvenci, a vloží na takovém místě doslovné zpětné lomítko, ale zároveň nám jemně naznačí, že by byl radši, kdybychom speciální přepínací význam zpětného lomítka v literálu explicitně vypnuli... pomocí zpětného lomítka:

In [179]:
"a \ b"
Out[179]:
'a \\ b'
In [180]:
print("a \ b")
a \ b
In [181]:
print("a \\ b")
a \ b
In [182]:
"a \ b" == "a \\ b"
Out[182]:
True

Z toho mj. plyne, že když chceme zpětných lomítek napsat víc za sebou, je jich potřeba vždy dvojnásobek:

In [183]:
"a \\\\ b"
Out[183]:
'a \\\\ b'
In [184]:
print("a \\\\ b")
a \\ b

Podobné literály začnou brzy vypadat nepřehledně. Člověk pak zatouží všechny speciální sekvence zrušit, jen aby všechny znaky fungovaly jednoduše doslovně. I to je možné, pomocí tzv. surových řetězců (angl. raw strings). V nich je zpětné lomítko jen další znak:

In [185]:
r"\t \\\\ \n"
Out[185]:
'\\t \\\\\\\\ \\n'
In [186]:
print(r"\t \\\\ \n")
\t \\\\ \n

Surové řetězce se velmi dobře hodí pro práci s regulárními výrazy (viz notebook regex.ipynb), kde se lomítka často používají a hodí se nemuset přemýšlet o tom, jestl nám je Python nějak nepomíchá.

Formátovací řetězce (angl. format strings) umožňují pomocí složených závorek vkládat do řetězců libovolné výrazy:

In [187]:
x = 1
y = 2
f"{x} + {y} = {x + y}"
Out[187]:
'1 + 2 = 3'

Místo pár jednoduchých/dvojitých uvozovek můžeme řetězce též delimitovat párem trojic jednoduchých/dvojitých uvozovek. Tyto literály můžou obsahovat doslovné znaky nového řádku (ne jen speciální sekvenci \n) jednoduché/dvojité uvozovky bez lomítek (jen ne tři za sebou).

In [188]:
"""a
b
c"""
Out[188]:
'a\nb\nc'
In [189]:
"""
a
b
c
"""
Out[189]:
'\na\nb\nc\n'
In [190]:
s = """"Hello," I said.
"Hi," she replied."""
In [191]:
print(s)
"Hello," I said.
"Hi," she replied.

Jinak v nich platí stejná pravidla jako v běžných řetězcích, můžou být taktéž surové nebo formátovací nebo obojí zároveň atp.

Řetězce mají mnoho užitečných metod. Několik jich začíná na is... a informují nás o obsahu řetězce:

In [192]:
"hello".islower()
Out[192]:
True
In [193]:
"HELLO".isupper()
Out[193]:
True
In [194]:
"Hello".istitle()
Out[194]:
True
In [195]:
"12".isnumeric()
Out[195]:
True

Jiné vytvoří nový pozměněný řetězec:

In [196]:
"hello".upper()
Out[196]:
'HELLO'
In [197]:
"hello".title()
Out[197]:
'Hello'

Metoda strip oseká z okrajů řetězce prázdné znaky (angl. whitespace)...

In [198]:
"  \n   ZZZ\n \t \n".strip()
Out[198]:
'ZZZ'

... popřípadě libovolné znaky, které jí zadáme:

In [199]:
"bbabZZZaabaaa".strip("ab")
Out[199]:
'ZZZ'

Metoda split naseká řetězec na dílčí řetězce na prázdných znacích...

In [200]:
"a b c".split()
Out[200]:
['a', 'b', 'c']

... nebo na každém výskytu zadaného podřetězce:

In [201]:
"a, b, c".split(", ")
Out[201]:
['a', 'b', 'c']

Prázdných znaků může být libovolné množství a pokud dělením vznikne prázdný řetězec, tak je z výsledku odebrán...

In [202]:
"   a\n  b  \t c\n  ".split()
Out[202]:
['a', 'b', 'c']

... ale pokud zadáme podřetězec, hledá metoda doslova a do písmene přesně jeho výskyty a případné prázdné řetězce ve výstupu zachovává:

In [203]:
" a, b, ,  c".split(", ")
Out[203]:
[' a', 'b', '', ' c']

Opakem metody split je metoda join, která pospojuje kolekci řetězců, kterou jí předáme jako argument:

In [204]:
"-".join(["a", "b", "c"])
Out[204]:
'a-b-c'
In [205]:
# i řetězec lze chápat jako kolekci jednoznakových řetězců
"-".join("abc")
Out[205]:
'a-b-c'

Důležité je, že řetězce patří k tzv. nemodifikovatelným typům, tj. ať s nimi děláme co děláme...

In [206]:
s = "   a,b,c   "
s
Out[206]:
'   a,b,c   '
In [207]:
s.strip().split(",")
Out[207]:
['a', 'b', 'c']

... původní řetězec vždy zůstane nedotčený:

In [208]:
s
Out[208]:
'   a,b,c   '

Více o (ne)modifikovatelných typech viz odd. Kolekce.

Ještě pár slov k funkci print. Tato funkce vytiskne všechny svoje poziční argumenty oddělené mezerami a na závěr přilepí znak nového řádku.

In [209]:
print(1, "a", [])
1 a []

Oddělovač (mezeru) i ukončovač (nový řádek) si můžeme upravit pomocí pojmenovaných argumentů sep a end -- může to být libovolný jiný řetězec.

In [210]:
print(1, "a", [], sep="__ODDĚLOVAČ__", end="__UKONČOVAČ__")
1__ODDĚLOVAČ__a__ODDĚLOVAČ__[]__UKONČOVAČ__

Jak to, že print umí vytisknout i jiné objekty než jen řetězce? Ve skutečnosti neumí, jen na každý poziční argument zavolá funkci str, která ho právě na řetězec převede.

In [211]:
str(1)
Out[211]:
'1'
In [212]:
str([])
Out[212]:
'[]'

U objektů, které již řetězci jsou, žádný převod pochopitelně není potřeba, str jako výsledek vrátí nezměněný argument:

In [213]:
str("a")
Out[213]:
'a'

Kolekce

Kolekce je objekt, který obsahuje další objekty, které z něj lze jeden po druhém vytáhnout. Mezi základní zabudované typy kolekcí patří:

  • seznamy -- typ list
  • n-tice -- typ tuple
  • množiny -- typ set
  • slovníky -- typ dict

Svým způsobem můžeme i na řetězce nahlížet jako na kolekce znaků -- jak uvidíme níže, jde s nimi provádět stejné typy operací. Stejně jako ostatní kolekce např. reagují na funkci len, která vrací počet prvků v kolekci...

In [214]:
len("abc")
Out[214]:
3

... nebo na operátor in, který testuje přítomnost prvku v kolekci...

In [215]:
"b" in "abc"
Out[215]:
True

... nebo na funkci sorted, která umí na základě kolekce vyrobit seřazený seznam jejích prvků:

In [216]:
sorted("bca")
Out[216]:
['a', 'b', 'c']

Seznamy

Klíčové vlastnosti:

  • uspořádanost (angl. se uspořádaná kolekce řekne ordered collection): zachovává pořadí prvků
  • prvky se mohou opakovat
  • modifikovatelnost (angl. mutability): seznam lze kdykoli upravovat (přidávat/ubírat prvky)

Literál seznamu sestává z hranatých závorek, mezi něž vypíšeme počáteční prvky seznamu oddělené čárkami:

In [217]:
[1, "a"]
Out[217]:
[1, 'a']
In [218]:
# opakované prvky
[1, 1, 1]
Out[218]:
[1, 1, 1]
In [219]:
# prázdný seznam
[]
Out[219]:
[]

Kromě literálu můžeme seznam vytvořit ještě pomocí funkce list, např. proměnit řetězec na seznam znaků:

In [220]:
list("abc")
Out[220]:
['a', 'b', 'c']

Argument pro funkci list může být jakýkoli iterovatelný objekt, tj. objekt, ze kterého lze opakovaně tahat další objekty jako králíky z klobouku (iterace = opakování). Většinou se jedná o různé typy kolekcí, ale ne nutně. Např. funkce range umí vytvořit objekt, z nějž lze postupně tahat čísla v zadaném rozpětí:

In [221]:
list(range(3))
Out[221]:
[0, 1, 2]
In [222]:
# prázdný seznam
list()
Out[222]:
[]

Vytvořme si nyní seznam na hraní:

In [223]:
zvířata = "kočka pes morče slon žirafa kočka".split()
zvířata
Out[223]:
['kočka', 'pes', 'morče', 'slon', 'žirafa', 'kočka']

Díky tomu, že je seznam uspořádaný, má každý jeho prvek pořadového číslo, tzv. index (číslováno od nuly). Pomocí indexů lze k jednotlivým prvkům seznamu přistupovat ("ukázat" si na ně -- index pochází z latinského slova pro ukazováček). Této operaci se říká indexace a vypadá tak, že za seznam napíšeme hranaté závorky s požadovaným indexem:

In [224]:
zvířata[0]
Out[224]:
'kočka'

Záporná čísla indexují odzadu:

In [225]:
zvířata[-1]
Out[225]:
'kočka'

Teoreticky lze indexaci přilepit přímo za literál seznamu, byť to v praxi není moc užitečné a zápis vypadá zvláštně:

In [226]:
["kočka", "pes", "morče"][2]
Out[226]:
'morče'

Dohledat index nějakého prvku můžeme pomocí metody index:

In [227]:
zvířata.index("morče")
Out[227]:
2
In [228]:
zvířata.index("orangutan")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-228-979eb2c01602> in <module>
----> 1 zvířata.index("orangutan")

ValueError: 'orangutan' is not in list

Spočítat počet výskytů nějakého prvku můžeme pomocí metody count:

In [229]:
zvířata.count("kočka")
Out[229]:
2

Když chceme vytáhnout ze seznamu ne jeden prvek, ale podseznam, použijeme při indexaci tzv. výřez (angl. slice):

In [230]:
zvířata[2:4]
Out[230]:
['morče', 'slon']

První číslo je index prvku, jímž podseznam začíná, druhé číslo je index prvního prvku, který již do podseznamu nepatří. To je na první pohled možná trochu zvláštní, ale v kombinaci s tím, že vypuštěním prvního/druhého čísla dosáhneme toho, že výřez bude od začátku/do konce, nám to umožňuje jednoduše rozříznout seznam na nepřekrývající se části:

In [231]:
hranice = 3
In [232]:
zvířata[:hranice]
Out[232]:
['kočka', 'pes', 'morče']
In [233]:
zvířata[hranice:]
Out[233]:
['slon', 'žirafa', 'kočka']

Když vynecháme obě čísla, vytvoříme podseznam odpovídající původnímu seznamu:

In [234]:
zvířata[:]
Out[234]:
['kočka', 'pes', 'morče', 'slon', 'žirafa', 'kočka']
In [235]:
# což je to samé jako toto
zvířata[0:len(zvířata)]
Out[235]:
['kočka', 'pes', 'morče', 'slon', 'žirafa', 'kočka']

Ve výřezu můžeme volitelně specifikovat ještě třetí číslo, které stanovuje, že do seznamu má být zahrnutý jen každý x-tý prvek (např. každý druhý):

In [236]:
zvířata[1:4:2]
Out[236]:
['pes', 'slon']

Když je toto číslo záporné, můžeme podseznam vyříznou v opačném směru (zprava doleva):

In [237]:
zvířata[4:1:-2]
Out[237]:
['žirafa', 'morče']
In [238]:
# takto lze tedy převrátit pořadí seznamu
zvířata[::-1]
Out[238]:
['kočka', 'žirafa', 'slon', 'morče', 'pes', 'kočka']
In [239]:
# ale srozumitelnější je asi tento ekvivalentní zápis
# pomocí funkce reversed
list(reversed(zvířata))
Out[239]:
['kočka', 'žirafa', 'slon', 'morče', 'pes', 'kočka']

Kromě vyřezávání lze nové seznamy vytvářet i spojováním seznamů pomocí operátoru +...

In [240]:
zvířata + ["mastodont"]
Out[240]:
['kočka', 'pes', 'morče', 'slon', 'žirafa', 'kočka', 'mastodont']

... nebo klonováním pomocí operátoru *:

In [241]:
2 * zvířata
Out[241]:
['kočka',
 'pes',
 'morče',
 'slon',
 'žirafa',
 'kočka',
 'kočka',
 'pes',
 'morče',
 'slon',
 'žirafa',
 'kočka']

Říkali jsme si, že seznamy jsou modifikovatelné, že lze prvky libovolně ubírat a přidávat. Nicméně všechno, co jsme zatím s naším seznamem zvířata prováděli, na něm nezkřivilo ani vlásek:

In [242]:
zvířata
Out[242]:
['kočka', 'pes', 'morče', 'slon', 'žirafa', 'kočka']

Zatím jsme tedy tento seznam nijak nemodifikovali, všechny operace, které jsme na něj aplikovali, vedly k tomu, že na jeho základě vznikl nový, který z toho původního nějakým způsobem vycházel.

K modifikaci seznamu slouží některé jeho metody. Ukažme si to na příkladu seřazování. Samostatná funkce sorted vytvoří na základě původního seznamu nový seznam, který obsahuje stejné prvky, ovšem seřazené, a vrátí ho jako výsledek:

In [243]:
seřazená_zvířata = sorted(zvířata)
seřazená_zvířata
Out[243]:
['kočka', 'kočka', 'morče', 'pes', 'slon', 'žirafa']

Můžeme si jednoduše ověřit, že zvířata a seřazená_zvířata jsou dva různé objekty...

In [244]:
zvířata is seřazená_zvířata
Out[244]:
False

... a ani si nejsou rovné (protože záleží na pořadí):

In [245]:
zvířata == seřazená_zvířata
Out[245]:
False

Naopak metoda sort seřadí původní seznam, na kterém ji zavoláme, a vrátí None:

In [246]:
# v tuto chvíli poprvé modifikujeme seznam zvířata...
zvířata.sort()
In [247]:
# ... což si můžeme jednoduše ověřit (změnilo se pořadí)
zvířata
Out[247]:
['kočka', 'kočka', 'morče', 'pes', 'slon', 'žirafa']

Seznamy zvířata a seřazená_zvířata jsou nadále různé objekty...

In [248]:
zvířata is seřazená_zvířata
Out[248]:
False

... ale v tuto chvíli už jsou si rovné (protože teď už obsahují stejné prvky ve stejném pořadí):

In [249]:
zvířata == seřazená_zvířata
Out[249]:
True

Některé další užitečné modifikující metody objektů typu list:

In [250]:
# přidání jednoho prvku na konec
zvířata.append("užovka")
zvířata
Out[250]:
['kočka', 'kočka', 'morče', 'pes', 'slon', 'žirafa', 'užovka']
In [251]:
# pomocí indexace lze nahradit jeden prvek...
zvířata[4] = "slonice"
zvířata
Out[251]:
['kočka', 'kočka', 'morče', 'pes', 'slonice', 'žirafa', 'užovka']
In [252]:
# ... nebo celý podseznam:
zvířata[4:6] = ["slon", "žirafáč"]
zvířata
Out[252]:
['kočka', 'kočka', 'morče', 'pes', 'slon', 'žirafáč', 'užovka']
In [253]:
# přidání jednoho prvku na libovolný index
zvířata.insert(1, "kocour")
zvířata
Out[253]:
['kočka', 'kocour', 'kočka', 'morče', 'pes', 'slon', 'žirafáč', 'užovka']
In [254]:
# přidání celé kolekce prvků na konec
zvířata.extend(["kapr", "štika", "candát"])
zvířata
Out[254]:
['kočka',
 'kocour',
 'kočka',
 'morče',
 'pes',
 'slon',
 'žirafáč',
 'užovka',
 'kapr',
 'štika',
 'candát']
In [255]:
# odebrání posledního prvku
zvířata.pop()
Out[255]:
'candát'
In [256]:
zvířata
Out[256]:
['kočka',
 'kocour',
 'kočka',
 'morče',
 'pes',
 'slon',
 'žirafáč',
 'užovka',
 'kapr',
 'štika']
In [257]:
# odebrání prvku na libovolném indexu
zvířata.pop(0)
Out[257]:
'kočka'
In [258]:
zvířata
Out[258]:
['kocour',
 'kočka',
 'morče',
 'pes',
 'slon',
 'žirafáč',
 'užovka',
 'kapr',
 'štika']
In [259]:
# odebrání prvního výskytu konkrétního prvku
zvířata.remove("pes")
zvířata
Out[259]:
['kocour', 'kočka', 'morče', 'slon', 'žirafáč', 'užovka', 'kapr', 'štika']
In [260]:
# převrácení pořadí prvků
zvířata.reverse()
zvířata
Out[260]:
['štika', 'kapr', 'užovka', 'žirafáč', 'slon', 'morče', 'kočka', 'kocour']

Důležitým důsledkem modifikovatelnosti je, že pokud na ten samý seznam odkazuje více proměnných (= má více jmen), jakákoli modifikace se projeví pod všemi jmény. Představte si analogii: pokud mám bratra, který se jmenuje Jan, tak když se Jan nechá ostříhat, bude ostříhaný i můj bratr, protože je to jedna a tatáž osoba. Podobně se seznamy:

In [261]:
jan = ["nohy", "trup", "hlava", "vlasy"]
bratr = jan
In [262]:
jan is bratr
Out[262]:
True
In [263]:
# teď Jana "ostříháme"
jan.pop()
Out[263]:
'vlasy'
In [264]:
jan
Out[264]:
['nohy', 'trup', 'hlava']
In [265]:
bratr
Out[265]:
['nohy', 'trup', 'hlava']

Modifikace je ošemetná zejména v případě, kdy je skrytá v nějaké funkci a týká se argumentů, které funkci předáváme zvenčí. Pak nám vůbec nemusí dojít, že argumenty vlastně modifikujeme:

In [266]:
def zkrášlit(osoba):
    # osobu zkrášlíme tak, že ji ostříháme
    osoba.pop()

josef = ["nohy", "trup", "hlava", "vlasy"]
In [267]:
zkrášlit(josef)

Nic není vidět, zafungovala to vůbec, podařilo se nám Josefa zkrášlit? No pro jistotu to zkusíme ještě jednou, tím přece nemůžeme nic zkazit, že...

In [268]:
zkrášlit(josef)

Tak co teď? Podívejme se na Josefa...

In [269]:
josef
Out[269]:
['nohy', 'trup']

Ježišmarja chudák Josef, kde má hlavu?!

(Je to trochu fórek pórek, ale snad si rozumíme, v čem tkví nebezpečí :) )

Závěrem -- s modifikací je potřeba být opatrný a raději s ní šetřit:

  • pokud možno přímo nemodifikovat argumenty funkcí
  • kde se to hodí, používat místo seznamů nemodifikovatelné n-tice

n-tice

Klíčové vlastnosti jsou stejné jako u seznamů jen s tím rozdílem, že jsou nemodifikovatelné. Lze s nimi tedy provádět ty samé operace, s výjimkou těch modifikujících.

Literály n-tic v zásadě mohou sestávat jen z prvků oddělených čárkami:

In [270]:
1, "a"
Out[270]:
(1, 'a')

Nicméně v praxi je často potřeba obalit n-tici do kulatých závorek, protože čárka má jako operátor velmi nízkou prioritu (viz odd. Čísla). Je to podobné, jako když musíme použít závorky, aby sčítání proběhlo před dělením -- např. 2 + 3 × 4 = 14 vs. (2 + 3) × 4 = 20. Kdo se tím nechce moc zabývat, může závorky okolo n-tic používat pořád.

In [271]:
(1, "a")
Out[271]:
(1, 'a')

Specifickou syntax má prázdná n-tice...

In [272]:
()
Out[272]:
()
In [273]:
type(())
Out[273]:
tuple
In [274]:
len(())
Out[274]:
0

... a jednoprvková n-tice (čárka před uzavírací závorkou je povinná):

In [275]:
("a",)
Out[275]:
('a',)

Jiný iterovatelný objekt na n-tici proměníme pomocí funkce tuple:

In [276]:
tuple([1, "a"])
Out[276]:
(1, 'a')
In [277]:
tuple("abc")
Out[277]:
('a', 'b', 'c')
In [278]:
tuple(range(3))
Out[278]:
(0, 1, 2)

Jedním z důsledků nemodifikovatelnosti n-tic je, že do nich lze sice indexovat...

In [279]:
ntice = tuple("abc")
ntice[1]
Out[279]:
'b'

... ale už nelze pomocí indexace prvky nahrazovat jinými:

In [280]:
ntice[1] = "Z"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-280-53c8f0165f7f> in <module>
----> 1 ntice[1] = "Z"

TypeError: 'tuple' object does not support item assignment

Podobně řetězce, které jsou také nemodifikovatelné:

In [281]:
řetězec = "abc"
řetězec[1]
Out[281]:
'b'
In [282]:
řetězec[1] = "Z"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-282-a215aaf9569e> in <module>
----> 1 řetězec[1] = "Z"

TypeError: 'str' object does not support item assignment

Množiny

Klíčové vlastnosti:

  • neuspořádanost: prvky nemají dané pořadí
  • prvky se nesmí opakovat
  • modifikovatelnost

Literály vypadají následovně:

In [283]:
{1, "a"}
Out[283]:
{1, 'a'}

Ovšem pozor, {} není prázdná množina, ale prázdný slovník (viz níže)! Prázdnou množinu získáme pomocí funkce set...

In [284]:
set()
Out[284]:
set()

... která nám poslouží i k převodu jiné kolekce na množinu:

In [285]:
set("kočička")
Out[285]:
{'a', 'i', 'k', 'o', 'č'}

Prvky v množině se nesmějí opakovat v tom smyslu, že do množiny nelze vložit dva prvky a a b, o nichž platí, že a == b. Nicméně není třeba se bát, když se o to pokusíme, nenastane chyba, množina prostě druhý pokus jednoduše ignoruje:

In [286]:
{1, 2, 1}
Out[286]:
{1, 2}
In [287]:
set([1, 2, 1])
Out[287]:
{1, 2}

Vytvořme si dvě množiny na hraní:

In [288]:
set1 = {1, 2, 3}
set2 = {2, 3, 4}

Důležitým důsledkem neuspořádanosti je, že do množiny nelze indexovat -- ptát se po "prvním" prvku množiny nedává smysl:

In [289]:
set1[0]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-289-c38563f1af7a> in <module>
----> 1 set1[0]

TypeError: 'set' object is not subscriptable

Množiny podporují různé množinové operace. Některé jsou nemodifikující...

In [290]:
# sjednocení
set1.union(set2)
Out[290]:
{1, 2, 3, 4}
In [291]:
# též možno pomocí operátoru |
set1 | set2
Out[291]:
{1, 2, 3, 4}
In [292]:
# průnik
set1.intersection(set2)
Out[292]:
{2, 3}
In [293]:
# též možno pomocí operátoru &
set1 & set2
Out[293]:
{2, 3}
In [294]:
# rozdíl
set1.difference(set2)
Out[294]:
{1}
In [295]:
# též možno pomocí operátoru -
set1 - set2
Out[295]:
{1}
In [296]:
# vztahy mezi množinami
set1.issubset(set2)
Out[296]:
False

... jiné jsou modifikující:

In [297]:
# přidání prvku
set1.add(4)
set1
Out[297]:
{1, 2, 3, 4}
In [298]:
# zde k modifikaci nedojde, protože set1 už prvek 4 obsahuje
set1.add(4)
set1
Out[298]:
{1, 2, 3, 4}
In [299]:
# odebrání prvku
set1.remove(4)
set1
Out[299]:
{1, 2, 3}
In [300]:
# přidání více prvků, ovšem znovu pochopitelně s vyřazením duplicit
set1.update([0, 1, 2])
set1
Out[300]:
{0, 1, 2, 3}
In [301]:
# průnik, přičemž výsledek operace se uloží do původní množiny set1
set1.intersection_update(set2)
set1
Out[301]:
{2, 3}

Teď už tedy platí:

In [302]:
set1.issubset(set2)
Out[302]:
True
In [303]:
# též možno pomocí operátoru < ("menší než")
set1 < set2
Out[303]:
True
In [304]:
set2.issuperset(set1)
Out[304]:
True
In [305]:
# též možno pomocí operátoru > ("větší než")
set2 > set1
Out[305]:
True

Kromě toho, že nám množiny dobře poslouží, když chceme kolekci s duplicitami zredukovat na kolekci unikátních objektů (např. když chceme seznam tokenů v textu převést na množinu typů), mají ještě jednu výhodu: díky tomu, že prvky nemusí respektovat pořadí zadané uživatelem, můžou být interně uspořádané tak, aby umožňovaly velmi rychle zjistit, zda množina daný prvek obsahuje či ne.

V případě seznamu je potřeba ho procházet prvek po prvku, od začátku do konce, abychom zjistili, zda daný prvek obsahuje či ne. Čím je seznam delší, a čím dál v něm prvek je (nebo pokud v něm prvek vůbec není), tím déle to trvá:

In [306]:
# seznam prvního milionu čísel
seznam = list(range(1_000_000))
In [307]:
# Python musí seznam projít celý, prvek po prvku, aby zjistil,
# že řetězec "a" v našem seznamu prvního milionu čísel není
"a" in seznam
Out[307]:
False
In [308]:
%%timeit
# speciální direktiva %%timeit není přímo součástí Pythonu,
# poskytuje ji prostředí Jupyter; slouží k tomu, že spustí
# danou buňku opakovaně, pokaždé změří, jak dlouho trvá
# buňku vykonat, a pak nám ukáže průměrný čas
"a" in seznam
8.28 ms ± 26.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Oproti tomu v množině lze díky jejímu internímu uspořádání dohledat prvek velmi rychle:

In [309]:
množina = set(seznam)
In [310]:
"a" in množina
Out[310]:
False
In [311]:
%%timeit
"a" in množina
26.3 ns ± 0.0176 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

U seznamu trvá naše operace v řádu desítek milisekund (10⁻³ s), kdežto u množiny jsou to desítky nanosekund (10⁻⁹ s) -- je tedy milionkrát rychlejší!

Nemodifikovatelným protějškem typu set je typ frozenset.

Slovníky

Slovníky se od ostatních kolekcí, které jsme doposud potkali, liší v tom, že obsahují dva typy prvků:

  • klíče (angl. keys), podle nichž se ve slovníku hledá
  • a hodnoty (angl. values), které pod klíči dohledáme

Je to podobně jako s jazykovými slovníky (proto se tak v Pythonu jmenují): když ve slovníku hledám slovo "kočka", tak "kočka" je klíč, a definice, kterou najdu, je jeho odpovídající hodnota.

Stěžejní vlastnosti slovníků:

  • neuspořádanost: prvky nemají dané pořadí
  • klíče se nesmí opakovat, ale hodnoty klidně můžou
  • modifikovatelnost

Literály vypadají následovně:

In [312]:
{"a": 1, "b": 2}
Out[312]:
{'a': 1, 'b': 2}

Prvek před dvojtečkou je vždy klíč, za dvojtečkou pak jeho odpovídající hodnota.

Když stejný klíč specifikujeme víckrát, nenastane chyba, jen poslední asociovaná hodnota přemaže ty předchozí:

In [313]:
{"a": 1, "a": 2, "a": 3}
Out[313]:
{'a': 3}
In [314]:
# prázdný slovník
{}
Out[314]:
{}
In [315]:
type({})
Out[315]:
dict

Slovníky lze vytvářet i pomocí funkce dict, a to v zásadě dvěma způsoby. První možnost je, že do slovníku "nasypeme" kolekci sestávající z dvojic objektů, z nichž první vždy bude brán jako klíč a druhý jako jeho asociovaná hodnota:

In [316]:
# např. seznam n-tic o dvou položkách...
dict([("a", 1), ("b", 2)])
Out[316]:
{'a': 1, 'b': 2}
In [317]:
# ... nebo klidně seznam seznamů o dvou položkách...
dict([["a", 1], ["b", 2]])
Out[317]:
{'a': 1, 'b': 2}
In [318]:
# ... nebo třeba n-tice řetězců o dvou znacích atp.
dict(("ab", "cd"))
Out[318]:
{'a': 'b', 'c': 'd'}

Druhá možnost je, že využijeme pojmenované argumenty -- jména argumentů pak skončí jako klíče, argumenty samotné jako hodnoty:

In [319]:
dict(a=1, b=2)
Out[319]:
{'a': 1, 'b': 2}

Vytvořme si slovník na hraní:

In [320]:
slovník = {
    "pes": "nejlepší přítel člověka",
    "kočka": "vypočítavá potvora"
}

Slovníky jsou sice stejně jako množiny neuspořádané, ale indexaci na rozdíl od nich podporují. Jen se jako index nepoužívá pořadové číslo (protože žádné pořadí neexistuje), ale klíč:

In [321]:
slovník["kočka"]
Out[321]:
'vypočítavá potvora'

Přes metodu keys se dostaneme ke všem klíčům ve slovníku...

In [322]:
slovník.keys()
Out[322]:
dict_keys(['pes', 'kočka'])

... přes metodu values k hodnotám...

In [323]:
slovník.values()
Out[323]:
dict_values(['nejlepší přítel člověka', 'vypočítavá potvora'])

... a přes metodu items k uspořádaným dvojicím (klíč, hodnota):

In [324]:
slovník.items()
Out[324]:
dict_items([('pes', 'nejlepší přítel člověka'), ('kočka', 'vypočítavá potvora')])

Když se pokusíme indexovat podle klíče, který ve slovníku není, Python zkolabuje:

In [325]:
slovník["morče"]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-325-21eeb4f972e5> in <module>
----> 1 slovník["morče"]

KeyError: 'morče'

Abychom se kolapsu (a tím i zastavení běhu programu) vyhnuli, můžeme použít metodu get, která v případě absence klíče vrátí místo hodnoty ve slovníku nějaký jiný objekt (defaultně je to None, ale můžeme specifikovat libovolný objekt).

In [326]:
slovník.get("pes")
Out[326]:
'nejlepší přítel člověka'
In [327]:
slovník.get("morče")
In [328]:
slovník.get("morče", "MORČE VE SLOVNÍKU NENÍ!")
Out[328]:
'MORČE VE SLOVNÍKU NENÍ!'

Díky tomu, že jsou slovníky modifikovatelné, můžeme hodnoty odpovídající jednotlivým klíčům libovolně měnit...

In [329]:
slovník["kočka"] = "chlupatý mazlíček"
slovník
Out[329]:
{'pes': 'nejlepší přítel člověka', 'kočka': 'chlupatý mazlíček'}

... nebo přidávat nové:

In [330]:
slovník["rybička"] = "němá slizká tvář"
slovník
Out[330]:
{'pes': 'nejlepší přítel člověka',
 'kočka': 'chlupatý mazlíček',
 'rybička': 'němá slizká tvář'}

V prvním případě jsme přepsali původní hodnotu odpovídající klíči "kočka", ve druhém jsme přidali nový klíč "rybička" a k němu odpovídající hodnotu. Syntax (zápis) ale vypadá v obou případech stejně.

Občas potřebujeme zajistit, aby za žádnou cenu nedošlo k přepsání existující hodnoty (viz první případ výše). K tomu poslouží metoda setdefault -- pokud klíč už ve slovníku existuje, původní hodnotu nezmění, jen ji vrátí...

In [331]:
slovník.setdefault("kočka", "tuhle novou hodnotu do slovníku neprocpu :(")
Out[331]:
'chlupatý mazlíček'
In [332]:
slovník
Out[332]:
{'pes': 'nejlepší přítel člověka',
 'kočka': 'chlupatý mazlíček',
 'rybička': 'němá slizká tvář'}

... ale pokud neexistuje, klíč a odpovídající hodnotu do slovníku přidá (a hodnotu taky vrátí):

In [333]:
slovník.setdefault("morče", "ale tuhle procpu :)")
Out[333]:
'ale tuhle procpu :)'
In [334]:
slovník
Out[334]:
{'pes': 'nejlepší přítel člověka',
 'kočka': 'chlupatý mazlíček',
 'rybička': 'němá slizká tvář',
 'morče': 'ale tuhle procpu :)'}

Metoda setdefault tedy funguje podobně jako metoda get s tím rozdílem, že v případě nepřítomnosti klíče ve slovníku defaultní hodnotu nejen vrátí, ale i uloží do slovníku.

Metoda update, která přidává nové prvky do slovníku / upravuje stávající, nabízí co do argumentů, které zvládne zpracovat, podobné možnosti jako samotná funkce dict:

In [335]:
# libovolná kolekce dvojic prvků...
slovník.update([("a", 1), ("b", 2)])
slovník
Out[335]:
{'pes': 'nejlepší přítel člověka',
 'kočka': 'chlupatý mazlíček',
 'rybička': 'němá slizká tvář',
 'morče': 'ale tuhle procpu :)',
 'a': 1,
 'b': 2}
In [336]:
slovník.update(("ab", "cd"))
slovník
Out[336]:
{'pes': 'nejlepší přítel člověka',
 'kočka': 'chlupatý mazlíček',
 'rybička': 'němá slizká tvář',
 'morče': 'ale tuhle procpu :)',
 'a': 'b',
 'b': 2,
 'c': 'd'}
In [337]:
# ... jiný slovník...
slovník.update({"b": "z"})
slovník
Out[337]:
{'pes': 'nejlepší přítel člověka',
 'kočka': 'chlupatý mazlíček',
 'rybička': 'němá slizká tvář',
 'morče': 'ale tuhle procpu :)',
 'a': 'b',
 'b': 'z',
 'c': 'd'}
In [338]:
# ... nebo pojmenované argumenty:
slovník.update(žralok="dravá mořská paryba", kůň="tam někde v pastvinách")
slovník
Out[338]:
{'pes': 'nejlepší přítel člověka',
 'kočka': 'chlupatý mazlíček',
 'rybička': 'němá slizká tvář',
 'morče': 'ale tuhle procpu :)',
 'a': 'b',
 'b': 'z',
 'c': 'd',
 'žralok': 'dravá mořská paryba',
 'kůň': 'tam někde v pastvinách'}

Odebírat prvky ze slovníku lze různými způsoby, podle toho, zda chceme odebrat konkrétní klíč a chceme ještě pracovat s jeho hodnotou...

In [339]:
# metoda pop smaže prvek podle klíče a vrátí odpovídající hodnotu
slovník.pop("a")
Out[339]:
'b'
In [340]:
slovník
Out[340]:
{'pes': 'nejlepší přítel člověka',
 'kočka': 'chlupatý mazlíček',
 'rybička': 'němá slizká tvář',
 'morče': 'ale tuhle procpu :)',
 'b': 'z',
 'c': 'd',
 'žralok': 'dravá mořská paryba',
 'kůň': 'tam někde v pastvinách'}

... nebo zda hodnotu k ničemu nepotřebujeme...

In [341]:
# operátor del smaže prvek podle klíče a nevrátí nic
del slovník["b"]
In [342]:
slovník
Out[342]:
{'pes': 'nejlepší přítel člověka',
 'kočka': 'chlupatý mazlíček',
 'rybička': 'němá slizká tvář',
 'morče': 'ale tuhle procpu :)',
 'c': 'd',
 'žralok': 'dravá mořská paryba',
 'kůň': 'tam někde v pastvinách'}

... nebo zda je nám jedno, který prvek odebereme (necháme Python vybrat za nás):

In [343]:
# metoda popitem odebere nějaký prvek (nevíme předem jaký)
# a vrátí klíč a hodnotu jako n-tici
slovník.popitem()
Out[343]:
('kůň', 'tam někde v pastvinách')
In [344]:
slovník
Out[344]:
{'pes': 'nejlepší přítel člověka',
 'kočka': 'chlupatý mazlíček',
 'rybička': 'němá slizká tvář',
 'morče': 'ale tuhle procpu :)',
 'c': 'd',
 'žralok': 'dravá mořská paryba'}

Díky neuspořádanosti a zákazu opakování je dohledávání klíčů ve slovníku velmi rychlé (stejně jako u množin) a zapisuje se velmi jednoduše:

In [345]:
"kočka" in slovník
Out[345]:
True

Hodnoty ale ve slovníku takhle jednoduše nedohledáme:

In [346]:
"nejlepší přítel člověka" in slovník
Out[346]:
False

Je potřeba je projít jednu po druhé pomocí metody values, což na rozdíl od klíčů trvá stejně dlouho jako u seznamu (asi jako kdybychom v jazykovém slovníku hledali tak, že bychom pročítali jen definice):

In [347]:
"nejlepší přítel člověka" in slovník.values()
Out[347]:
True

Můžeme si to ověřit na libovolném velkém slovníku. K vytvoření takového velkého testovacího slovníku můžeme použít zabudovanou funkci zip, která "sezipuje" dohromady prvky několika iterovatelných objektů. Zní to složitě, ale když se na ni podíváme v akci, je to myslím jednoduché:

In [348]:
# vše obalíme do funkce list, aby se výsledné "sezipované" prvky
# uložily do seznamu
list(zip("abc", [1, 2, 3]))
Out[348]:
[('a', 1), ('b', 2), ('c', 3)]

Pomocí funkce zip jednoduše můžeme vytvořit slovník, v němž namapujeme první milion čísel na sebe sama, ve stylu...

In [349]:
dict(zip(range(3), range(3)))
Out[349]:
{0: 0, 1: 1, 2: 2}

... akorát větší. Takový slovník samozřejmě není k ničemu užitečný, jen je velký, takže dobře poslouží k ilustraci rozdílu rychlosti v hledání mezi klíči a hodnotami.

In [350]:
libovolný_velký_slovník = dict(zip(range(1_000_000), range(1_000_000)))

Hledání v hodnotách:

In [351]:
"a" in libovolný_velký_slovník.values()
Out[351]:
False
In [352]:
%%timeit
"a" in libovolný_velký_slovník.values()
11.4 ms ± 243 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Hledání v klíčích:

In [353]:
"a" in libovolný_velký_slovník
Out[353]:
False
In [354]:
%%timeit
"a" in libovolný_velký_slovník
23.6 ns ± 0.00474 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Znovu pozorujeme rozdíl šesti řádů: hledání v klíčích je milionkrát rychlejší než hledání v hodnotách.

A když už máme slovníku plné zuby, můžeme jeho obsah vymazat a začít nanovo:

In [355]:
libovolný_velký_slovník.clear()
In [356]:
libovolný_velký_slovník
Out[356]:
{}

Vnořené kolekce

Jak už jsme ostatně v některých příkladech výše naznačili, kolekce lze do sebe libovolně zanořovat, čímž můžeme reprezentovat různé složité strukturní vztahy. Literální zápis vnořených kolekcí je celkem intuitivní:

In [357]:
slovník = {
    "hesla": {
        "kočka": {
            "definice": "chlupatý mazlíček",
            "příklad": "Na okně seděla kočka...",
        },
        "pes": {
            "definice": "nejlepší přítel člověka",
            "příklad": "... a venku štěkal pes.",
        },
    },
    "autoři": [
        {"jméno": "John", "příjmení": "Doe"},
        {"jméno": "Jane", "příjmení": "Doe"},
    ],
    "datum": (2018, 11, 7),
}

Naproti tomu indexace do vnořených kolekcí občas lidem činí při prvním setkání potíže. Chceme-li např. vytáhnout ze slovníku definici kočky, někdo má tendenci zkoušet následující zápis, který svým vnořením zrcadlí vnoření literálu:

In [358]:
slovník["hesla"["kočka"]]
<>:1: SyntaxWarning: str indices must be integers or slices, not str; perhaps you missed a comma?
<>:1: SyntaxWarning: str indices must be integers or slices, not str; perhaps you missed a comma?
<ipython-input-358-c2f5af94dca9>:1: SyntaxWarning: str indices must be integers or slices, not str; perhaps you missed a comma?
  slovník["hesla"["kočka"]]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-358-c2f5af94dca9> in <module>
----> 1 slovník["hesla"["kočka"]]

TypeError: string indices must be integers

Chybová hláška zní trochu krypticky, ale můžeme z ní odvodit, že se Python zřejmě snaží indexovat do nějakého řetězce (tj. vytáhnout znak z řetězce), což jsme rozhodně nezamýšleli, takže zápis musí být špatně.

Co se tedy děje? Pojďme si výraz rozebrat tak, jak ho vidí Python. Nejdřív vidí proměnnou slovník, za ní hranaté závorky. Usoudí tedy (správně), že se snažíme přistoupit k nějakému klíči ve slovníku. K jakému klíči? To je potřeba zjistit na základě obsahu hranatých závorek. Jinými slovy, Python v tuto chvíli vidí slovník[X], kde X je zástupný znak pro klíč, který chceme, aby ve slovníku dohledal.

Dobře, tak dál. Abych mohl X dohledat, uvažuje Python, musím nejdřív zjistit, co je zač. Hm, podle všeho mám za X dosadit výraz "hesla"[Y]. Ten musím tedy vyhodnotit jako první, abych mohl posléze vyhodnotit výraz slovník[X].

Co je "hesla"[Y] za výraz? Je to výraz, v němž máme řetězec "hesla", a pomocí indexace se z něj snažíme vytáhnout znak, který odpovídá indexu Y.

Skvěle, tetelí se Python, už tedy stačí jen zjistit, jaká je hodnota indexu Y, a můžu začít celý výraz vyhodnocovat, pěkně zvnitřka k vnějšku. Jenže ouha, Y by mělo být pořadové číslo požadovaného znaku, ale místo toho je to řetězec "kočka". Chudák Python neví, co to znamená, vytáhnout "kočka"-tý znak z řetězce "hesla", a tak to vzdá a jen si postěžuje, že indexy do řetězců by měly být celá čísla ("string indices must be integers").

Jinými slovy, vyhodnocování celého výrazu selže na tomto podvýrazu:

In [359]:
"hesla"["kočka"]
<>:1: SyntaxWarning: str indices must be integers or slices, not str; perhaps you missed a comma?
<>:1: SyntaxWarning: str indices must be integers or slices, not str; perhaps you missed a comma?
<ipython-input-359-acc1bf557eb4>:1: SyntaxWarning: str indices must be integers or slices, not str; perhaps you missed a comma?
  "hesla"["kočka"]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-359-acc1bf557eb4> in <module>
----> 1 "hesla"["kočka"]

TypeError: string indices must be integers

Jak vidno, chyba je stejná.

Jak tedy na to? Vždycky je možné rozdělit indexaci do vnořené kolekce na víc jednoduchých indexací s použitím pomocných proměnných:

In [360]:
hesla = slovník["hesla"]
kočka = hesla["kočka"]
kočka
Out[360]:
{'definice': 'chlupatý mazlíček', 'příklad': 'Na okně seděla kočka...'}

Z výše uvedeného zápisu ale plyne, že se meziproměnné hesla můžeme zbavit pomocí dosazování. Začněme od proměnné kočka, která obsahuje zamýšlený výsledek (slovníkové heslo pro kočku; mezery slouží jen ke zvýraznění toho, jak probíhá dosazování):

In [361]:
kočka
Out[361]:
{'definice': 'chlupatý mazlíček', 'příklad': 'Na okně seděla kočka...'}

Za výraz kočka lze dosadit výraz hesla["kočka"] (protože kočka = hesla["kočka"]):

In [362]:
hesla              ["kočka"]
Out[362]:
{'definice': 'chlupatý mazlíček', 'příklad': 'Na okně seděla kočka...'}

Výborně, výsledek zůstal stejný, takže dosazení platí. Dále můžeme za výraz hesla dosadit výraz slovník["hesla"] (protože hesla = slovník["hesla]), přičemž útržek kódu ["kočka"] zůstane tam, kde byl:

In [363]:
slovník["hesla"]   ["kočka"]
Out[363]:
{'definice': 'chlupatý mazlíček', 'příklad': 'Na okně seděla kočka...'}

Skvěle, výsledek je stále stejný! Teď už se jen zbavíme mezer, aby byl zápis konvenčnější, a vymysleli jsme způsob, jak indexovat do vnořených kolekcí.

In [364]:
slovník["hesla"]["kočka"]
Out[364]:
{'definice': 'chlupatý mazlíček', 'příklad': 'Na okně seděla kočka...'}
In [365]:
# rok
slovník["datum"][0]
Out[365]:
2018
In [366]:
# příkladová věta pro psa
slovník["hesla"]["pes"]["příklad"]
Out[366]:
'... a venku štěkal pes.'
In [367]:
# jméno druhého autora
slovník["autoři"][1]["jméno"]
Out[367]:
'Jane'

A tak podobně.

Nezabudované kolekce

Existuje samozřejmě mnoho nezabudovaných kolekcí -- např. třída FreqDist z knihovny nltk je taky kolekce (obsahuje dílčí prvky). Nezabudované kolekce pochopitelně nemají literály, je potřeba je inicializovat pomocí příslušného konstruktoru třídy (viz odd. Typy a třídy):

In [368]:
from nltk import FreqDist
In [369]:
FreqDist("aababc")
Out[369]:
FreqDist({'a': 3, 'b': 2, 'c': 1})

Opakování operací: for-cyklus

Často chceme nějakou operaci provést opakovaně, ale pokaždé s trochu jinými daty. Např. chceme vytisknout čísla od 0 do 2. Mohli bychom samozřejmě použít copy-paste, tj. řádek třikrát zkopírovat a pokaždé jen nahradit číslo...

In [370]:
print(0)
print(1)
print(2)
0
1
2

... jenže pak je problém, že jakmile chceme akci trochu upravit, třeba vytisknout "Číslo: 0" místo jen "0", a podobně pro ostatní čísla, musíme provést úpravu na každém řádku:

In [371]:
print("Číslo:", 0)
print("Číslo:", 1)
print("Čísloo:", 2)
Číslo: 0
Číslo: 1
Čísloo: 2

Při takovém opisování / kopírování je snadné udělat na jednom místě chybu (viz výše "Čísloo" místo "Číslo") a najednou místo toho, abychom provedli třikrát tu samou operaci, provedeme dvakrát jednu a potřetí trochu jinou.

Proto je lepší se zamyslet, co se při každém opakování mění a co naopak zůstává stejné, a zaznamenat to pomocí tzv. for-cyklu (angl. for-loop):

In [372]:
for i in [0, 1, 2]:     # hlavička
    print("Číslo:", i)  # tělo
Číslo: 0
Číslo: 1
Číslo: 2

Takový for-cyklus můžeme parafrázovat následovně: každé číslo ze seznamu [0, 1, 2] postupně ulož do proměnné i a proveď sérii operací popsaných v těle cyklu. Zkráceně: pro každé (angl for each) číslo ze seznamu proveď následující operaci/operace.

Hlavička for-cyklu popisuje proměnlivou část akce, kterou provádíme (zde: proměnná i postupně nabývá různých hodnot, které čerpáme ze seznamu). Tělo for-cyklu popisuje repetitivní část (zde: pokaždé proměnnou i vytiskneme, spolu s prefixem "Číslo: "). Podobně jako u funkcí poznáme to, co ještě patří do těla for-cyklu, a to, co už ne, podle odsazení:

In [373]:
for i in [0, 1, 2]:
    print("Číslo:", i)
    print("Já ještě do těla cyklu patřím!")

print("Já už ne :(")
Číslo: 0
Já ještě do těla cyklu patřím!
Číslo: 1
Já ještě do těla cyklu patřím!
Číslo: 2
Já ještě do těla cyklu patřím!
Já už ne :(

Obecně můžeme for-cyklus charakterizovat takto:

for item in iterable:
    # do something with item

Proměnná iterable může obsahovat jakýkoli iterovatelný objekt (= objekt, ze kterého lze tahat další objekty, viz odd. Kolekce). Seznamy už jsme v roli iterable viděli v akci výše, n-tice fungují stejně. Řetězce taky, přičemž ve for-cyklu je procházíme znak po znaku:

In [374]:
řetězec = "abc"

for znak in řetězec:
    print(znak)
a
b
c

Množiny fungují podobně, jen není zaručené pořadí, v němž budeme prvky vytahovat:

In [375]:
množina = {2, 3, 1}

for prvek in množina:
    print(prvek)
1
2
3

U slovníků je to trochu složitější -- záleží, zda chceme procházet klíče...

In [376]:
slovník = {"a": 1, "b": 2, "c": 3}

for klíč in slovník:
    print(klíč)
a
b
c
In [377]:
# nebo též explicitněji
for klíč in slovník.keys():
    print(klíč)
a
b
c

... hodnoty...

In [378]:
for hodnota in slovník.values():
    print(hodnota)
1
2
3

... nebo obojí zároveň:

In [379]:
# jak přesně tato syntax funguje si vysvětlíme o kousek níž
for klíč, hodnota in slovník.items():
    print(f"Pod klíčem {klíč!r} je uložená hodnota {hodnota}.")
Pod klíčem 'a' je uložená hodnota 1.
Pod klíčem 'b' je uložená hodnota 2.
Pod klíčem 'c' je uložená hodnota 3.

Ale iterovatelný objekt nerovná se nutně jen kolekce. V kombinaci s for-cyklem se často hodí funkce range:

In [380]:
for i in range(3):
    print("Číslo:", i)
Číslo: 0
Číslo: 1
Číslo: 2

Funkce range vytvoří objekt, který v hlavičce for-cyklu postupně generuje čísla v zadaném rozpětí (v našem případě od 0 do 3, horní hranici vyjímaje). Proč je to užitečné? Představte si, že bychom chtěli operaci print("Číslo:", i) provést pro prvních milion nezáporných čísel. Oproti seznamu má funkce range dvě výhody:

  • pro nás je výhoda, že nemusíme ručně vypisovat seznam s milionem čísel
  • pro počítač je výhoda, že nemusí vytvářet celý seznam najednou a pak ho uchovávat v paměti (milion čísel sice dnešní počítače zvládnou hravě, ale obecně platí, že paměti nikdy není neomezeně) -- čísla tiskne jedno po druhém a jakmile jedno zpracuje, tak ho může zapomenout

Když proměnnou z hlavičky for-cyklu nikde v těle nepoužijeme, bývá zvykem jí dát speciální jméno _, čímž ostatním programátorům naznačíme, že se nemají divit, že není použitá.

In [381]:
# tři hody kostkou
for _ in range(3):
    print(randint(1, 6))
2
5
5

Občas se stane, že potřebujeme for-cyklem projít kolekci, jejímiž prvky jsou n-tice nebo seznamy, které v rámci for-cyklu potřebujeme dále rozebrat na dílčí prvky. Můžeme samozřejmě použít indexaci:

In [382]:
# Větu "Prší." máme reprezentovanou jako seznam dvou tokenů. Každý token
# je trojice řetězců: první označuje slovní tvar, druhý lemma (=
# slovníkovou podobu), třetí slovní druh (V = sloveso, Z = interpunkce).
věta = [("Prší", "pršet", "V"), (".", ".", "Z")]

for token in věta:
    word = token[0]
    lemma = token[1]
    tag = token[2]
    print(f"WORD: {word!r}, LEMMA: {lemma!r}, TAG: {tag!r}")
WORD: 'Prší', LEMMA: 'pršet', TAG: 'V'
WORD: '.', LEMMA: '.', TAG: 'Z'

Práci nám ale může ušetřit tzv. destrukturace (angl. destructuring, též se tomu někdy říká unpacking nebo pattern matching): máme-li uspořádanou kolekci (seznam nebo n-tici) o X prvcích, můžeme prvky rovnou namapovat na X proměnných:

In [383]:
a, b, c = [1, 2, 3]
In [384]:
a
Out[384]:
1
In [385]:
b
Out[385]:
2
In [386]:
c
Out[386]:
3

Jak to funguje? Strukturní vzorec (pattern) nalevo od = obsahuje tři volné "sloty" (odpovídající třem proměnným). Python se tento vzorec pokusí přiložit na datovou strukturu napravo od = (představte si průhledný pauzovací papír), namapovat (match) objekty, které datová struktura obsahuje, na volné sloty, a strukturu tak rozebrat na dílčí prvky (proto hovoříme o destrukturaci nebo unpacking -- rozbalení).

Ne vždycky se to samozřejmě povede -- např. když je slotů víc než prvků...

In [387]:
a, b, c = (1, 2)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-387-24077ba23852> in <module>
----> 1 a, b, c = (1, 2)

ValueError: not enough values to unpack (expected 3, got 2)

... nebo naopak:

In [388]:
a, b = (1, 2, 3)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-388-faee755feda6> in <module>
----> 1 a, b = (1, 2, 3)

ValueError: too many values to unpack (expected 2)

Strukturní vzorec může obsahovat proměnnou s hvězdičkou (viz odd. Funkce), pak na sebe tato proměnná naváže zbývající prvky kolekce:

In [389]:
a, *rest = (1, 2, 3, 4, 5, 6)
In [390]:
a
Out[390]:
1
In [391]:
rest
Out[391]:
[2, 3, 4, 5, 6]

Když je kolekce vnořená, můžeme strukturní vzorec namapovat jen na nejvyšší úroveň...

In [392]:
a, b = (1, (2, 3))
In [393]:
a
Out[393]:
1
In [394]:
b
Out[394]:
(2, 3)

... nebo může vzorec být taktéž vnořený:

In [395]:
a, (b, c) = (1, (2, 3))
In [396]:
a
Out[396]:
1
In [397]:
b
Out[397]:
2
In [398]:
c
Out[398]:
3

Když dáme destrukturaci dohromady s for-cyklem, zkrátí a zpřehlední se nám zápis:

In [399]:
for token in věta:
    word, lemma, tag = token
    print(f"WORD: {word!r}, LEMMA: {lemma!r}, TAG: {tag!r}")
WORD: 'Prší', LEMMA: 'pršet', TAG: 'V'
WORD: '.', LEMMA: '.', TAG: 'Z'

A dokonce můžeme ubrat ještě jeden řádek, protože destrukturaci lze provést přímo v rámci hlavičky for-cyklu:

In [400]:
for word, lemma, tag in věta:
    print(f"WORD: {word!r}, LEMMA: {lemma!r}, TAG: {tag!r}")
WORD: 'Prší', LEMMA: 'pršet', TAG: 'V'
WORD: '.', LEMMA: '.', TAG: 'Z'

Varianta s vnořeným strukturním vzorcem může nastat např. v případě, že si jednotlivé tokeny očíslujeme pomocí funkce enumerate, která ke každému prvku ve zdrojové kolekci přidá pořadové číslo. Takhle to vypadá, když si výsledek uložíme do seznamu:

In [401]:
list(enumerate(věta))
Out[401]:
[(0, ('Prší', 'pršet', 'V')), (1, ('.', '.', 'Z'))]

A takhle bychom ji spolu s vnořenou destrukturací mohli použít ve for-cyklu:

In [402]:
for i, (word, lemma, tag) in enumerate(věta):
    print(f"{i + 1}. WORD: {word!r}, LEMMA: {lemma!r}, TAG: {tag!r}")
1. WORD: 'Prší', LEMMA: 'pršet', TAG: 'V'
2. WORD: '.', LEMMA: '.', TAG: 'Z'

V prvním opakování for-cyklu provede Python tuto destrukturaci...

In [403]:
i, (word, lemma, tag) = (0, ("Prší", "pršet", "V"))

... v druhém pak tuto:

In [404]:
i, (word, lemma, tag) = (1, (".", ".", "Z"))

Na závěr příklad použití destrukturace s hvězdičkou -- může se hodit, když jsou jednotlivé dílčí kolekce, které postupně ve for-cyklu zpracováváme, různě dlouhé:

In [405]:
for i, *rest in [(1,), (2, 3), (4, 5, 6)]:
    print(i)
    print(rest)
1
[]
2
[3]
4
[5, 6]

Občas se v rámci for-cyklů můžou hodit klíčová slovíčka break a continue. break okamžitě ukončí for-cyklus. Následující for-cyklus by měl sice teoreticky projít čísla od 0 do 4, ale zastaví se u čísla 3:

In [406]:
for i in range(5):
    # syntax podmínek viz odd. Podmínky
    if i == 3:
        break
    print(i)
0
1
2

continue ukončí současnou iteraci for-cyklu a zahájí další. Následující for-cyklus tedy projde čísla od 0 do 4, ale vytiskne jen ta lichá, protože u sudých se k řádku s funkcí print vůbec nedostane:

In [407]:
for i in range(5):
    if i % 2 == 0:
        continue
    print(i)
1
3

POZN.: V tělech for-cyklů jsme všude použili funkci print, abychom mohli nahlédnout do jejich průběhu, tj. do toho, jak se jednotlivé operace opakují. Bez funkce print to jde samozřejmě taky, jen se nám nedostane žádné vizuální zpětné vazby, která by nám pomohla pochopit, co se po spuštění for-cyklu odehrává:

In [408]:
num = 0

for x in range(101):
    num += x
In [409]:
num
Out[409]:
5050

Jen ještě doplním, že elegantněji můžeme čísla od 0 do 100 sečíst pomocí funkce sum:

In [410]:
sum(range(101))
Out[410]:
5050

Logika

V Pythonu lze pracovat i s logickými výroky. Základem pro to jsou konstanty True a False, které označují pravdu, resp. nepravdu, a které Python vrací, když posuzuje pravdivost nějakého logického výroku:

In [411]:
# rovnost
2 + 2 == 4
Out[411]:
True
In [412]:
list("abc") == sorted("cab")
Out[412]:
True
In [413]:
# negace rovnosti
2 + 2 != 5
Out[413]:
True
In [414]:
# přítomnost prvku v kolekci
"a" in "abc"
Out[414]:
True
In [415]:
# přítomnost celého čísla v rozpětí celých čísel
7 in range(5, 10)
Out[415]:
True
In [416]:
# identita objektu
jan = bratr = ["nohy", "trup", "hlava", "vlasy"]
jan is bratr
Out[416]:
True
In [417]:
# různé typy nerovností -- menší než
2 < 3
Out[417]:
True
In [418]:
# menší nebo rovno
3 <= 3
Out[418]:
True
In [419]:
# větší
2 > 1
Out[419]:
True
In [420]:
# větší nebo rovno
1 >= 1
Out[420]:
True
In [421]:
1 < 3 > 2
Out[421]:
True
In [422]:
# výroky o řetězcích
"abc".islower()
Out[422]:
True

A tak podobně, viz příklady operací, které vrací True nebo False, v předchozích oddílech.

Pravdivostní hodnotu ale mají všechny objekty v Pythonu. Můžeme ji zjistit pomocí funkce bool:

In [423]:
bool("abc")
Out[423]:
True

Pravdivé jsou skoro všechny objekty kromě:

In [424]:
# čísla 0
bool(0)
Out[424]:
False
In [425]:
# prázdných kolekcí
bool([])
Out[425]:
False
In [426]:
bool({})
Out[426]:
False
In [427]:
bool("")
Out[427]:
False
In [428]:
# None
bool(None)
Out[428]:
False
In [429]:
# a pochopitelně samotné konstanty False
bool(False)
Out[429]:
False

Sestavovat z jednoduchých výroků složitější lze pomocí operátorů and, or a not:

In [430]:
# x and y platí, když jsou oba výroky x a y pravdivé
True and True
Out[430]:
True
In [431]:
True and False
Out[431]:
False
In [432]:
# x or y platí, když je aspoň jeden z výroků x a y pravdivý
True or True
Out[432]:
True
In [433]:
True or False
Out[433]:
True
In [434]:
False or False
Out[434]:
False
In [435]:
# not invertuje pravdivostní hodnotu výroku
not True
Out[435]:
False
In [436]:
not False
Out[436]:
True

V praxi použijeme třeba následovně:

In [437]:
řetězec = "kočka"

len(řetězec) > 3 and "č" in řetězec
Out[437]:
True

Jak jsme psali výše, pravdivostní hodnotu mají všechny objekty v Pythonu, můžeme tedy klidně napsat:

In [438]:
not "abc"
Out[438]:
False
In [439]:
True or 0
Out[439]:
True

Díky tomu můžeme trochu upřesnit chování operátorů and a or. and začne vyhodnocovat výrazy (zleva doprava) a vrátí buď první nepravdivý, nebo poslední zbývající:

In [440]:
# poslední zbývající (pravdivý)
True and 1 and "abc" and sorted
Out[440]:
<function sorted(iterable, /, *, key=None, reverse=False)>
In [441]:
# první nepravdivý
True and 1 and "abc" and sorted and [] and 0
Out[441]:
[]

or taky začne vyhodnocovat výrazy (zleva doprava) a vrátí buď první pravdivý, nebo poslední zbývající:

In [442]:
# první pravdivý
0 or [] or False or 42 or "abc"
Out[442]:
42
In [443]:
# poslední zbývající (nepravdivý)
0 or [] or False or {}
Out[443]:
{}

Jakmile and nebo or narazí na výraz, podle kterého se může rozhodnout, další už nevyhodnocuje. Můžeme si to ukázat, když jeden z výrazu bude volání funkce, jejímž vedlejším efektem je, že něco vytiskne na obrazovku.

In [444]:
def funkce():
    print("volám funkci...")
    return "výsledek funkce"
In [445]:
# zde může and vrátit hned první výraz, takže se funkce nezavolá
0 and funkce()
Out[445]:
0
In [446]:
# zde je potřeba po prvním výrazu pokračovat v ověřování pravdivostních
# hodnot, takže se funkce zavolá
1 and funkce()
volám funkci...
Out[446]:
'výsledek funkce'

Pomocí or tedy můžeme např. nahradit metodu setdefault (viz odd. Kolekce -- Slovníky) -- úpravu slovníku provedeme pouze v případě, že daný klíč ještě neobsahuje:

In [447]:
slovník = dict(a=1, b=2)
In [448]:
# "a" už ve slovníku je, první výraz tedy platí a druhý se ani
# nevyhodnotí
"a" in slovník or slovník.update(a=42)
Out[448]:
True
In [449]:
slovník
Out[449]:
{'a': 1, 'b': 2}
In [450]:
# "c" ve slovníku není, první výraz tedy neplatí a druhý se
# vyhodnotí
"c" in slovník or slovník.update(c=42)
In [451]:
slovník
Out[451]:
{'a': 1, 'b': 2, 'c': 42}

Máme-li kolekci objektů a chceme ověřit, zda je aspoň jeden z nich pravdivý, můžeme použít zabudovanou funkci any:

In [452]:
any([True, False, False])
Out[452]:
True

Chceme-li ověřit, zda jsou všechny pravdivé, použijeme zabudovanou funkci all:

In [453]:
all([True, True, True])
Out[453]:
True

any a all jsou velmi užitečné v kombinaci s konvertory kolekcí (viz odd. Konvertory kolekcí).

Varování na závěr -- složitější logické výroky jsou zrádné, jejich úpravy jsou netriviální a řídí se přesně danými pravidly. Kdo si je podobně jako já ze střední školy pamatuje spíš matně, měl by si dát extra pozor ;) Např. výrok not (x and y) je ekvivalentní výroku not x or not y, ne výroku not x and not y, jak bychom možná mohli mít tendenci naivně "roznásobit".

Složitější případy si naštěstí vždy lze pro jistotu ověřit pomocí pravdivostní tabulky, ať už si ji nakreslíme ručně nebo si rovnost výroků pro všechny možné kombinace hodnot x a y zkontrolujeme pomocí Pythonu:

In [454]:
for x in [True, False]:
    for y in [True, False]:
        #         ↓ výroky, jejichž rovnost ověřujeme ↓
        is_equal = (not (x and y)) == (not x or not y)
        print(f"x is {x}, y is {y}, the equality is {is_equal}")
x is True, y is True, the equality is True
x is True, y is False, the equality is True
x is False, y is True, the equality is True
x is False, y is False, the equality is True

Aby rovnost dvou výrazů platila obecně, musí platit pro každou variantu dosazení konkrétních hodnot za proměnné (zde x a y). O tom se můžeme přesvědčit buď ověřením výstupů funkce print (viz předchozí buňka), nebo můžeme kód taky upravit jako konvertor kolekce v kombinaci s funkcí all a dostaneme tak celkový výsledek rovnou:

In [455]:
all(
    (not (x and y)) == (not x or not y)
    for x in [True, False]
    for y in [True, False]
)
Out[455]:
True

A ještě pro srovnání, jak by vypadaly výstupy v případě, že si porovnávané výrazy obecně vzato rovné nejsou (všimněte si, že pro některé kombinace hodnot x a y rovnost platí, ale ne pro všechny)...

In [456]:
for x in [True, False]:
    for y in [True, False]:
        is_equal = (not (x and y)) == (not x and not y)
        print(f"x is {x}, y is {y}, the equality is {is_equal}")
x is True, y is True, the equality is True
x is True, y is False, the equality is False
x is False, y is True, the equality is False
x is False, y is False, the equality is True

... takže celkový verdikt je...

In [457]:
all(
    (not (x and y)) == (not x and not y)
    for x in [True, False]
    for y in [True, False]
)
Out[457]:
False

Podmínky: if, elif, else (a while-cyklus)

Na logických výrocích lze dál stavět větvení programu pomocí podmínek:

In [458]:
if 2 + 2 == 4:                   # hlavička
    print("matematika funguje")  # tělo
matematika funguje

Syntax podmínek je znovu postavená na principu hlavičky a odsazeného těla, které se vykoná, pokud podmínka uvedená v hlavičce platí.

if je jako výhybka: platí-li podmínka, vydá se program trochu jinou cestou, než když neplatí. Alternativních podmíněných cest může být více, napojíme je pomocí klíčového slovíčka elif:

In [459]:
num = 2

if num == 0:
    print("nula")
elif num == 1:
    print("jedna")
elif num == 2:
    print("dva")
dva

Na závěr můžeme přidat ještě jednu nepodmíněnou alternativní cestu pomocí klíčového slovíčka else. Tou se programu vydá, když se ukáže, že ani jedna z předchozích podmínek neplatí:

In [460]:
num = 3

if num == 0:
    print("nula")
elif num == 1:
    print("jedna")
elif num == 2:
    print("dva")
else:
    print("něco jiného")
něco jiného

Je důležité si uvědomit, že větvení skutečně funguje jako výhybka: program se vydá první cestou, kde narazí na pravdivou podmínku, a zbývající ignoruje, i kdyby byly nakrásně pravdivé taky:

In [461]:
if True:
    print("podmínka u téhle větve je vždy pravdivá")
elif True:
    print("u téhle taky, ale není to nic platné, je až druhá")
podmínka u téhle větve je vždy pravdivá

Vizuálně si to můžeme představit takto:

     .---> if
    /
   /-----> elif
  /
 --------> elif
  \
   \-----{ ...
    \
     `---> else

Důležitým důsledkem je, že záleží na pořadí podmínek. Pravidlo pravé ruky zní, že specifičtější podmínky by měly pro jistotu být na začátku, jinak se k nim program nemusí dostat, protože ho odchytí dřívější méně specifické podmínky.

Např. podmínka x >= 0 je méně specifická než podmínka x == 2, takže když bude před ní, sebere jí všechny potenciální zákazníky:

In [462]:
for x in range(4):
    if x >= 0:
        print(f"{x} je větší než 0")
    elif x == 2:
        print("HA! DVOJKA.")
0 je větší než 0
1 je větší než 0
2 je větší než 0
3 je větší než 0

Srovnejte s výstupem, když pořadí podmínek prohodíme:

In [463]:
for x in range(4):
    if x == 2:
        print("HA! DVOJKA.")
    elif x >= 0:
        print(f"{x} je větší než 0")
0 je větší než 0
1 je větší než 0
HA! DVOJKA.
3 je větší než 0

Každé klíčové slovíčko if odstartuje nové větvení, pod něž spadají případná následující elif a else na stejné úrovni odsazení:

In [464]:
num = 2

if num > 1:
    print("1. větvení: num je větší než jedna")
elif num < 1:
    print("1. větvení: num je menší než jedna")
else:
    print("1. větvení: num je rovno jedné")

if num == 2:
    print("2. větvení: num je rovno dvěma")
1. větvení: num je větší než jedna
2. větvení: num je rovno dvěma

Vizuálně si to můžeme představit takto:

    .---> if
   /
  /-----> elif
 /
--------> elif
 \
  \-----{ ...
   \
    `---> else

--------> if

Existuje též speciální dvoučlenný operátor if ... else, který jako výsledek vrátí jednu ze dvou možností podle toho, jak dopadne pravdivostní test:

výsledek_je_li_test_pravdivý if test else výsledek_je_li_test_nepravdivý

Konkrétní příklad bude asi srozumitelnější:

In [465]:
"A" if True else "B"
Out[465]:
'A'
In [466]:
"A" if False else "B"
Out[466]:
'B'

Díky tomuto operátoru máme k dispozici kompaktnější zápis, když chceme hodnotu nějaké proměnné stanovit na základě pravdivostního testu. Bez operátoru if ... else bychom např. museli psát:

In [467]:
lednička = {"puding", "sýr", "salát"}

if "puding" in lednička:
    reakce = "hurá, puding!"
else:
    reakce = "není puding :("

reakce
Out[467]:
'hurá, puding!'

Ale s pomocí operátoru if ... else stačí napsat:

In [468]:
reakce = "hurá, puding!" if "puding" in lednička else "není puding :("
reakce
Out[468]:
'hurá, puding!'

S podmínkami souvisí i jiná podoba cyklu, tzv. while-cyklus. While-cyklus se opakuje tak dlouho, dokud je podmínka v hlavičce pravdivá:

In [469]:
i = 0

while i < 3:
    print("Číslo", i, "je menší než 3.")
    i += 1
Číslo 0 je menší než 3.
Číslo 1 je menší než 3.
Číslo 2 je menší než 3.

Konvertory kolekcí

Mnoho funkcí pracuje s kolekcemi, např. zabudovaná funkce sorted nebo konstruktor FreqDist z knihovny nltk. Někdy je užitečné mít možnost kolekci trochu upravit ve chvíli, kdy ji takové funkci předáváme. K tomu přesně slouží konvertory kolekce.

Kamkoli můžeme dát normální kolekci...

In [470]:
věta = "Bylo nás pět .".split()
věta
Out[470]:
['Bylo', 'nás', 'pět', '.']
In [471]:
sorted(věta)
Out[471]:
['.', 'Bylo', 'nás', 'pět']

... můžeme propašovat místo ní i konvertor:

In [472]:
sorted(slovo for slovo in věta if slovo.islower())
Out[472]:
['nás', 'pět']

Jejich syntax připomíná syntax for-cyklu, jen jsou jednotlivé prvky přeskládané a možnosti jsou omezenější než v plném for-cyklu. Abychom se v jejich zápisu lépe zorientovali, využijeme toho, že uvnitř jakýchkoli závorek můžeme kód v Pythonu libovolně nasekat na řádky a přidat odsazení, aby se nám lépe četl:

In [473]:
sorted(
    slovo
    for slovo in věta
    if slovo.islower()
)
Out[473]:
['nás', 'pět']

Teď už je lépe vidět, že konvertor kolekce má tři části. Čtou se odprostředka:

convert(item)            # 3.
for item in collection   # 1.
if test(item)            # 2.

Popis jednotlivých fází vypadá následovně:

  1. Procházíme kolekci prvek po prvku.
  2. Nepovinně můžeme provést nějaký test; pokud ho aktuální prvek nesplní, bude z výsledku vyřazen. Tím můžeme zdrojovou kolekci profiltrovat.
  3. Nakonec spočítáme hodnotu, kterou za daný prvek ze zdrojové kolekce zařadíme do výsledku. Tato hodnota může být původní prvek samotný, může být vypočítaná na základě prvku, nebo s ním taky vůbec nemusí souviset.

Nejjednodušší konvertor kolekce, kterým kolekce projede nezměněná, vypadá takto:

item                    # 3.
for item in collection  # 1.
                        # 2. nic
In [474]:
sorted(
    slovo              # 3.
    for slovo in věta  # 1.
                       # 2.
)
Out[474]:
['.', 'Bylo', 'nás', 'pět']

Chceme-li místo slov samotných do výsledku zařadit n-tici (slovo, délka_slova), musíme upravit fázi 3:

In [475]:
sorted(
    (slovo, len(slovo))  # 3. úprava zde
    for slovo in věta    # 1.
                         # 2.
)
Out[475]:
[('.', 1), ('Bylo', 4), ('nás', 3), ('pět', 3)]

Chceme-li navíc zahodit všechna slova, která nesestávají z malých písmen, musíme doplnit fázi 2:

In [476]:
sorted(
    (slovo, len(slovo))  # 3.
    for slovo in věta    # 1.
    if slovo.islower()   # 2. úprava zde
)
Out[476]:
[('nás', 3), ('pět', 3)]

Konvertor kolekce lze vždy přeskládat na normální for-cyklus, např. ten bezprostředně předcházející:

In [477]:
pomocný_seznam = []
for slovo in věta:
    if slovo.islower():
        pomocný_seznam.append((slovo, len(slovo)))
sorted(pomocný_seznam)
Out[477]:
[('nás', 3), ('pět', 3)]

Naopak přeskládat for-cyklus na konvertor kolekce pokaždé nejde, protože for-cyklus poskytuje mnohem větší volnost. Výměnou za toto omezení poskytují konvertory kolekce oproti for-cyklům několik výhod:

  • úspornější syntax (nesnaží se pokrýt plnou flexibilitu for-cyklů)
  • při běhu programu zabírají méně času a paměti (např. není potřeba vytvářet žádné pomocné kolekce typu pomocný_seznam)
  • ve for-cyklu se kvůli jeho flexibilitě snadno může schovat mnohem větší množství chyb

Vztah konvertorů kolekcí k for-cyklům je tedy podobný jako vztah (nemodifikovatelných) n-tic k (modifikovatelným) seznamům: konvertory (a n-tice) jsou mnohem méně flexibilní, ale ve chvíli, kdy tu flexibilitu nepotřebujete, je dobré mít možnost se jí explicitně vzdát a uchránit se tak možných chyb, které by z ní mohly plynout.

Když není konvertor kolekce jediným argumentem funkce, je potřeba ho uzávorkovat...

In [478]:
sorted((slovo for slovo in věta if slovo.islower()), reverse=True)
Out[478]:
['pět', 'nás']

... jinak nás Python vyplísní:

In [479]:
sorted(slovo for slovo in věta if slovo.islower(), reverse=True)
  File "<ipython-input-479-bd2750ac97af>", line 1
    sorted(slovo for slovo in věta if slovo.islower(), reverse=True)
           ^
SyntaxError: Generator expression must be parenthesized

Existuje speciální syntax na to, když chceme výsledky z konvertoru kolekce nasypat do seznamu (angl. se tomuto zápisu říká list comprehension)...

In [480]:
[slovo for slovo in věta if slovo.islower()]
Out[480]:
['nás', 'pět']

... do množiny (angl. set comprehension)...

In [481]:
{len(slovo) for slovo in věta}
Out[481]:
{1, 3, 4}

... nebo do slovníku (angl. dict comprehension):

In [482]:
{slovo: len(slovo) for slovo in věta}
Out[482]:
{'Bylo': 4, 'nás': 3, 'pět': 3, '.': 1}

U ostatních kolekcí speciální syntax neexistuje, ale není proč si zoufat, jejich konstruktory většinou podporují inicializaci na základě zdrojového iterovatelného objektu, takže stačí vepsat konvertor kolekce do konstruktoru:

In [483]:
tuple(slovo.lower() for slovo in věta)
Out[483]:
('bylo', 'nás', 'pět', '.')
In [484]:
from nltk import FreqDist

FreqDist(slovo.lower() for slovo in věta)
Out[484]:
FreqDist({'bylo': 1, 'nás': 1, 'pět': 1, '.': 1})

Konvertory kolekcí jsou velmi užitečné v kombinaci s logickými funkcemi all a any (viz výše odd. Logika):

In [485]:
all(x < 10 for x in range(5))
Out[485]:
True
In [486]:
any(x == 2 for x in range(5))
Out[486]:
True
In [487]:
all(x == 2 for x in range(5))
Out[487]:
False

Konvertorům kolekce též říkáme generátorové výrazy (angl. generator expression), podle toho, že jejich výsledkem je generátor -- objekt, který generuje další objekty (na základě prvků původní kolekce). Když ho vytvoříme samostatně, můžeme si ho i prohlédnout:

In [488]:
gen = (slovo for slovo in věta)
In [489]:
gen
Out[489]:
<generator object <genexpr> at 0x7fea335dd7b0>
In [490]:
type(gen)
Out[490]:
generator

Generátory poslouží kdekoli, kde je potřeba iterovatelný objekt (podobně jako kolekce nebo funkce range). Navíc z nich lze prvky vytahovat po jednom pomocí zabudované funkce next:

In [491]:
next(gen)
Out[491]:
'Bylo'

Existují dvě široké kategorie iterovatelných objektů:

  1. kolekce
  2. a něco, co bychom mohli souhrnně nazvat potenciální či virtuální kolekce (sem patří generátory, výstupy funkcí range, reversed, enumerate apod.)

Rozdíl mezi reálnou kolekcí o milionu prvků a potenciální kolekcí o milionu prvků je v tom, že v případě reálné kolekce musí celý milion prvků zároveň fyzicky existovat v paměti počítače. U potenciální kolekce stačí mít recept, jak ten milion prvků vytvořit... až budou potřeba.

Často nepotřebujeme všechny prvky kolekce najednou, stačí nám je zpracovávat jeden po druhém. Pak jsou potenciální kolekce ideální -- zabírají mnohem méně paměti.

Jindy ale všechny prvky najednou potřebujeme, typicky když si je chceme prohlédnout. Pak nám potenciální kolekce moc neposlouží:

In [492]:
(slovo for slovo in věta)
Out[492]:
<generator object <genexpr> at 0x7fea2bf89510>
In [493]:
enumerate(slovo for slovo in věta)
Out[493]:
<enumerate at 0x7fea2bf86c80>
In [494]:
reversed([1, 2, 3])
Out[494]:
<list_reverseiterator at 0x7fea2bfbfd30>
In [495]:
range(-2, 2)
Out[495]:
range(-2, 2)

Řešení je naštěstí jednoduché -- stačí potenciální kolekci donutit, aby vygenerovala všechny prvky, které v ní dřímají, a uložit je do reálné kolekce, např. do seznamu:

In [496]:
list(slovo for slovo in věta)
Out[496]:
['Bylo', 'nás', 'pět', '.']
In [497]:
list(enumerate(slovo for slovo in věta))
Out[497]:
[(0, 'Bylo'), (1, 'nás'), (2, 'pět'), (3, '.')]
In [498]:
list(reversed([1, 2, 3]))
Out[498]:
[3, 2, 1]
In [499]:
list(range(-2, 2))
Out[499]:
[-2, -1, 0, 1]

Drobnosti

Práce se soubory

Nejjednodušeji se v Pythonu pracuje se soubory v podobě tzv. čistého textu (angl. plain text; často mívají příponu .txt).

K otevření souboru slouží zabudovaná funkce open. Chceme-li do souboru zapisovat, je potřeba specifikovat mód w (jako write):

In [500]:
text = "kočka leze dírou\npes oknem"

with open("kočka.txt", "w", encoding="utf-8") as file:  # hlavička
    file.write(text)                                    # tělo

Argument encoding je nepovinný, Python ho automaticky stanoví na základě nastavení vašeho operačního systému (v podstatě všude kromě Windows to bude UTF-8). Vzhledem k tomu, že vaše první volba kódování by vždy měla být UTF-8 (viz notebook unicode.ipynb), je dobré si zvyknout ho vypisovat explicitně a nenechat operační systém rozhodovat za vás.

Syntaxi s klíčovým slovíčkem with se říká context manager. V hlavičce zavoláme funkci open a její výsledek uložíme do proměnné file, která reprezentuje otevřený soubor. Tělo je pak kontext, v jehož rámci s tímto otevřeným souborem můžeme pracovat (v našem případě do něj zapisovat). Jakmile kontext skončí (= zrušíme odsazení), Python za nás soubor automaticky zase zavře, aniž bychom museli ručně volat metodu file.close(). Co víc, pokud v rámci těla nastane nějaký problém (chyba), tak Python soubor taky zavře, ještě než zkolabuje, abychom po sobě nenechali nepořádek.

Když chceme soubor znovu načíst, můžeme při jeho otevírání specifikovat mód r (jako read), ale nemusíme, protože je to default. Při načítání máme tři možnosti -- buď načteme celý soubor najednou jako jeden dlouhý řetězec...

In [501]:
with open("kočka.txt", encoding="utf-8") as file:
    text = file.read()

text
Out[501]:
'kočka leze dírou\npes oknem'

... nebo celý soubor najednou jako seznam řádků...

In [502]:
with open("kočka.txt", encoding="utf-8") as file:
    lines = file.readlines()

lines
Out[502]:
['kočka leze dírou\n', 'pes oknem']

... nebo ho můžeme zpracovávat řádek po řádku pomocí for-cyklu:

In [503]:
with open("kočka.txt", encoding="utf-8") as file:
    for line in file:
        print(line, end="")
kočka leze dírou
pes oknem

Poslední varianta se hodí zejména v případě, že máme velký textový soubor, který nepotřebujeme (a tím pádem ani nechceme) načítat celý najednou do paměti.

Někdy jsou plaintextové soubory strukturované, takže načítat je řádek po řádku není úplně nejlepší způsob, jak s nimi pracovat. Např. jupyterovské notebooky jsou ve formátu JSON, který se snaží reprezentovat základní datové typy (čísla, řetězce, seznamy a slovníky) tak, aby šly vyměňovat mezi různými programovacími jazyky.

Když načteme tento notebook jedním ze standardních způsobů popsaných výše, tahle struktura se nám nevyjeví:

In [504]:
with open("python_crash_course.ipynb", encoding="utf-8") as file:
    nb = file.readlines()

# prvních deset řádků souboru s notebookem
nb[:10]
Out[504]:
['{\n',
 ' "cells": [\n',
 '  {\n',
 '   "cell_type": "code",\n',
 '   "execution_count": 1,\n',
 '   "metadata": {},\n',
 '   "outputs": [\n',
 '    {\n',
 '     "data": {\n',
 '      "text/html": [\n']

Vypadne na nás jen seznam řetězců (= řádků). Podle jejich obsahu se zdá, že tam nějaká struktura bude (vidíme odsazení, uvozovky, hranaté a složené závorky), ale nemůžeme s ní nijak pracovat, jediné, co máme k dispozici, jsou řádky textu.

Na rozpoznání struktury většiny běžných plaintextových formátů (tzv. parsování) naštěstí existují knihovny:

In [505]:
import json

with open("python_crash_course.ipynb", encoding="utf-8") as file:
    nb = json.load(file)

Pomocí knihovny json za nás Python naparsuje strukturu textového souboru a převede ji do podoby, s níž umíme pracovat (vnořené kolekce):

In [506]:
type(nb)
Out[506]:
dict
In [507]:
nb.keys()
Out[507]:
dict_keys(['cells', 'metadata', 'nbformat', 'nbformat_minor'])
In [508]:
nb["cells"][0]["source"]
Out[508]:
['from utils import TableOfContents\n',
 '\n',
 'TableOfContents("python_crash_course.ipynb")']

A tak podobně.

Pokud budete někdy potřebovat v Pythonu pracovat s jinými typy souborů než s čistým textem (např. Excel, Word atp.), na 99 % půjde vygooglit nějakou knihovnu, která vám s tím pomůže. Třeba s excelovými tabulkami se dobře pracuje pomocí knihovny pandas.

Výrazy vs. příkazy

Někdy je užitečné mít jemnější terminologické rozlišení pro různé části kódu.

Výraz (angl. expression) je jakýkoli kousek pythonovského kódu, který lze vyhodnotit a jeho výsledek uložit do proměnné. Jinými slovy, výraz je cokoli, co můžeme napsat napravo od operátoru =. Všechno ostatní v Pythonu jsou příkazy (angl. statement).

Příklady:

Hlavička for-cyklu (for x in y:) je příkaz -- sama o sobě nemá žádný výsledek, který by šel uložit do proměnné. Klíčové slovíčko for ale může být i součástí výrazu, konkrétně generátorového výrazu, jehož výsledkem je generátor:

In [509]:
(x for x in range(3))
Out[509]:
<generator object <genexpr> at 0x7fea2bfebf90>

Podobný rozdíl existuje mezi hlavičkou podmínkového větvení (if x:, elif y: i else: jsou všechno příkazy) a operátorem if ... else, který je součástí výrazu:

In [510]:
42 if False else 0
Out[510]:
0

A do třetice všeho dobrého: přiřazování proměnné (např. x = 3) je příkaz tvořený:

  1. jménem proměnné (zde x)
  2. přiřazovacím operátorem =
  3. a libovolným výrazem (zde číselný literál 3)

Dokumentace

Dokumentace programu slouží k tomu, aby byl čitelný nejen pro Python, ale i pro nás a další programátory. To je důležité, protože když je program nesrozumitelný, těžko se upravuje / opravuje.

Složitější místa v kódu si zaslouží komentář. Je ale zbytečné komentovat každou operaci, zejména když je její záměr i výsledek naprosto jasný už z kódu:

In [511]:
# sečíst dvě jedničky a výsledek uložit do proměnná dva
dva = 1 + 1

Funkce, které nejsou jen na jedno použití, by měly mít dokumentační řetězec (angl. docstring).

In [512]:
def funkce():
    """Toto je dokumentační řetězec.
    
    První řádek by měl obsahovat stručný popis, k čemu funkce
    slouží. Další mohou obsahovat detaily, charakteristiku
    parametrů funkce atp.
    
    """
    pass

Dokumentační řetězec k libovolné funkci si lze zobrazit pomocí zabudované funkce help...

In [513]:
help(funkce)
Help on function funkce in module __main__:

funkce()
    Toto je dokumentační řetězec.
    
    První řádek by měl obsahovat stručný popis, k čemu funkce
    slouží. Další mohou obsahovat detaily, charakteristiku
    parametrů funkce atp.

... nebo v prostředí Jupyter pomocí ?:

In [514]:
funkce?
Signature: funkce()
Docstring:
Toto je dokumentační řetězec.

První řádek by měl obsahovat stručný popis, k čemu funkce
slouží. Další mohou obsahovat detaily, charakteristiku
parametrů funkce atp.
File:      ~/src/dlukes.github.io/content/notebooks/<ipython-input-512-8724e3419bb6>
Type:      function

Inspiraci, jak psát užitečné docstringy, doporučuju hledat u funkcí, které sami používáte :)

In [515]:
sorted?
Signature: sorted(iterable, /, *, key=None, reverse=False)
Docstring:
Return a new list containing all items from the iterable in ascending order.

A custom key function can be supplied to customize the sort order, and the
reverse flag can be set to request the result in descending order.
Type:      builtin_function_or_method

Comments

comments powered by Disqus