Dnes budeme potřebovat do virtuálního prostředí nainstalovat tyto knihovny:

$ python -m pip install --upgrade pip
$ python -m pip install notebook numpy cython pytest pytest-profiling

Také je potřeba nainstalovat překladač jazyka C a hlavičkové soubory Pythonu:

  • Na Linuxu bude stačit nainstalovat balíčky gcc a python3-devel (Fedora) nebo python3-dev (Ubuntu/Debian).
  • Na Windows se řiďte instrukcemi pro vaši verzi Pythonu na Python wiki.

C API

Mluvíme-li o „Pythonu“, máme často na mysli jak jazyk samotný, tak i interpret, program, který programy v tomto jazyce umí spouštět. Správně je ale „Python“ pouze jméno jazyka. Interpretů tohoto jazyka je více, například:

  • CPython, referenční implementace napsaná v C; interpret, který spouštíme příkazem python3
  • PyPy, implementace zaměřená na rychlost, napsaná v Pythonu
  • MicroPython, implementace pro mikroprocesory a zařízení s minimem paměti
  • Jython, implementace napsaná v Javě, která umožňuje využívat javovské třídy
  • IronPython, napsaný v C#, s integrací do .NET
  • Batavia, Brython, pyjs – různé pokusy o integraci do JavaScriptu

Jednotlivé interprety se liší v detailech jako jsou přesnost reálných čísel, vypisování chybových hlášek, řazení záznamů ve slovnících nebo přístup k interním strukturám interpretu. Správně napsaný pythonní program by neměl na takových detailech záviset, pokud není k nekompatibilitě mezi interprety dobrý důvod.

Někdy to ale je potřeba, a dnešní přednáška bude specifická pro CPython a přímé využití jeho API pro jazyk C.

Rychlost

Častý důvod proč sáhnout k C API je rychlost: CPython je celkem pomalý. Tradiční metoda optimalizace je zjistit, které části jsou kritické, a přepsat je do C. Využijí se tak výhody obou jazyků: Python pro rychlý vývoj, snadné prototypování a přehlednost kódu, a C pro rychlost.

Když je náš program příliš pomalý, je potřeba ho optimalizovat. První krok k tomu je vždy zkontrolovat, co zabírá více času, než by mělo. K tomu se dá použít nástroj profile ze standardní knihovny, který vypíše tabulku počtu volání jednotlivých funkcí a času v nich stráveného:

$ python -m profile -s cumtime program.py

Profilovat běh pytest testů se dá jednoduše pomocí modulu pytest-profiling:

$ python -m pip install pytest-profiling
$ python -m pytest --profile

Když máme představu o tom, co nás brzdí, můžeme začít přepisovat do C způsoby popsanými níže.

Jiná možnost, jak program zrychlit, je ho pustit, tak jak je, pod interpretem PyPy, který obsahuje optimalizovaný překladač. To je ale jiná kapitola.

Externí knihovny

Druhý důvod, proč programátoři používají C API, je použití knihoven, které mají rozhraní pro C. Takových knihoven existuje mnoho – pokud není něco specifické pro určitý jazyk, často se to dá volat i z C.

Pro práci s externími knihovnami se dá použít C API nebo vestavěný modul ctypes, ale v dnešní době je dobré místo toho použít CFFI, knihovnu která funguje i s PyPy (a teoreticky jinými implementacemi).

CPython

Třetí důvod, proč použít C API, je práce s CPythonem samotným. Když člověk zabředne do složitého problému, může na CPython pustit C debugger jako gdb nebo Valgrind, prozkoumat potíže na nižší úrovni a zjistit, kde přesně se chyba nachází.

Modul v C

Pojďme začít příkladem. Vytvořte si následující soubor, který implementuje rozšíření (importovatelný modul) s jednou funkcí.

(Nebudeme chtít, abyste podobný kód uměli napsat, ale měli byste být schopní porozumět tomu, co dělá.)

demo.c:

#include <Python.h>

PyDoc_STRVAR(
    mod_docstring, 
    "Demo extension module with a Python wrapper for the system(3) function");

static PyObject *demo_system(PyObject *self, PyObject *args){
    const char *command;
    int retval;

    /* Parse the given arguments: expect one string, convert to char* */
    if (!PyArg_ParseTuple(args, "s", &command)) {
        /* Error handling: if PyArg_ParseTuple returns zero, return NULL */
        return NULL;
    }

    /* Call the C function */
    retval = system(command);

    /* Return result as Python int (error handling built in) */
    return PyLong_FromLong(retval);
}

/* List of all methods in the module */
static PyMethodDef DemoMethods[] = {
    {"system",  demo_system, METH_VARARGS,
            PyDoc_STR("Execute a shell command.")},
    {NULL, NULL, 0, NULL}        /* Sentinel */
};

/* Module specification */
static struct PyModuleDef demo_module = {
   PyModuleDef_HEAD_INIT,
   "demo",          /* name of module */
   mod_docstring,   /* dosctring (may be NULL) */
   0,               /* size of per-interpreter state of the module */
   DemoMethods,     /* list of methods */
};


/* Module entrypoint */
PyMODINIT_FUNC
PyInit_demo(void)
{
    return PyModuleDef_Init(&demo_module);
}

Z tohoto souboru by měla být patrná struktura podobných rozšíření: máme funkci (demo_system), která převádí objekty Pythonu na datové typy C, volá samotnou funkci a výsledek převádí zpět na pythonní objekt.

Dále máme pole záznamů o funkcích (DemoMethods), kde je ke každé funkci přiřazeno jméno, dokumentační řetězec a způsob volání (v našem případě METH_VARARGS, tedy volání s proměnným počtem nepojmenovaných argumentů, podobně jako bychom v Pythonu napsali def system(*args)).

Další potřebná proměnná, demo_module, obsahuje informace o modulu: jméno, dokumentační řetězec a seznam funkcí. Kdybychom potřebovali kromě funkcí definovat i třídy nebo konstanty, zde bychom pomocí slotů definovali funkci, která modul inicializuje, t.j. má podobnou funkci jako __init__ u třídy v Pythonu.

Poslední část je funkce PyInit, jediná která není definována jako static, takže jediná, která je exportována jako API knihovny, kterou vytváříme. Až bude Python tento modul importovat, najde tuto funkci podle jména, spustí ji a podle vrácené struktury typu PyModuleDef vytvoří pythonní objekt s modulem.

Překlad

Abychom mohli takovýto modul naimportovat, musíme ho nejdřív přeložit a sestavit z něj sdílenou knihovnu – soubor .so (nebo .dll) – s názvem modulu: buď jen demo.so, nebo i s identifikací architektury a verze Pythonu, např. demo.cpython-35m-x86_64-linux-gnu.so. (Výhoda delších názvů je v tom, že v jednom adresáři může být víc modulů pro různé architektury a že se Python nebude snažit načíst nekompatibilní moduly.)

Překlad je nutné provést se správnými přepínači a volbami, nejlépe takovými, s jakými byl sestaven samotný Python.

Pro zjednodušení tohoto procesu můžeme použít setuptools: do nám už známého souboru setup.py přidáme argument ext_modules se seznamem rozšiřovacích modulů. Podrobný popis třídy Extension je v dokumentaci; nám bude stačit jen jméno a seznam zdrojových souborů:

setup.py:

from setuptools import setup, Extension

module1 = Extension(
    'demo',
    sources=['demo.c'],
)

setup(
    name = 'demo',
    version = '0.1',
    description = 'Demo package',
    ext_modules = [module1]
)

Příkazy python setup.py sdist a python setup.py install budou fungovat jako normálně, jen je na instalaci potřeba překladač jazyka C.

Aby uživatelé překladač mít nemuseli, můžeme nainstalovat knihovnu wheel (python -m pip install wheel) a pak příkazem python setup.py bdist_wheel vygenerovat tzv. wheel archiv, např. dist/demo-0.1-cp35-cp35m-linux_x86_64.whl. Tento archiv jde nahrát na PyPI a následně nainstalovat, ovšem jen na architektuře a verzi Pythonu, pro které byl vytvořen.

Existuje způsob, jak vytvořit co nejvíce platformě nezávislý linuxový wheel. Jedná se o platformu nazvanou manulinux1, což je ve zkratce velmi stará verze Linuxu (CentOS 5), na které se wheely vytvoří, aby šly použít na různých novějších i relativně starých distribucích. Pro tvorbu wheelů se používá Docker obraz manylinux, vývojáři samozřejmě nepoužívají pro vývoj CentOS 5 (tedy většina ne).

Zajímavým nástrojem, který stojí za zmínku, je cibuildwheel. Zjednodušuje tvorbu wheelů pro Linux, macOS i Windows pomocí CI služeb Travis CI a AppVeyor.

Wheels jdou vytvářet i pro moduly tvořené jen pythonním kódem. Nejsou pak vázané na verzi a architekturu. Jejich výhoda oproti sdist archivům spočívá v tom, že se rychleji instalují.

Alternativa k instalaci, alespoň pro lokální vývoj, je rozšíření jen přeložit a dát do aktuálního adresáře (nebo jakéhokoli jiného adresáře, odkud se importují moduly). K tomu slouží příkaz python setup.py build_ext --inplace. Pozor na to, že po každé změně zdrojového kódu je potřeba rozšíření znovu přeložit.

Příkaz python setup.py develop bude fungovat jako dřív (používá build_ext --inplace), jen je opět potřeba příkaz po každé změně znovu spustit.

PyObject

Podívejme se teď na základní mechanismy interpretu CPython.

Základní datová struktura, která reprezentuje jakýkoli objekt Pythonu, je PyObject (dokumentace, definice). Skládá se ze dvou prvků:

typedef struct _object {
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

První je počet referencí (reference count), který se dá popsat jako počet míst, ze kterých je možné k tomuto objektu přistoupit. Když objekt uložíme do proměnné nebo do seznamu, zvýší se počet referencí o 1. Když seznam nebo proměnná zanikne (nebo náš objekt přepíšeme jiným), počet referencí se zase sníží. Když počet referencí dosáhne nuly, znamená to, že se k objektu už nedá dostat a Python ho uvolní z paměti.

Druhý prvek struktury PyObject je ukazatel na typ. Typ je pythonní objekt (class), který definuje chování třídy objektů: operátory, atributy a metody, které ten objekt má.

Struktura PyObject slouží jako hlavička, za kterou pak následují data interpretovaná podle typu daného objektu. Například pythonní objekt typu float vypadá následovně:

typedef struct {
    PyObject ob_base;
    double ob_fval;
} PyFloatObject;

...tedy struktura PyObject, za kterou je v paměti číselná hodnota.

Seznamy obsahují za hlavičkou např. velikost a (ukazatel na) pole ukazatelů na jednotlivé prvky. Podobně objekty typu int (které mají v Pythonu neomezený rozsah) mají délku a pole jednotlivých 30bitových „číslic“. NumPy matice mají metadata (velikost, typ, popis rozložení v paměti) a ukazatel na pole hodnot.

To základní, co potřebujeme vědět, je, že na úrovni C je každý pythonní objekt reprezentován jako struktura počtu referencí, ukazatele na typ a dat specifických pro daný typ.

Reference counting

Tak jako v C je důležité správně alokovat a dealokovat paměť, při tvorbě rozšíření do CPythonu je třeba správně pracovat s referencemi: ke každému Py_INCREF (přičtení 1 k počtu referencí) je potřeba později zavolat Py_DECREF (odečtení 1 a případné uvolnění objektu). Jakákoli práce s objektem se smí provádět jen mezi INCREF a příslušným DECREF.

Platí konvence, že argumenty funkcí se předávají jako tzv. borrowed reference: o počitadlo se stará volající a v průběhu volané funkce se objekt dá používat. Pokud bychom ale argument potřebovali i po skončení volané funkce (např. si ho uložíme do globální proměnné), je potřeba mu počitadlo zvýšit (a po skončení práce zase snížit).

V našem modulu demo přebíráme jako parametr n-tici. Zodpovědnost zavolat na tuto n-tici Py_DECREF má ale volající, ne my. Zavoláním funkce PyArg_ParseTuple získáme char*, který ale můžeme používat jen v rámci naší funkce: po jejím skončení může volající argumenty funkce uvolnit, a tím řetězec zrušit.

Funkce, které vracejí pythonní objekty, předpokládají, že na vrácenou hodnotu provede DECREF volající. V modulu demo voláme funkci PyLong_FromLong, která vytvoří nové pythonní číslo. Za vzniklou referenci naše funkce přebírá zodpovědnost, je tedy na nás, abychom se postarali o zavolání Py_DECREF. Vrácením výsledku tuto zodpovědnost ale předáváme na funkci, která volá tu naši.

Hodnoty a výjimky

Další konvence, kterou většina funkcí v C API dodržuje, je způsob vracení výjimek.

Funkce, které vrací pythonní objekty, na úrovni C vrací PyObject*. Nastane-li výjimka, objekt výjimky se zaznamená do globální (přesněji, thread-local) proměnné a funkce vrátí NULL.

V našem modulu demo voláme funkci PyArg_ParseTuple, která může vyvolat výjimku: typicky TypeError kvůli nesprávnému počtu nebo typu argumentů. V takovém případě tato funkce výjimku zaznamená a vrátí NULL. Naší funkci system už stačí vrátit NULL, protože víme, že výjimka už je zaznamenaná.

Další funkce, která může neuspět, je PyLong_FromLong. Vzhledem k tomu, že její výsledek rovnou vracíme, není potřeba úspěch kontrolovat – vrátíme buď správnou hodnotu nebo NULL se zaznamenanou výjimkou.

GIL

Poslední omezení, kterého si autor rozšíření musí být vědom, je Global Interpreter Lock. Stručně řečeno, s objekty PyObject* může pracovat pouze jedno vlákno. Toto vlákno drží globální zámek, který čas od času odemkne a znovu se pokusí zamknout, aby mohly běžet i ostatní vlákna.

Díky GIL je vícevláknové programování v Pythonu relativně bezpečné: nemůže např. nastat souběh (race condition), kdy by se nastavilo počitadlo referencí na špatnou hodnotu. Na druhou stranu tento zámek ale omezuje paralelismus, a tedy i rychlost programu.

Globální zámek se dá odemknout v situacích, kdy nepracujeme s PyObject* a nevoláme pythonní kód. Například čtení ze souboru nebo sítě ostatní vlákna neblokuje. Stejně tak maticové operace v NumPy typicky nedrží GIL zatímco počítají na úrovni C nebo Fortranu.

Cython

Teď, když víme jak to všechno funguje, se můžeme podívat na způsob, jak rozšíření psát jednoduše. C API se totiž dá použít nejen z C, ale z jakéhokoli jazyka, který umí volat funkce se stejnými konvencemi, např. C++ (s pomocí extern C). Další způsob, jak použít C API ale nepsat C, je použít překladač z příjemnějšího jazyka do C.

Jeden takový jazyk je Cython (neplést s CPython).

Cython je jazyk podobný Pythonu, který ale lze přeložit na C a dále optimalizovat.

Cython si nainstalujte pomocí příkazu:

$ python -m pip install cython

Kompilace Pythonu

Když chceme převést modul z Pythonu do Cythonu, nejjednodušší začátek je přejmenovat soubor .py na .pyx, aby bylo jasné, že jde o jiný jazyk, který nepůjde naimportovat přímo.

Jazyky Python a Cython nejsou 100% kompatibilní, ale zvláště u kódu, který pracuje hlavně s čísly, se nekompatibilita neprojeví. Vývojáři Cythonu považují každou odchylku od specifikace jazyka za chybu, kterou je nutno opravit.

Jako příklad můžete použít tuto naivní implementaci celočíselného a maticového násobení. Uložte si ji jako matmul.py:

import numpy

def intmul(a, b):
    result = a * b
    return result

def matmul(a, b):
    n = a.shape[0]
    m = a.shape[1]
    if b.shape[0] != m:
        raise ValueError('incompatible sizes')
    p = b.shape[1]
    result = numpy.zeros((n, p))
    for i in range(n):
        for j in range(p):
            for k in range(m):
                x = a[i, k]
                y = b[k, j]
                result[i, j] += x * y
    return result

Stáhněte si testy a zkontrolujte, že prochází.

Potom soubor přejmenujte na matmul.pyx.

Výsledek bychom mohli převést na C pomocí příkazu cython -3 matmul.pyx, čímž vznikne matmul.c. Ten můžeme přeložit výše uvedeným způsobem.

Jednodušší varianta je použít Cython v setup.py. Pro naše účely bude setup.py s Cythonem a NumPy vypadat takto:

from setuptools import setup
from Cython.Build import cythonize
import numpy

setup(
    name='matmul',
    ext_modules=cythonize('matmul.pyx', language_level=3),
    include_dirs=[numpy.get_include()],
    setup_requires=[
        'Cython',
        'NumPy',
    ],
    install_requires=[
        'NumPy',
    ],
)

V případě problémech s nefungujícím include_dirs na systému macOS použijte komplikovanější variantu:

from distutils.extension import Extension
...
ext_modules = cythonize([Extension('matmul', ['matmul.pyx'],
                                   include_dirs=[numpy.get_include()])],
                        language_level=3)

Po zadání python setup.py develop nebo python setup.py build_ext --inplace atp. se modul matmul.pyx zkompiluje s použitím nainstalovaného NumPy a bude připraven na použití. (Zkontrolujte, že testy prochází i se zkompilovaným modulem.)

Nevýhoda tohoto přístupu je, že k spuštění takového setup.py je již potřeba mít nainstalovaný cython a numpy. Instalace z archivu sdist se tedy nemusí povést – je potřeba uživatelům říct, že dané moduly už musí mít nainstalované. Tento problém aktuálně řeší PyPA (správci pip a setuptools).

Instalace z archivů wheel by měla být bezproblémová.

Anotace

Kód, který takto vznikne, není o moc rychlejší než původní Python. Je to tím, že sekvence příkazů ve funkci je sice převedená do C a přeložená do strojového kódu, ale každá operace pracuje s generickými pythonními objekty, takže musí pro každé číslo číslo z matice zkonstruovat pythonní objekt, vyhledat implementaci sčítání pro dvě celá čísla, a výsledek převést zpět na int64 a uložit do matice.

Na situaci se můžeme podívat pomocí přepínače --annotate:

$ cython -3 --annotate matmul.pyx

To vygeneruje soubor matmul.html, kde jsou potencionálně pomalé operace vysvíceny žlutě. Ke každému řádku se navíc dá kliknutím ukázat odpovídající kód v C (který bývá docela složitý, protože řeší věci jako zpětnou kompatibilitu a ošetřování chyb, a navíc používá hodně pomocných maker).

Obecně nebývá problém mít „žluté“ ty řádky, které se ve funkci provádí pouze jednou. Ale v cyklech, zvláště těch třikrát zanořených, se autor rozšíření typicky snaží žlutým řádkům vyhnout. Nejjednodušší způsob, jak toho docílit, je doplnění statických informací o typech.

Doplnění typů

Začneme u funkce intmul, kde doplníme informaci o tom, že parametry a a b a proměnná result jsou typu int. Parametrům stačí doplnit typ podobně jako v C, ostatní lokální proměnné potřebují definici pomocí příkazu cdef:

def intmul(int a, int b):
    cdef int result
    result = a * b
    return result

Teď bude funkce nepatrně rychlejší, ale také méně obecná: nejde jí násobit řetězec číslem, ale ani reálná čísla (float), a dokonce ani celá čísla, která se nevejdou do 64 bitů (příp. jiné velikosti, dle systému). Typ int v Cythonu je totiž int z C, ne ten neomezený z Pythonu.

Další věc, kterou můžeme udělat, je změnit příkaz def na cpdef a doplnit typ návratové hodnoty:

cpdef int intmul(int a, int b):
    cdef int result
    result = a * b
    return result

Tím se zbavíme nákladného převodu výsledku na PyObject. Bohužel ale toto zrychlení pocítíme, jen když takovou funkci zavoláme z jiné funkce napsané v Cythonu.

Tři typy funkcí

Funkce jdou deklarovat třemi způsoby:

  • def func(...): je funkce, která jde volat z Pythonu i z Cythonu, ale volání z Cythonu je pomalé (argumenty a výsledek se převádí na pythonní objekty a zpět),
  • cdef <type> func(...): je funkce, která jde volat pouze z Cythonu, ale volání je rychlé (pracuje se s C typy),
  • cpdef <type> func(...): je funkce, která se z Cythonu volá rychle, ale jde volat i z Pythonu (ve skutečnosti Cython vytvoří dva druhy této funkce).

Třídy

Cython umožňuje vytvářet tzv. built-in třídy: stejný druh tříd jako je např. str nebo int. Práce s takovými třídami je rychlejší, ale mají pevně danou strukturu. Ani jim ani jejich instancím nelze z Pythonu nastavovat nové atributy:

>>> "foo".bar = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'bar'

Příklad definice built-in třídy:

cdef class Foo:
    # Všechny členské proměnné musí být nadefinované tady
    cdef int foo
    ...

    def __cinit__(self, int f):
        # Inicializace třídy.
        # Cython zajistí, že se tato funkce zavolá pouze jednou (na rozdíl
        # od __init__, kterou lze z pythonního kódu zavolat kdykoli)
        self.foo = f
        ...

    def __dealloc__(self):
        # Deinicializace třídy
        ...

    cpdef int method(self):
        ...
        return self.foo

Více o definici tříd najdete v dokumentaci Cythonu.

Používání NumPy

Pro funkci matmul můžeme nadefinovat číselné proměnné (n, m, p, i, j, k, x, y) jako int, ale tím si moc nepomůžeme: většinu času program stráví vybíráním a ukládáním hodnot z/do matic, a protože Cython nemá informace o tom, že jsou to NumPy matice, používá obecný protokol pro pythonní kontejnery, takže se každá hodnota převede na pythonní objekt.

Je tedy potřeba říct Cythonu, že používáme NumPy matice. Naštěstí v NumPy existuje integrace s Cythonem, takže můžeme na úrovni C „naimportovat“ rozšíření pro NumPy:

cimport numpy

... a potom použít typ „dvourozměrná matice celých čísel“, který se v Cythonu jmenuje numpy.ndarray[numpy.int64_t, ndim=2]. Naše funkce tedy bude začínat takto:

cpdef numpy.ndarray[numpy.int64_t, ndim=2] matmul(
        numpy.ndarray[numpy.int64_t, ndim=2] a,
        numpy.ndarray[numpy.int64_t, ndim=2] b):
    cdef numpy.ndarray[numpy.int64_t, ndim=2] result
    ...

Kdybychom si nebyli jistí typem matice, můžeme si ho nadefinovat pomocí ctypedef:

ctypedef numpy.int64_t DATATYPE

...a pak používat tento alias. Na maticové typy bohužel typedef zatím nefunguje.

Pro práci s maticí ASCII znaků lze použít typ numpy.int8_t, ale je třeba při zapisování přímo na konkrétní pozice zapisovat číselný typ char:

cdef numpy.ndarray[numpy.int8_t, ndim=2]  directions = numpy.full((h, w), b'#', dtype=('a', 1))
directions[maze >= 0] = b' '  # Python level, using b' '
directions[1, 2] == ord('x')  # C level, using char

Použití matrix[a, b] je v Cythonu rychlejší než matrix[a][b], protože se uvnitř dějí jiné věci. Při použití matrix[a, b] u matice deklarované jako dvourozměrné pole nějakého typu Cython přistoupí přímo k obsahu na úrovni jazyka C. Při použití matrix[a][b] se ale dějí operace dvě, nejprve matrix[a] vrací jeden řádek matice a až poté [b] vrací jeden prvek z tohoto řádku. Obě operace probíhají na úrovni Pythonu a proto budou pomalejší a při použití --annotate bude řádek s takovou operací označen žlutě.

Direktivy

Anotací typů matic se naše demo maticového násobení dostalo skoro na úroveň C, ale ne úplně: řádky, které pracují s maticemi, jsou ve výstupu --annotate stále trochu žluté. Cython totiž při každém přístupu k matici kontroluje, jestli nečteme nebo nezapisujeme mimo pole a případně vyvolá IndexError.

Pokud víme – jako v našem případě – že je taková kontrola zbytečná, můžeme Cythonu říct, aby ji nedělal. Přístupy mimo pole pak způsobí nedefinované chování (většinou program spadne, nebo hůř, bude pracovat se špatnými daty). Kontrola se vypíná direktivou boundscheck, která se dá zadat dvěma hlavními způsoby: dekorátorem:

@cython.boundscheck(False)
cpdef funkce():
    ...

... nebo příkazem with:

with cython.boundscheck(False):
    ...

... případně i pro celý soubor, viz dokumnetace.

Další zajímavá direktiva je cython.wraparound(False), která podobným způsobem vypíná pythonní způsob indexování zápornými čísly: místo indexování od konce s ní dostaneme nedefinované chování.

Seznam dalších direktiv najdete v dokumentaci.

Cython podporuje ještě blok with cython.nogil:, který je podobný direktivám, ale dá se použít jen s with. V rámci tohoto bloku je odemčený GIL (globální zámek). Smí se použít, pouze pokud nepracujeme s pythonními objekty – například když operujeme jen na obsahu už existujících maticí. Opak je with cython.gil:, kterým zámek zase zamkneme – například když potřebujeme vyhodit výjimku.

Struktury, ukazatele a dynamická alokace

Přestože v Cythonu můžete používat pythonní n-tice, slovníky, seznamy a další podobné nehomogenní typy, jejich použití je pomalé, protože vždy pracují s pythonními objekty.

Pokud máte kód, který potřebuje dočasné pole takových záznamů, je pro časově kritické části kódu lepší k problému přistoupit spíše „céčkovsky“, přes alokaci paměti a ukazatele.

Následující příklad ukazuje, jak naplnit pole heterogenních záznamů:

# Import funkcí pro alokaci paměti – chovají se jako malloc() apod.
from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free

# Definice struktury
cdef struct coords:
    int row
    int column
    char data

MAXSIZE = ...

def path(...):
    # Definice ukazatele, přetypování
    cdef coords * path = <coords *>PyMem_Malloc(MAXSIZE*sizeof(coords))
    if path == NULL:
        # nedostatek paměti
        raise MemoryError()

    cdef int used = 0
    for ...:
        ...

        #
        path[used] = coords(row, column, data)
        used += 1

    # pole můžeme používat
    ...

    # a musíme ho před vrácením předělat na list
    lpath = []
    cdef int i
    for i in range(used):
        lpath.append(path[i])

    # a uvolnit
    PyMem_Free(path)
    return lpath

Pro homogenní pole ale doporučujeme spíše NumPy matice.

Následující příklad ukazuje, jak lze přiřazovat do struktur:

cdef struct coord:
    float x
    float y
    float z

cdef coord a = coord(0.0, 2.0, 1.5)

cdef coord b = coord(x=0.0, y=2.0, z=1.5)

cdef coord c

c.x = 42.0
c.y = 2.0
c.z = 4.0

cdef coord d = {'x':2.0,
                'y':0.0,
                'z':-0.75}

Použití knihoven z C

Pro použití C knihoven z Pythonu je lepší použít CFFI. Ale když už píšete kód v Cythonu a potřebujete zavolat nějakou C funkci, můžete to udělat takto:

cdef extern from "stdlib.h":
    int rand()
    void srand(long int seedval)

cdef extern from "time.h":
    ctypedef long time_t
    long int time(time_t *)

srand(time(NULL))
print(rand())

Deklarace můžete vložit přímo do .pyx souboru, ale pokud je chcete používat z různých míst, pojmenujte soubor .pxd, to vám umožní na něj použít cimport.

Pro části standardní knihovny jsou takové deklarace již v Cythonu předpřipravené, můžete tedy použít cimport rovnou:

from libc.stdlib cimport rand, srand
from libc.time cimport time

srand(time(NULL))
print(rand())

Zkratky: pyximport a %%cython

Pro interaktivní práci v Jupyter Notebook má Cython vlastní „magii“. Na začátku Notebooku můžeme zadat:

%load_ext cython

a potom můžeme na začátku kterékoli buňky zadat %%cython:

%%cython

cpdef int mul(int a, int b):
    return a * b

Kód v takové buňce pak Notebook zkompiluje Cythonem a funkce/proměnné v něm nadefinované dá k dispozici.

Můžeme použít i %%cython --annotate, což vypíše anotace přímo do Notebooku.

Další zkratka je modul pyximort, který dává možnost importovat moduly .pyx přímo: hledají se podobně jako .py nebo .so a před importem se zkompilují. Zapíná se to následovně:

import pyximport
pyximport.install()

import matmul

Video

Před nedávnem měl Miro na Středisku unixových technologií nahrávanou ukázku přepsání úlohy ruksaku z předmětu MI-PAA z Pythonu do Cythonu (včetně nepříjemného záseku a live ukázky debugování problému). Na video se můžete podívat, mohlo by vám prozradit spoustu tipů, které se vám mohou hodit ke splnění úlohy. K obsahu jen dodáme, že místo malloc a free je lepší použít PyMem_Malloc a PyMem_Free z ukázky výše.

{
  "data": {
    "sessionMaterial": {
      "id": "session-material:2017/pyknihovny-brno:cython:0",
      "title": "Cython",
      "html": "\n          \n    \n\n    <p>Dnes budeme pot&#x159;ebovat do virtu&#xE1;ln&#xED;ho prost&#x159;ed&#xED; nainstalovat tyto knihovny:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">$ </span>python -m pip install --upgrade pip\n<span class=\"gp\">$ </span>python -m pip install notebook numpy cython pytest pytest-profiling\n</pre></div><p>Tak&#xE9; je pot&#x159;eba nainstalovat p&#x159;eklada&#x10D; jazyka C\na hlavi&#x10D;kov&#xE9; soubory Pythonu:</p>\n<ul>\n<li>Na Linuxu bude sta&#x10D;it nainstalovat bal&#xED;&#x10D;ky <code>gcc</code>\na <code>python3-devel</code> (Fedora) nebo <code>python3-dev</code> (Ubuntu/Debian).</li>\n<li>Na Windows se &#x159;i&#x10F;te instrukcemi pro va&#x161;i verzi Pythonu\nna <a href=\"https://wiki.python.org/moin/WindowsCompilers\">Python wiki</a>.</li>\n</ul>\n<hr>\n<h1>C API</h1>\n<p>Mluv&#xED;me-li o &#x201E;Pythonu&#x201C;, m&#xE1;me &#x10D;asto na mysli jak jazyk samotn&#xFD;, tak i interpret,\nprogram, kter&#xFD; programy v tomto jazyce um&#xED; spou&#x161;t&#x11B;t.\nSpr&#xE1;vn&#x11B; je ale &#x201E;Python&#x201C; pouze jm&#xE9;no jazyka.\nInterpret&#x16F; tohoto jazyka je v&#xED;ce, nap&#x159;&#xED;klad:</p>\n<ul>\n<li>CPython, referen&#x10D;n&#xED; implementace napsan&#xE1; v C; interpret, kter&#xFD; spou&#x161;t&#xED;me p&#x159;&#xED;kazem <code>python3</code></li>\n<li>PyPy, implementace zam&#x11B;&#x159;en&#xE1; na rychlost, napsan&#xE1; v Pythonu</li>\n<li>MicroPython, implementace pro mikroprocesory a za&#x159;&#xED;zen&#xED; s minimem pam&#x11B;ti</li>\n<li>Jython, implementace napsan&#xE1; v Jav&#x11B;, kter&#xE1; umo&#x17E;&#x148;uje vyu&#x17E;&#xED;vat javovsk&#xE9; t&#x159;&#xED;dy</li>\n<li>IronPython, napsan&#xFD; v C#, s integrac&#xED; do .NET</li>\n<li>Batavia, Brython, pyjs &#x2013; r&#x16F;zn&#xE9; pokusy o integraci do JavaScriptu</li>\n</ul>\n<p>Jednotliv&#xE9; interprety se li&#x161;&#xED; v detailech jako jsou p&#x159;esnost re&#xE1;ln&#xFD;ch &#x10D;&#xED;sel,\nvypisov&#xE1;n&#xED; chybov&#xFD;ch hl&#xE1;&#x161;ek, &#x159;azen&#xED; z&#xE1;znam&#x16F; ve slovn&#xED;c&#xED;ch nebo p&#x159;&#xED;stup\nk intern&#xED;m struktur&#xE1;m interpretu.\nSpr&#xE1;vn&#x11B; napsan&#xFD; pythonn&#xED; program by nem&#x11B;l na takov&#xFD;ch detailech z&#xE1;viset, pokud\nnen&#xED; k nekompatibilit&#x11B; mezi interprety dobr&#xFD; d&#x16F;vod.</p>\n<p>N&#x11B;kdy to ale je pot&#x159;eba, a dne&#x161;n&#xED; p&#x159;edn&#xE1;&#x161;ka bude specifick&#xE1; pro CPython\na p&#x159;&#xED;m&#xE9; vyu&#x17E;it&#xED; jeho API pro jazyk C.</p>\n<h2>Rychlost</h2>\n<p>&#x10C;ast&#xFD; d&#x16F;vod pro&#x10D; s&#xE1;hnout k C API je rychlost: CPython je celkem pomal&#xFD;.\nTradi&#x10D;n&#xED; metoda optimalizace je zjistit, kter&#xE9; &#x10D;&#xE1;sti jsou kritick&#xE9;, a p&#x159;epsat\nje do C.\nVyu&#x17E;ij&#xED; se tak v&#xFD;hody obou jazyk&#x16F;: Python pro rychl&#xFD; v&#xFD;voj, snadn&#xE9;\nprototypov&#xE1;n&#xED; a p&#x159;ehlednost k&#xF3;du, a C pro rychlost.</p>\n<p>Kdy&#x17E; je n&#xE1;&#x161; program p&#x159;&#xED;li&#x161; pomal&#xFD;, je pot&#x159;eba ho optimalizovat.\nPrvn&#xED; krok k tomu je v&#x17E;dy zkontrolovat, co zab&#xED;r&#xE1; v&#xED;ce &#x10D;asu, ne&#x17E; by m&#x11B;lo.\nK tomu se d&#xE1; pou&#x17E;&#xED;t n&#xE1;stroj <code>profile</code> ze standardn&#xED; knihovny, kter&#xFD; vyp&#xED;&#x161;e\ntabulku po&#x10D;tu vol&#xE1;n&#xED; jednotliv&#xFD;ch funkc&#xED; a &#x10D;asu v nich str&#xE1;ven&#xE9;ho:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">$ </span>python -m profile -s cumtime program.py\n</pre></div><p>Profilovat b&#x11B;h pytest test&#x16F; se d&#xE1; jednodu&#x161;e pomoc&#xED; modulu <a href=\"https://pypi.python.org/pypi/pytest-profiling\">pytest-profiling</a>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">$ </span>python -m pip install pytest-profiling\n<span class=\"gp\">$ </span>python -m pytest --profile\n</pre></div><p>Kdy&#x17E; m&#xE1;me p&#x159;edstavu o tom, co n&#xE1;s brzd&#xED;, m&#x16F;&#x17E;eme za&#x10D;&#xED;t p&#x159;episovat do C zp&#x16F;soby\npopsan&#xFD;mi n&#xED;&#x17E;e.</p>\n<p>Jin&#xE1; mo&#x17E;nost, jak program zrychlit, je ho pustit, tak jak je, pod interpretem\nPyPy, kter&#xFD; obsahuje optimalizovan&#xFD; p&#x159;eklada&#x10D;. To je ale jin&#xE1; kapitola.</p>\n<h2>Extern&#xED; knihovny</h2>\n<p>Druh&#xFD; d&#x16F;vod, pro&#x10D; program&#xE1;to&#x159;i pou&#x17E;&#xED;vaj&#xED; C API, je pou&#x17E;it&#xED; knihoven, kter&#xE9; maj&#xED;\nrozhran&#xED; pro C.\nTakov&#xFD;ch knihoven existuje mnoho &#x2013; pokud nen&#xED; n&#x11B;co specifick&#xE9; pro ur&#x10D;it&#xFD; jazyk,\n&#x10D;asto se to d&#xE1; volat i z C.</p>\n<p>Pro pr&#xE1;ci s extern&#xED;mi knihovnami se d&#xE1; pou&#x17E;&#xED;t C API nebo vestav&#x11B;n&#xFD; modul\n<a href=\"https://docs.python.org/3/library/ctypes.html\">ctypes</a>, ale v dne&#x161;n&#xED; dob&#x11B; je dobr&#xE9; m&#xED;sto toho pou&#x17E;&#xED;t <a href=\"http://cffi.readthedocs.io/en/latest/\">CFFI</a>, knihovnu\nkter&#xE1; funguje i s PyPy (a teoreticky jin&#xFD;mi implementacemi).</p>\n<h2>CPython</h2>\n<p>T&#x159;et&#xED; d&#x16F;vod, pro&#x10D; pou&#x17E;&#xED;t C API, je pr&#xE1;ce s CPythonem samotn&#xFD;m.\nKdy&#x17E; &#x10D;lov&#x11B;k zab&#x159;edne do slo&#x17E;it&#xE9;ho probl&#xE9;mu, m&#x16F;&#x17E;e na CPython pustit C debugger\njako <a href=\"https://en.wikipedia.org/wiki/GNU_Debugger\">gdb</a> nebo <a href=\"http://valgrind.org/\">Valgrind</a>, prozkoumat pot&#xED;&#x17E;e na ni&#x17E;&#x161;&#xED; &#xFA;rovni\na zjistit, kde p&#x159;esn&#x11B; se chyba nach&#xE1;z&#xED;.</p>\n<h2>Modul v C</h2>\n<p>Poj&#x10F;me za&#x10D;&#xED;t p&#x159;&#xED;kladem.\nVytvo&#x159;te si n&#xE1;sleduj&#xED;c&#xED; soubor, kter&#xFD; implementuje roz&#x161;&#xED;&#x159;en&#xED;\n(importovateln&#xFD; modul) s jednou funkc&#xED;.</p>\n<p>(Nebudeme cht&#xED;t, abyste podobn&#xFD; k&#xF3;d um&#x11B;li napsat, ale m&#x11B;li byste b&#xFD;t schopn&#xED;\nporozum&#x11B;t tomu, co d&#x11B;l&#xE1;.)</p>\n<p>demo.c:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"cp\">#include</span> <span class=\"cpf\">&lt;Python.h&gt;</span><span class=\"cp\"></span>\n\n<span class=\"n\">PyDoc_STRVAR</span><span class=\"p\">(</span>\n    <span class=\"n\">mod_docstring</span><span class=\"p\">,</span> \n    <span class=\"s\">&quot;Demo extension module with a Python wrapper for the system(3) function&quot;</span><span class=\"p\">);</span>\n\n<span class=\"k\">static</span> <span class=\"n\">PyObject</span> <span class=\"o\">*</span><span class=\"nf\">demo_system</span><span class=\"p\">(</span><span class=\"n\">PyObject</span> <span class=\"o\">*</span><span class=\"n\">self</span><span class=\"p\">,</span> <span class=\"n\">PyObject</span> <span class=\"o\">*</span><span class=\"n\">args</span><span class=\"p\">){</span>\n    <span class=\"k\">const</span> <span class=\"kt\">char</span> <span class=\"o\">*</span><span class=\"n\">command</span><span class=\"p\">;</span>\n    <span class=\"kt\">int</span> <span class=\"n\">retval</span><span class=\"p\">;</span>\n\n    <span class=\"cm\">/* Parse the given arguments: expect one string, convert to char* */</span>\n    <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">PyArg_ParseTuple</span><span class=\"p\">(</span><span class=\"n\">args</span><span class=\"p\">,</span> <span class=\"s\">&quot;s&quot;</span><span class=\"p\">,</span> <span class=\"o\">&amp;</span><span class=\"n\">command</span><span class=\"p\">))</span> <span class=\"p\">{</span>\n        <span class=\"cm\">/* Error handling: if PyArg_ParseTuple returns zero, return NULL */</span>\n        <span class=\"k\">return</span> <span class=\"nb\">NULL</span><span class=\"p\">;</span>\n    <span class=\"p\">}</span>\n\n    <span class=\"cm\">/* Call the C function */</span>\n    <span class=\"n\">retval</span> <span class=\"o\">=</span> <span class=\"n\">system</span><span class=\"p\">(</span><span class=\"n\">command</span><span class=\"p\">);</span>\n\n    <span class=\"cm\">/* Return result as Python int (error handling built in) */</span>\n    <span class=\"k\">return</span> <span class=\"n\">PyLong_FromLong</span><span class=\"p\">(</span><span class=\"n\">retval</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n\n<span class=\"cm\">/* List of all methods in the module */</span>\n<span class=\"k\">static</span> <span class=\"n\">PyMethodDef</span> <span class=\"n\">DemoMethods</span><span class=\"p\">[]</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n    <span class=\"p\">{</span><span class=\"s\">&quot;system&quot;</span><span class=\"p\">,</span>  <span class=\"n\">demo_system</span><span class=\"p\">,</span> <span class=\"n\">METH_VARARGS</span><span class=\"p\">,</span>\n            <span class=\"n\">PyDoc_STR</span><span class=\"p\">(</span><span class=\"s\">&quot;Execute a shell command.&quot;</span><span class=\"p\">)},</span>\n    <span class=\"p\">{</span><span class=\"nb\">NULL</span><span class=\"p\">,</span> <span class=\"nb\">NULL</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"nb\">NULL</span><span class=\"p\">}</span>        <span class=\"cm\">/* Sentinel */</span>\n<span class=\"p\">};</span>\n\n<span class=\"cm\">/* Module specification */</span>\n<span class=\"k\">static</span> <span class=\"k\">struct</span> <span class=\"n\">PyModuleDef</span> <span class=\"n\">demo_module</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n   <span class=\"n\">PyModuleDef_HEAD_INIT</span><span class=\"p\">,</span>\n   <span class=\"s\">&quot;demo&quot;</span><span class=\"p\">,</span>          <span class=\"cm\">/* name of module */</span>\n   <span class=\"n\">mod_docstring</span><span class=\"p\">,</span>   <span class=\"cm\">/* dosctring (may be NULL) */</span>\n   <span class=\"mi\">0</span><span class=\"p\">,</span>               <span class=\"cm\">/* size of per-interpreter state of the module */</span>\n   <span class=\"n\">DemoMethods</span><span class=\"p\">,</span>     <span class=\"cm\">/* list of methods */</span>\n<span class=\"p\">};</span>\n\n\n<span class=\"cm\">/* Module entrypoint */</span>\n<span class=\"n\">PyMODINIT_FUNC</span>\n<span class=\"nf\">PyInit_demo</span><span class=\"p\">(</span><span class=\"kt\">void</span><span class=\"p\">)</span>\n<span class=\"p\">{</span>\n    <span class=\"k\">return</span> <span class=\"n\">PyModuleDef_Init</span><span class=\"p\">(</span><span class=\"o\">&amp;</span><span class=\"n\">demo_module</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</pre></div><p>Z tohoto souboru by m&#x11B;la b&#xFD;t patrn&#xE1; struktura podobn&#xFD;ch roz&#x161;&#xED;&#x159;en&#xED;:\nm&#xE1;me funkci (<code>demo_system</code>), kter&#xE1; p&#x159;ev&#xE1;d&#xED; objekty Pythonu\nna datov&#xE9; typy C, vol&#xE1; samotnou funkci a v&#xFD;sledek p&#x159;ev&#xE1;d&#xED; zp&#x11B;t na pythonn&#xED;\nobjekt.</p>\n<p>D&#xE1;le m&#xE1;me pole z&#xE1;znam&#x16F; o funkc&#xED;ch (<code>DemoMethods</code>), kde je ke ka&#x17E;d&#xE9; funkci\np&#x159;i&#x159;azeno jm&#xE9;no, dokumenta&#x10D;n&#xED; &#x159;et&#x11B;zec a zp&#x16F;sob vol&#xE1;n&#xED; (v na&#x161;em p&#x159;&#xED;pad&#x11B;\nMETH_VARARGS, tedy vol&#xE1;n&#xED; s prom&#x11B;nn&#xFD;m po&#x10D;tem nepojmenovan&#xFD;ch argument&#x16F;,\npodobn&#x11B; jako bychom v Pythonu napsali <code>def system(*args)</code>).</p>\n<p>Dal&#x161;&#xED; pot&#x159;ebn&#xE1; prom&#x11B;nn&#xE1;, <code>demo_module</code>, obsahuje  informace o modulu:\njm&#xE9;no, dokumenta&#x10D;n&#xED; &#x159;et&#x11B;zec a seznam funkc&#xED;.\nKdybychom pot&#x159;ebovali krom&#x11B; funkc&#xED; definovat i t&#x159;&#xED;dy nebo konstanty,\nzde bychom pomoc&#xED; <a href=\"https://docs.python.org/3/c-api/module.html#c.PyModuleDef_Slot\">slot&#x16F;</a> definovali funkci, kter&#xE1; modul\ninicializuje, t.j. m&#xE1; podobnou funkci jako <code>__init__</code> u t&#x159;&#xED;dy v Pythonu.</p>\n<p>Posledn&#xED; &#x10D;&#xE1;st je funkce <code>PyInit</code>, jedin&#xE1; kter&#xE1; nen&#xED; definov&#xE1;na jako <code>static</code>,\ntak&#x17E;e jedin&#xE1;, kter&#xE1; je exportov&#xE1;na jako API knihovny, kterou vytv&#xE1;&#x159;&#xED;me.\nA&#x17E; bude Python tento modul importovat, najde tuto funkci podle jm&#xE9;na, spust&#xED; ji\na podle vr&#xE1;cen&#xE9; struktury typu <code>PyModuleDef</code> vytvo&#x159;&#xED; pythonn&#xED; objekt s modulem.</p>\n<h2>P&#x159;eklad</h2>\n<p>Abychom mohli takov&#xFD;to modul naimportovat, mus&#xED;me ho nejd&#x159;&#xED;v p&#x159;elo&#x17E;it a sestavit\nz n&#x11B;j sd&#xED;lenou knihovnu &#x2013; soubor .so (nebo .dll) &#x2013; s n&#xE1;zvem modulu:\nbu&#x10F; jen <code>demo.so</code>, nebo i s identifikac&#xED; architektury a verze Pythonu,\nnap&#x159;. <code>demo.cpython-35m-x86_64-linux-gnu.so</code>.\n(V&#xFD;hoda del&#x161;&#xED;ch n&#xE1;zv&#x16F; je v tom, &#x17E;e v jednom adres&#xE1;&#x159;i m&#x16F;&#x17E;e b&#xFD;t v&#xED;c modul&#x16F; pro\nr&#x16F;zn&#xE9; architektury a &#x17E;e se Python nebude sna&#x17E;it na&#x10D;&#xED;st nekompatibiln&#xED; moduly.)</p>\n<p>P&#x159;eklad je nutn&#xE9; prov&#xE9;st se spr&#xE1;vn&#xFD;mi p&#x159;ep&#xED;na&#x10D;i a volbami, nejl&#xE9;pe takov&#xFD;mi,\ns jak&#xFD;mi byl sestaven samotn&#xFD; Python.</p>\n<p>Pro zjednodu&#x161;en&#xED; tohoto procesu m&#x16F;&#x17E;eme pou&#x17E;&#xED;t setuptools: do n&#xE1;m u&#x17E; zn&#xE1;m&#xE9;ho\nsouboru <code>setup.py</code> p&#x159;id&#xE1;me argument <code>ext_modules</code> se seznamem roz&#x161;i&#x159;ovac&#xED;ch modul&#x16F;.\nPodrobn&#xFD; popis t&#x159;&#xED;dy <code>Extension</code> je v <a href=\"https://docs.python.org/3/distutils/apiref.html#distutils.core.Extension\">dokumentaci</a>; n&#xE1;m bude sta&#x10D;it\njen jm&#xE9;no a seznam zdrojov&#xFD;ch soubor&#x16F;:</p>\n<p>setup.py:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">setuptools</span> <span class=\"kn\">import</span> <span class=\"n\">setup</span><span class=\"p\">,</span> <span class=\"n\">Extension</span>\n\n<span class=\"n\">module1</span> <span class=\"o\">=</span> <span class=\"n\">Extension</span><span class=\"p\">(</span>\n    <span class=\"s1\">&apos;demo&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">sources</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s1\">&apos;demo.c&apos;</span><span class=\"p\">],</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">setup</span><span class=\"p\">(</span>\n    <span class=\"n\">name</span> <span class=\"o\">=</span> <span class=\"s1\">&apos;demo&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">version</span> <span class=\"o\">=</span> <span class=\"s1\">&apos;0.1&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">description</span> <span class=\"o\">=</span> <span class=\"s1\">&apos;Demo package&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">ext_modules</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">module1</span><span class=\"p\">]</span>\n<span class=\"p\">)</span>\n</pre></div><p>P&#x159;&#xED;kazy <code>python setup.py sdist</code> a <code>python setup.py install</code> budou fungovat jako norm&#xE1;ln&#x11B;,\njen je na instalaci pot&#x159;eba p&#x159;eklada&#x10D; jazyka C.</p>\n<p>Aby u&#x17E;ivatel&#xE9; p&#x159;eklada&#x10D; m&#xED;t nemuseli, m&#x16F;&#x17E;eme nainstalovat knihovnu <code>wheel</code> (<code>python -m pip install wheel</code>) a pak p&#x159;&#xED;kazem <code>python setup.py bdist_wheel</code> vygenerovat tzv. <em>wheel</em> archiv,\nnap&#x159;. <code>dist/demo-0.1-cp35-cp35m-linux_x86_64.whl</code>. Tento archiv jde nahr&#xE1;t na PyPI a n&#xE1;sledn&#x11B;\nnainstalovat, ov&#x161;em jen na architektu&#x159;e a verzi Pythonu, pro kter&#xE9; byl vytvo&#x159;en.</p>\n<p>Existuje zp&#x16F;sob, jak vytvo&#x159;it co nejv&#xED;ce platform&#x11B; nez&#xE1;visl&#xFD; linuxov&#xFD; wheel.\nJedn&#xE1; se o platformu nazvanou <code>manulinux1</code>, co&#x17E; je ve zkratce velmi star&#xE1; verze\nLinuxu (CentOS 5), na kter&#xE9; se wheely vytvo&#x159;&#xED;, aby &#x161;ly pou&#x17E;&#xED;t na r&#x16F;zn&#xFD;ch\nnov&#x11B;j&#x161;&#xED;ch i relativn&#x11B; star&#xFD;ch distribuc&#xED;ch. Pro tvorbu wheel&#x16F; se pou&#x17E;&#xED;v&#xE1;\n<a href=\"https://github.com/pypa/manylinux\">Docker obraz manylinux</a>,\nv&#xFD;voj&#xE1;&#x159;i samoz&#x159;ejm&#x11B; nepou&#x17E;&#xED;vaj&#xED; pro v&#xFD;voj CentOS 5 (tedy v&#x11B;t&#x161;ina ne).</p>\n<div class=\"admonition note\"><p>Zaj&#xED;mav&#xFD;m n&#xE1;strojem, kter&#xFD; stoj&#xED; za zm&#xED;nku, je <a href=\"https://github.com/joerick/cibuildwheel#cibuildwheel\">cibuildwheel</a>.\nZjednodu&#x161;uje tvorbu wheel&#x16F; pro Linux, macOS i Windows pomoc&#xED;\nCI slu&#x17E;eb <a href=\"https://travis-ci.org/\">Travis CI</a> a <a href=\"https://www.appveyor.com/\">AppVeyor</a>.</p>\n</div><p>Wheels jdou vytv&#xE1;&#x159;et i pro moduly tvo&#x159;en&#xE9; jen pythonn&#xED;m k&#xF3;dem.\nNejsou pak v&#xE1;zan&#xE9; na verzi a architekturu.\nJejich v&#xFD;hoda oproti <code>sdist</code> archiv&#x16F;m spo&#x10D;&#xED;v&#xE1; v tom, &#x17E;e se rychleji instaluj&#xED;.</p>\n<p>Alternativa k instalaci, alespo&#x148; pro lok&#xE1;ln&#xED; v&#xFD;voj, je roz&#x161;&#xED;&#x159;en&#xED; jen p&#x159;elo&#x17E;it a d&#xE1;t do\naktu&#xE1;ln&#xED;ho adres&#xE1;&#x159;e (nebo jak&#xE9;hokoli jin&#xE9;ho adres&#xE1;&#x159;e, odkud se importuj&#xED; moduly).\nK tomu slou&#x17E;&#xED; p&#x159;&#xED;kaz <code>python setup.py build_ext --inplace</code>.\nPozor na to, &#x17E;e po ka&#x17E;d&#xE9; zm&#x11B;n&#x11B; zdrojov&#xE9;ho k&#xF3;du je pot&#x159;eba roz&#x161;&#xED;&#x159;en&#xED; znovu p&#x159;elo&#x17E;it.</p>\n<p>P&#x159;&#xED;kaz <code>python setup.py develop</code> bude fungovat jako d&#x159;&#xED;v (pou&#x17E;&#xED;v&#xE1; <code>build_ext --inplace</code>),\njen je op&#x11B;t pot&#x159;eba p&#x159;&#xED;kaz po ka&#x17E;d&#xE9; zm&#x11B;n&#x11B; znovu spustit.</p>\n<h2>PyObject</h2>\n<p>Pod&#xED;vejme se te&#x10F; na z&#xE1;kladn&#xED; mechanismy interpretu CPython.</p>\n<p>Z&#xE1;kladn&#xED; datov&#xE1; struktura, kter&#xE1; reprezentuje jak&#xFD;koli objekt Pythonu, je PyObject\n(<a href=\"https://docs.python.org/3/c-api/structures.html#c.PyObject\">dokumentace</a>, \n<a href=\"https://github.com/python/cpython/blob/3.5/Include/object.h#L106\">definice</a>).\nSkl&#xE1;d&#xE1; se ze dvou prvk&#x16F;:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">typedef</span> <span class=\"k\">struct</span> <span class=\"n\">_object</span> <span class=\"p\">{</span>\n    <span class=\"n\">Py_ssize_t</span> <span class=\"n\">ob_refcnt</span><span class=\"p\">;</span>\n    <span class=\"k\">struct</span> <span class=\"n\">_typeobject</span> <span class=\"o\">*</span><span class=\"n\">ob_type</span><span class=\"p\">;</span>\n<span class=\"p\">}</span> <span class=\"n\">PyObject</span><span class=\"p\">;</span>\n</pre></div><p>Prvn&#xED; je po&#x10D;et referenc&#xED; (<em>reference count</em>), kter&#xFD; se d&#xE1; popsat jako po&#x10D;et m&#xED;st,\nze kter&#xFD;ch je mo&#x17E;n&#xE9; k tomuto objektu p&#x159;istoupit.\nKdy&#x17E; objekt ulo&#x17E;&#xED;me do prom&#x11B;nn&#xE9; nebo do seznamu, zv&#xFD;&#x161;&#xED; se po&#x10D;et referenc&#xED; o 1.\nKdy&#x17E; seznam nebo prom&#x11B;nn&#xE1; zanikne (nebo n&#xE1;&#x161; objekt p&#x159;ep&#xED;&#x161;eme jin&#xFD;m),\npo&#x10D;et referenc&#xED; se zase sn&#xED;&#x17E;&#xED;.\nKdy&#x17E; po&#x10D;et referenc&#xED; dos&#xE1;hne nuly, znamen&#xE1; to, &#x17E;e se k objektu u&#x17E; ned&#xE1; dostat a Python ho\nuvoln&#xED; z pam&#x11B;ti.</p>\n<p>Druh&#xFD; prvek struktury PyObject je ukazatel na typ.\nTyp je pythonn&#xED; objekt (<code>class</code>), kter&#xFD; definuje chov&#xE1;n&#xED; t&#x159;&#xED;dy objekt&#x16F;: oper&#xE1;tory,\natributy a metody, kter&#xE9; ten objekt m&#xE1;.</p>\n<p>Struktura PyObject slou&#x17E;&#xED; jako hlavi&#x10D;ka, za kterou pak n&#xE1;sleduj&#xED; data interpretovan&#xE1; podle\ntypu dan&#xE9;ho objektu.\nNap&#x159;&#xED;klad pythonn&#xED; <a href=\"https://github.com/python/cpython/blob/3.5/Include/floatobject.h#L15\">objekt typu float</a> vypad&#xE1; n&#xE1;sledovn&#x11B;:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">typedef</span> <span class=\"k\">struct</span> <span class=\"p\">{</span>\n    <span class=\"n\">PyObject</span> <span class=\"n\">ob_base</span><span class=\"p\">;</span>\n    <span class=\"kt\">double</span> <span class=\"n\">ob_fval</span><span class=\"p\">;</span>\n<span class=\"p\">}</span> <span class=\"n\">PyFloatObject</span><span class=\"p\">;</span>\n</pre></div><p>...tedy struktura PyObject, za kterou je v pam&#x11B;ti &#x10D;&#xED;seln&#xE1; hodnota.</p>\n<p><a href=\"https://github.com/python/cpython/blob/3.5/Include/listobject.h#L23\">Seznamy</a> obsahuj&#xED; za hlavi&#x10D;kou nap&#x159;. velikost a (ukazatel na) pole ukazatel&#x16F; na jednotliv&#xE9;\nprvky.\nPodobn&#x11B; <a href=\"https://github.com/python/cpython/blob/3.5/Include/longintrepr.h#L89\">objekty typu int</a> (kter&#xE9; maj&#xED; v Pythonu neomezen&#xFD; rozsah) maj&#xED; d&#xE9;lku a pole\njednotliv&#xFD;ch 30bitov&#xFD;ch &#x201E;&#x10D;&#xED;slic&#x201C;.\nNumPy matice maj&#xED; metadata (velikost, typ, popis rozlo&#x17E;en&#xED; v pam&#x11B;ti) a ukazatel na pole hodnot.</p>\n<p>To z&#xE1;kladn&#xED;, co pot&#x159;ebujeme v&#x11B;d&#x11B;t, je, &#x17E;e na &#xFA;rovni C je ka&#x17E;d&#xFD; pythonn&#xED; objekt reprezentov&#xE1;n\njako struktura po&#x10D;tu referenc&#xED;, ukazatele na typ a dat specifick&#xFD;ch pro dan&#xFD; typ.</p>\n<h2>Reference counting</h2>\n<p>Tak jako v C je d&#x16F;le&#x17E;it&#xE9; spr&#xE1;vn&#x11B; alokovat a dealokovat pam&#x11B;&#x165;, p&#x159;i tvorb&#x11B; roz&#x161;&#xED;&#x159;en&#xED; do CPythonu\nje t&#x159;eba spr&#xE1;vn&#x11B; pracovat s referencemi: ke ka&#x17E;d&#xE9;mu <a href=\"https://docs.python.org/3/c-api/refcounting.html#c.Py_INCREF\">Py_INCREF</a> (p&#x159;i&#x10D;ten&#xED; 1 k po&#x10D;tu referenc&#xED;)\nje pot&#x159;eba pozd&#x11B;ji zavolat <a href=\"https://docs.python.org/3/c-api/refcounting.html#c.Py_DECREF\">Py_DECREF</a> (ode&#x10D;ten&#xED; 1 a p&#x159;&#xED;padn&#xE9; uvoln&#x11B;n&#xED; objektu).\nJak&#xE1;koli pr&#xE1;ce s objektem se sm&#xED; prov&#xE1;d&#x11B;t jen mezi INCREF a p&#x159;&#xED;slu&#x161;n&#xFD;m DECREF.</p>\n<p>Plat&#xED; konvence, &#x17E;e argumenty funkc&#xED; se p&#x159;ed&#xE1;vaj&#xED; jako tzv. <em>borrowed reference</em>: o po&#x10D;itadlo\nse star&#xE1; volaj&#xED;c&#xED; a v pr&#x16F;b&#x11B;hu volan&#xE9; funkce se objekt d&#xE1; pou&#x17E;&#xED;vat.\nPokud bychom ale argument pot&#x159;ebovali i po skon&#x10D;en&#xED; volan&#xE9; funkce (nap&#x159;. si ho ulo&#x17E;&#xED;me\ndo glob&#xE1;ln&#xED; prom&#x11B;nn&#xE9;), je pot&#x159;eba mu po&#x10D;itadlo zv&#xFD;&#x161;it (a po skon&#x10D;en&#xED; pr&#xE1;ce zase sn&#xED;&#x17E;it).</p>\n<p>V na&#x161;em modulu <code>demo</code> p&#x159;eb&#xED;r&#xE1;me jako parametr n-tici.\nZodpov&#x11B;dnost zavolat na tuto n-tici Py_DECREF m&#xE1; ale volaj&#xED;c&#xED;, ne my.\nZavol&#xE1;n&#xED;m funkce <code>PyArg_ParseTuple</code> z&#xED;sk&#xE1;me <code>char*</code>, kter&#xFD; ale m&#x16F;&#x17E;eme pou&#x17E;&#xED;vat jen v r&#xE1;mci na&#x161;&#xED;\nfunkce: po jej&#xED;m skon&#x10D;en&#xED; m&#x16F;&#x17E;e volaj&#xED;c&#xED; argumenty funkce uvolnit, a t&#xED;m &#x159;et&#x11B;zec zru&#x161;it.</p>\n<p>Funkce, kter&#xE9; vracej&#xED; pythonn&#xED; objekty, p&#x159;edpokl&#xE1;daj&#xED;, &#x17E;e na vr&#xE1;cenou hodnotu provede DECREF volaj&#xED;c&#xED;.\nV modulu <code>demo</code> vol&#xE1;me funkci <a href=\"https://docs.python.org/3/c-api/long.html#c.PyLong_FromLong\">PyLong_FromLong</a>, kter&#xE1; vytvo&#x159;&#xED; nov&#xE9; pythonn&#xED; &#x10D;&#xED;slo.\nZa vzniklou referenci na&#x161;e funkce p&#x159;eb&#xED;r&#xE1; zodpov&#x11B;dnost, je tedy na n&#xE1;s, abychom se postarali\no zavol&#xE1;n&#xED; Py_DECREF.\nVr&#xE1;cen&#xED;m v&#xFD;sledku tuto zodpov&#x11B;dnost ale p&#x159;ed&#xE1;v&#xE1;me na funkci, kter&#xE1; vol&#xE1; tu na&#x161;i.</p>\n<h2>Hodnoty a v&#xFD;jimky</h2>\n<p>Dal&#x161;&#xED; konvence, kterou v&#x11B;t&#x161;ina funkc&#xED; v C API dodr&#x17E;uje, je zp&#x16F;sob vracen&#xED; v&#xFD;jimek.</p>\n<p>Funkce, kter&#xE9; vrac&#xED; pythonn&#xED; objekty, na &#xFA;rovni C vrac&#xED; <code>PyObject*</code>.\nNastane-li v&#xFD;jimka, objekt v&#xFD;jimky se zaznamen&#xE1; do glob&#xE1;ln&#xED; (p&#x159;esn&#x11B;ji, <em>thread-local</em>)\nprom&#x11B;nn&#xE9; a funkce vr&#xE1;t&#xED; NULL.</p>\n<p>V na&#x161;em modulu <code>demo</code> vol&#xE1;me funkci <code>PyArg_ParseTuple</code>, kter&#xE1; m&#x16F;&#x17E;e vyvolat v&#xFD;jimku: typicky\n<code>TypeError</code> kv&#x16F;li nespr&#xE1;vn&#xE9;mu po&#x10D;tu nebo typu argument&#x16F;.\nV takov&#xE9;m p&#x159;&#xED;pad&#x11B; tato funkce v&#xFD;jimku zaznamen&#xE1; a vr&#xE1;t&#xED; NULL.\nNa&#x161;&#xED; funkci <code>system</code> u&#x17E; sta&#x10D;&#xED; vr&#xE1;tit NULL, proto&#x17E;e v&#xED;me, &#x17E;e v&#xFD;jimka u&#x17E; je zaznamenan&#xE1;.</p>\n<p>Dal&#x161;&#xED; funkce, kter&#xE1; m&#x16F;&#x17E;e neusp&#x11B;t, je <code>PyLong_FromLong</code>.\nVzhledem k tomu, &#x17E;e jej&#xED; v&#xFD;sledek rovnou vrac&#xED;me, nen&#xED; pot&#x159;eba &#xFA;sp&#x11B;ch kontrolovat &#x2013; vr&#xE1;t&#xED;me\nbu&#x10F; spr&#xE1;vnou hodnotu nebo NULL se zaznamenanou v&#xFD;jimkou.</p>\n<h2>GIL</h2>\n<p>Posledn&#xED; omezen&#xED;, kter&#xE9;ho si autor roz&#x161;&#xED;&#x159;en&#xED; mus&#xED; b&#xFD;t v&#x11B;dom, je <em>Global Interpreter Lock</em>.\nStru&#x10D;n&#x11B; &#x159;e&#x10D;eno, s objekty <code>PyObject*</code> m&#x16F;&#x17E;e pracovat pouze jedno vl&#xE1;kno.\nToto vl&#xE1;kno dr&#x17E;&#xED; glob&#xE1;ln&#xED; z&#xE1;mek, kter&#xFD; &#x10D;as od &#x10D;asu odemkne a znovu se pokus&#xED; zamknout,\naby mohly b&#x11B;&#x17E;et i ostatn&#xED; vl&#xE1;kna.</p>\n<p>D&#xED;ky GIL je v&#xED;cevl&#xE1;knov&#xE9; programov&#xE1;n&#xED; v Pythonu relativn&#x11B; bezpe&#x10D;n&#xE9;: nem&#x16F;&#x17E;e nap&#x159;. nastat soub&#x11B;h\n(<em>race condition</em>), kdy by se nastavilo po&#x10D;itadlo referenc&#xED; na &#x161;patnou hodnotu.\nNa druhou stranu tento z&#xE1;mek ale omezuje paralelismus, a tedy i rychlost programu.</p>\n<p>Glob&#xE1;ln&#xED; z&#xE1;mek se d&#xE1; odemknout v situac&#xED;ch, kdy nepracujeme s <code>PyObject*</code> a nevol&#xE1;me pythonn&#xED; k&#xF3;d.\nNap&#x159;&#xED;klad &#x10D;ten&#xED; ze souboru nebo s&#xED;t&#x11B; ostatn&#xED; vl&#xE1;kna neblokuje.\nStejn&#x11B; tak maticov&#xE9; operace v NumPy typicky nedr&#x17E;&#xED; GIL zat&#xED;mco po&#x10D;&#xED;taj&#xED; na &#xFA;rovni C nebo Fortranu.</p>\n<h1>Cython</h1>\n<p>Te&#x10F;, kdy&#x17E; v&#xED;me jak to v&#x161;echno funguje, se m&#x16F;&#x17E;eme pod&#xED;vat na zp&#x16F;sob, jak roz&#x161;&#xED;&#x159;en&#xED; ps&#xE1;t\njednodu&#x161;e.\nC API se toti&#x17E; d&#xE1; pou&#x17E;&#xED;t nejen z C, ale z jak&#xE9;hokoli jazyka, kter&#xFD; um&#xED; volat funkce se\nstejn&#xFD;mi konvencemi, nap&#x159;. C++ (s pomoc&#xED; <code>extern C</code>).\nDal&#x161;&#xED; zp&#x16F;sob, jak pou&#x17E;&#xED;t C API ale nepsat C, je pou&#x17E;&#xED;t p&#x159;eklada&#x10D; z p&#x159;&#xED;jemn&#x11B;j&#x161;&#xED;ho jazyka do C.</p>\n<p>Jeden takov&#xFD; jazyk je Cython (nepl&#xE9;st s CPython).</p>\n<p>Cython je jazyk podobn&#xFD; Pythonu, kter&#xFD; ale lze p&#x159;elo&#x17E;it na C a d&#xE1;le optimalizovat.</p>\n<p>Cython si nainstalujte pomoc&#xED; p&#x159;&#xED;kazu:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">$ </span>python -m pip install cython\n</pre></div><h2>Kompilace Pythonu</h2>\n<p>Kdy&#x17E; chceme p&#x159;ev&#xE9;st modul z Pythonu do Cythonu, nejjednodu&#x161;&#x161;&#xED; za&#x10D;&#xE1;tek je p&#x159;ejmenovat soubor <code>.py</code>\nna <code>.pyx</code>, aby bylo jasn&#xE9;, &#x17E;e jde o jin&#xFD; jazyk, kter&#xFD; nep&#x16F;jde naimportovat p&#x159;&#xED;mo.</p>\n<p>Jazyky Python a Cython nejsou 100% kompatibiln&#xED;, ale zvl&#xE1;&#x161;t&#x11B; u k&#xF3;du, kter&#xFD; pracuje hlavn&#x11B; s\n&#x10D;&#xED;sly, se nekompatibilita neprojev&#xED;.\nV&#xFD;voj&#xE1;&#x159;i Cythonu pova&#x17E;uj&#xED; ka&#x17E;dou odchylku od specifikace jazyka za chybu, kterou je nutno opravit.</p>\n<p>Jako p&#x159;&#xED;klad m&#x16F;&#x17E;ete pou&#x17E;&#xED;t tuto naivn&#xED; implementaci celo&#x10D;&#xED;seln&#xE9;ho a maticov&#xE9;ho n&#xE1;soben&#xED;.\nUlo&#x17E;te si ji jako <code>matmul.py</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">import</span> <span class=\"nn\">numpy</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">intmul</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"n\">b</span><span class=\"p\">):</span>\n    <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">a</span> <span class=\"o\">*</span> <span class=\"n\">b</span>\n    <span class=\"k\">return</span> <span class=\"n\">result</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">matmul</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"n\">b</span><span class=\"p\">):</span>\n    <span class=\"n\">n</span> <span class=\"o\">=</span> <span class=\"n\">a</span><span class=\"o\">.</span><span class=\"n\">shape</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n    <span class=\"n\">m</span> <span class=\"o\">=</span> <span class=\"n\">a</span><span class=\"o\">.</span><span class=\"n\">shape</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n    <span class=\"k\">if</span> <span class=\"n\">b</span><span class=\"o\">.</span><span class=\"n\">shape</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"n\">m</span><span class=\"p\">:</span>\n        <span class=\"k\">raise</span> <span class=\"ne\">ValueError</span><span class=\"p\">(</span><span class=\"s1\">&apos;incompatible sizes&apos;</span><span class=\"p\">)</span>\n    <span class=\"n\">p</span> <span class=\"o\">=</span> <span class=\"n\">b</span><span class=\"o\">.</span><span class=\"n\">shape</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n    <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">n</span><span class=\"p\">,</span> <span class=\"n\">p</span><span class=\"p\">))</span>\n    <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">n</span><span class=\"p\">):</span>\n        <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">p</span><span class=\"p\">):</span>\n            <span class=\"k\">for</span> <span class=\"n\">k</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">m</span><span class=\"p\">):</span>\n                <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">a</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"n\">k</span><span class=\"p\">]</span>\n                <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">b</span><span class=\"p\">[</span><span class=\"n\">k</span><span class=\"p\">,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n                <span class=\"n\">result</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">+=</span> <span class=\"n\">x</span> <span class=\"o\">*</span> <span class=\"n\">y</span>\n    <span class=\"k\">return</span> <span class=\"n\">result</span>\n</pre></div><p>St&#xE1;hn&#x11B;te si <a href=\"/2017/pyknihovny-brno/intro/cython/static/test_matmul.py\">testy</a> a zkontrolujte, &#x17E;e proch&#xE1;z&#xED;.</p>\n<p>Potom soubor p&#x159;ejmenujte na <code>matmul.pyx</code>.</p>\n<p>V&#xFD;sledek bychom mohli p&#x159;ev&#xE9;st na C pomoc&#xED; p&#x159;&#xED;kazu <code>cython -3 matmul.pyx</code>, &#x10D;&#xED;m&#x17E;\nvznikne <code>matmul.c</code>. Ten m&#x16F;&#x17E;eme p&#x159;elo&#x17E;it v&#xFD;&#x161;e uveden&#xFD;m zp&#x16F;sobem.</p>\n<p>Jednodu&#x161;&#x161;&#xED; varianta je pou&#x17E;&#xED;t Cython v <code>setup.py</code>.\nPro na&#x161;e &#xFA;&#x10D;ely bude <code>setup.py</code> s Cythonem a NumPy vypadat takto:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">setuptools</span> <span class=\"kn\">import</span> <span class=\"n\">setup</span>\n<span class=\"kn\">from</span> <span class=\"nn\">Cython.Build</span> <span class=\"kn\">import</span> <span class=\"n\">cythonize</span>\n<span class=\"kn\">import</span> <span class=\"nn\">numpy</span>\n\n<span class=\"n\">setup</span><span class=\"p\">(</span>\n    <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s1\">&apos;matmul&apos;</span><span class=\"p\">,</span>\n    <span class=\"n\">ext_modules</span><span class=\"o\">=</span><span class=\"n\">cythonize</span><span class=\"p\">(</span><span class=\"s1\">&apos;matmul.pyx&apos;</span><span class=\"p\">,</span> <span class=\"n\">language_level</span><span class=\"o\">=</span><span class=\"mi\">3</span><span class=\"p\">),</span>\n    <span class=\"n\">include_dirs</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">get_include</span><span class=\"p\">()],</span>\n    <span class=\"n\">setup_requires</span><span class=\"o\">=</span><span class=\"p\">[</span>\n        <span class=\"s1\">&apos;Cython&apos;</span><span class=\"p\">,</span>\n        <span class=\"s1\">&apos;NumPy&apos;</span><span class=\"p\">,</span>\n    <span class=\"p\">],</span>\n    <span class=\"n\">install_requires</span><span class=\"o\">=</span><span class=\"p\">[</span>\n        <span class=\"s1\">&apos;NumPy&apos;</span><span class=\"p\">,</span>\n    <span class=\"p\">],</span>\n<span class=\"p\">)</span>\n</pre></div><div class=\"admonition note\"><p>V p&#x159;&#xED;pad&#x11B; probl&#xE9;mech s nefunguj&#xED;c&#xED;m <code>include_dirs</code> na syst&#xE9;mu macOS\npou&#x17E;ijte komplikovan&#x11B;j&#x161;&#xED; variantu:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">distutils.extension</span> <span class=\"kn\">import</span> <span class=\"n\">Extension</span>\n<span class=\"o\">...</span>\n<span class=\"n\">ext_modules</span> <span class=\"o\">=</span> <span class=\"n\">cythonize</span><span class=\"p\">([</span><span class=\"n\">Extension</span><span class=\"p\">(</span><span class=\"s1\">&apos;matmul&apos;</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"s1\">&apos;matmul.pyx&apos;</span><span class=\"p\">],</span>\n                                   <span class=\"n\">include_dirs</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">get_include</span><span class=\"p\">()])],</span>\n                        <span class=\"n\">language_level</span><span class=\"o\">=</span><span class=\"mi\">3</span><span class=\"p\">)</span>\n</pre></div></div><p>Po zad&#xE1;n&#xED; <code>python setup.py develop</code> nebo <code>python setup.py build_ext --inplace</code> atp.\nse modul <code>matmul.pyx</code> zkompiluje s pou&#x17E;it&#xED;m nainstalovan&#xE9;ho NumPy a bude p&#x159;ipraven na pou&#x17E;it&#xED;.\n(Zkontrolujte, &#x17E;e testy proch&#xE1;z&#xED; i se zkompilovan&#xFD;m modulem.)</p>\n<p>Nev&#xFD;hoda tohoto p&#x159;&#xED;stupu je, &#x17E;e k spu&#x161;t&#x11B;n&#xED; takov&#xE9;ho <code>setup.py</code> je ji&#x17E; pot&#x159;eba\nm&#xED;t nainstalovan&#xFD; <code>cython</code> a <code>numpy</code>.\nInstalace z archivu <code>sdist</code> se tedy nemus&#xED; pov&#xE9;st &#x2013; je pot&#x159;eba u&#x17E;ivatel&#x16F;m &#x159;&#xED;ct,\n&#x17E;e dan&#xE9; moduly u&#x17E; mus&#xED; m&#xED;t nainstalovan&#xE9;.\nTento probl&#xE9;m aktu&#xE1;ln&#x11B; &#x159;e&#x161;&#xED; PyPA (spr&#xE1;vci <code>pip</code> a <code>setuptools</code>).</p>\n<p>Instalace z archiv&#x16F; <code>wheel</code> by m&#x11B;la b&#xFD;t bezprobl&#xE9;mov&#xE1;.</p>\n<h2>Anotace</h2>\n<p>K&#xF3;d, kter&#xFD; takto vznikne, nen&#xED; o moc rychlej&#x161;&#xED; ne&#x17E; p&#x16F;vodn&#xED; Python.\nJe to t&#xED;m, &#x17E;e sekvence p&#x159;&#xED;kaz&#x16F; ve funkci je sice p&#x159;eveden&#xE1; do C a p&#x159;elo&#x17E;en&#xE1; do strojov&#xE9;ho k&#xF3;du,\nale ka&#x17E;d&#xE1; operace pracuje s generick&#xFD;mi pythonn&#xED;mi objekty, tak&#x17E;e mus&#xED; pro ka&#x17E;d&#xE9; &#x10D;&#xED;slo\n&#x10D;&#xED;slo z matice zkonstruovat pythonn&#xED; objekt, vyhledat implementaci s&#x10D;&#xED;t&#xE1;n&#xED; pro dv&#x11B; cel&#xE1; &#x10D;&#xED;sla,\na v&#xFD;sledek p&#x159;ev&#xE9;st zp&#x11B;t na <code>int64</code> a ulo&#x17E;it do matice.</p>\n<p>Na situaci se m&#x16F;&#x17E;eme pod&#xED;vat pomoc&#xED; p&#x159;ep&#xED;na&#x10D;e <code>--annotate</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">$ </span>cython -3 --annotate matmul.pyx\n</pre></div><p>To vygeneruje soubor <code>matmul.html</code>, kde jsou potencion&#xE1;ln&#x11B; pomal&#xE9; operace vysv&#xED;ceny &#x17E;lut&#x11B;.\nKe ka&#x17E;d&#xE9;mu &#x159;&#xE1;dku se nav&#xED;c d&#xE1; kliknut&#xED;m uk&#xE1;zat odpov&#xED;daj&#xED;c&#xED; k&#xF3;d v C (kter&#xFD; b&#xFD;v&#xE1; docela slo&#x17E;it&#xFD;,\nproto&#x17E;e &#x159;e&#x161;&#xED; v&#x11B;ci jako zp&#x11B;tnou kompatibilitu a o&#x161;et&#x159;ov&#xE1;n&#xED; chyb, a nav&#xED;c pou&#x17E;&#xED;v&#xE1; hodn&#x11B; pomocn&#xFD;ch\nmaker).</p>\n<p>Obecn&#x11B; neb&#xFD;v&#xE1; probl&#xE9;m m&#xED;t &#x201E;&#x17E;lut&#xE9;&#x201C; ty &#x159;&#xE1;dky, kter&#xE9; se ve funkci prov&#xE1;d&#xED; pouze jednou.\nAle v cyklech, zvl&#xE1;&#x161;t&#x11B; t&#x11B;ch t&#x159;ikr&#xE1;t zano&#x159;en&#xFD;ch, se autor roz&#x161;&#xED;&#x159;en&#xED; typicky sna&#x17E;&#xED; &#x17E;lut&#xFD;m &#x159;&#xE1;dk&#x16F;m\nvyhnout.\nNejjednodu&#x161;&#x161;&#xED; zp&#x16F;sob, jak toho doc&#xED;lit, je dopln&#x11B;n&#xED; statick&#xFD;ch informac&#xED; o typech.</p>\n<h2>Dopln&#x11B;n&#xED; typ&#x16F;</h2>\n<p>Za&#x10D;neme u funkce <code>intmul</code>, kde dopln&#xED;me informaci o tom, &#x17E;e parametry <code>a</code> a <code>b</code> a prom&#x11B;nn&#xE1;\n<code>result</code> jsou typu <code>int</code>.\nParametr&#x16F;m sta&#x10D;&#xED; doplnit typ podobn&#x11B; jako v C, ostatn&#xED; lok&#xE1;ln&#xED; prom&#x11B;nn&#xE9; pot&#x159;ebuj&#xED; definici pomoc&#xED;\np&#x159;&#xED;kazu <code>cdef</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">intmul</span><span class=\"p\">(</span><span class=\"nb\">int</span> <span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"nb\">int</span> <span class=\"n\">b</span><span class=\"p\">):</span>\n    <span class=\"n\">cdef</span> <span class=\"nb\">int</span> <span class=\"n\">result</span>\n    <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">a</span> <span class=\"o\">*</span> <span class=\"n\">b</span>\n    <span class=\"k\">return</span> <span class=\"n\">result</span>\n</pre></div><p>Te&#x10F; bude funkce nepatrn&#x11B; rychlej&#x161;&#xED;, ale tak&#xE9; m&#xE9;n&#x11B; obecn&#xE1;: nejde j&#xED; n&#xE1;sobit &#x159;et&#x11B;zec &#x10D;&#xED;slem,\nale ani re&#xE1;ln&#xE1; &#x10D;&#xED;sla (<code>float</code>), a dokonce ani cel&#xE1; &#x10D;&#xED;sla, kter&#xE1; se nevejdou do 64 bit&#x16F; (p&#x159;&#xED;p.\njin&#xE9; velikosti, dle syst&#xE9;mu).\nTyp int v Cythonu je toti&#x17E; int z C, ne ten neomezen&#xFD; z Pythonu.</p>\n<p>Dal&#x161;&#xED; v&#x11B;c, kterou m&#x16F;&#x17E;eme ud&#x11B;lat, je zm&#x11B;nit p&#x159;&#xED;kaz <code>def</code> na <code>cpdef</code> a doplnit typ n&#xE1;vratov&#xE9;\nhodnoty:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">cpdef</span> <span class=\"nb\">int</span> <span class=\"n\">intmul</span><span class=\"p\">(</span><span class=\"nb\">int</span> <span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"nb\">int</span> <span class=\"n\">b</span><span class=\"p\">):</span>\n    <span class=\"n\">cdef</span> <span class=\"nb\">int</span> <span class=\"n\">result</span>\n    <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">a</span> <span class=\"o\">*</span> <span class=\"n\">b</span>\n    <span class=\"k\">return</span> <span class=\"n\">result</span>\n</pre></div><p>T&#xED;m se zbav&#xED;me n&#xE1;kladn&#xE9;ho p&#x159;evodu v&#xFD;sledku na PyObject.\nBohu&#x17E;el ale toto zrychlen&#xED; poc&#xED;t&#xED;me, jen kdy&#x17E; takovou funkci zavol&#xE1;me\nz&#xA0;jin&#xE9; funkce napsan&#xE9; v&#xA0;Cythonu.</p>\n<h2>T&#x159;i typy funkc&#xED;</h2>\n<p>Funkce jdou deklarovat t&#x159;emi zp&#x16F;soby:</p>\n<ul>\n<li><code>def func(...):</code> je funkce, kter&#xE1; jde volat z Pythonu i z Cythonu, ale vol&#xE1;n&#xED; z Cythonu je pomal&#xE9; (argumenty a v&#xFD;sledek se p&#x159;ev&#xE1;d&#xED; na pythonn&#xED; objekty a zp&#x11B;t),</li>\n<li><code>cdef &lt;type&gt; func(...):</code> je funkce, kter&#xE1; jde volat pouze z Cythonu, ale vol&#xE1;n&#xED; je rychl&#xE9; (pracuje se s C typy),</li>\n<li><code>cpdef &lt;type&gt; func(...):</code> je funkce, kter&#xE1; se z Cythonu vol&#xE1; rychle, ale jde volat i z Pythonu (ve skute&#x10D;nosti Cython vytvo&#x159;&#xED; dva druhy t&#xE9;to funkce).</li>\n</ul>\n<h2>T&#x159;&#xED;dy</h2>\n<p>Cython umo&#x17E;&#x148;uje vytv&#xE1;&#x159;et tzv. <em>built-in</em> t&#x159;&#xED;dy: stejn&#xFD; druh t&#x159;&#xED;d jako je\nnap&#x159;. <code>str</code> nebo <code>int</code>.\nPr&#xE1;ce s takov&#xFD;mi t&#x159;&#xED;dami je rychlej&#x161;&#xED;, ale maj&#xED; pevn&#x11B; danou strukturu.\nAni jim ani jejich instanc&#xED;m nelze z&#xA0;Pythonu nastavovat nov&#xE9; atributy:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"o\">&gt;&gt;&gt;</span> <span class=\"s2\">&quot;foo&quot;</span><span class=\"o\">.</span><span class=\"n\">bar</span> <span class=\"o\">=</span> <span class=\"mi\">3</span>\n<span class=\"n\">Traceback</span> <span class=\"p\">(</span><span class=\"n\">most</span> <span class=\"n\">recent</span> <span class=\"n\">call</span> <span class=\"n\">last</span><span class=\"p\">):</span>\n  <span class=\"n\">File</span> <span class=\"s2\">&quot;&lt;stdin&gt;&quot;</span><span class=\"p\">,</span> <span class=\"n\">line</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"ow\">in</span> <span class=\"o\">&lt;</span><span class=\"n\">module</span><span class=\"o\">&gt;</span>\n<span class=\"ne\">AttributeError</span><span class=\"p\">:</span> <span class=\"s1\">&apos;str&apos;</span> <span class=\"nb\">object</span> <span class=\"n\">has</span> <span class=\"n\">no</span> <span class=\"n\">attribute</span> <span class=\"s1\">&apos;bar&apos;</span>\n</pre></div><p>P&#x159;&#xED;klad definice <em>built-in</em> t&#x159;&#xED;dy:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">cdef</span> <span class=\"k\">class</span> <span class=\"nc\">Foo</span><span class=\"p\">:</span>\n    <span class=\"c1\"># V&#x161;echny &#x10D;lensk&#xE9; prom&#x11B;nn&#xE9; mus&#xED; b&#xFD;t nadefinovan&#xE9; tady</span>\n    <span class=\"n\">cdef</span> <span class=\"nb\">int</span> <span class=\"n\">foo</span>\n    <span class=\"o\">...</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">__cinit__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">int</span> <span class=\"n\">f</span><span class=\"p\">):</span>\n        <span class=\"c1\"># Inicializace t&#x159;&#xED;dy.</span>\n        <span class=\"c1\"># Cython zajist&#xED;, &#x17E;e se tato funkce zavol&#xE1; pouze jednou (na rozd&#xED;l</span>\n        <span class=\"c1\"># od __init__, kterou lze z pythonn&#xED;ho k&#xF3;du zavolat kdykoli)</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">foo</span> <span class=\"o\">=</span> <span class=\"n\">f</span>\n        <span class=\"o\">...</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">__dealloc__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n        <span class=\"c1\"># Deinicializace t&#x159;&#xED;dy</span>\n        <span class=\"o\">...</span>\n\n    <span class=\"n\">cpdef</span> <span class=\"nb\">int</span> <span class=\"n\">method</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n        <span class=\"o\">...</span>\n        <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">foo</span>\n</pre></div><p>V&#xED;ce o definici t&#x159;&#xED;d najdete v&#xA0;<a href=\"http://cython.readthedocs.io/en/latest/src/tutorial/cdef_classes.html\">dokumentaci Cythonu</a>.</p>\n<h2>Pou&#x17E;&#xED;v&#xE1;n&#xED; NumPy</h2>\n<p>Pro funkci <code>matmul</code> m&#x16F;&#x17E;eme nadefinovat &#x10D;&#xED;seln&#xE9; prom&#x11B;nn&#xE9; (<code>n</code>, <code>m</code>, <code>p</code>, <code>i</code>, <code>j</code>, <code>k</code>, <code>x</code>, <code>y</code>)\njako <code>int</code>, ale t&#xED;m si moc nepom&#x16F;&#x17E;eme: v&#x11B;t&#x161;inu &#x10D;asu program str&#xE1;v&#xED; vyb&#xED;r&#xE1;n&#xED;m a ukl&#xE1;d&#xE1;n&#xED;m hodnot\nz/do matic, a proto&#x17E;e Cython nem&#xE1; informace o tom, &#x17E;e jsou to NumPy matice, pou&#x17E;&#xED;v&#xE1; obecn&#xFD;\nprotokol pro pythonn&#xED; kontejnery, tak&#x17E;e se ka&#x17E;d&#xE1; hodnota p&#x159;evede na pythonn&#xED; objekt.</p>\n<p>Je tedy pot&#x159;eba &#x159;&#xED;ct Cythonu, &#x17E;e pou&#x17E;&#xED;v&#xE1;me NumPy matice.\nNa&#x161;t&#x11B;st&#xED; v NumPy existuje integrace s Cythonem, tak&#x17E;e m&#x16F;&#x17E;eme na &#xFA;rovni C &#x201E;naimportovat&#x201C;\nroz&#x161;&#xED;&#x159;en&#xED; pro NumPy:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">cimport</span> <span class=\"n\">numpy</span>\n</pre></div><p>... a potom pou&#x17E;&#xED;t typ &#x201E;dvourozm&#x11B;rn&#xE1; matice cel&#xFD;ch &#x10D;&#xED;sel&#x201C;, kter&#xFD; se v Cythonu jmenuje\n<code>numpy.ndarray[numpy.int64_t, ndim=2]</code>.\nNa&#x161;e funkce tedy bude za&#x10D;&#xED;nat takto:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">cpdef</span> <span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">ndarray</span><span class=\"p\">[</span><span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">int64_t</span><span class=\"p\">,</span> <span class=\"n\">ndim</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"n\">matmul</span><span class=\"p\">(</span>\n        <span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">ndarray</span><span class=\"p\">[</span><span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">int64_t</span><span class=\"p\">,</span> <span class=\"n\">ndim</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"n\">a</span><span class=\"p\">,</span>\n        <span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">ndarray</span><span class=\"p\">[</span><span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">int64_t</span><span class=\"p\">,</span> <span class=\"n\">ndim</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"n\">b</span><span class=\"p\">):</span>\n    <span class=\"n\">cdef</span> <span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">ndarray</span><span class=\"p\">[</span><span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">int64_t</span><span class=\"p\">,</span> <span class=\"n\">ndim</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"n\">result</span>\n    <span class=\"o\">...</span>\n</pre></div><p>Kdybychom si nebyli jist&#xED; typem matice, m&#x16F;&#x17E;eme si ho nadefinovat pomoc&#xED; <code>ctypedef</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">ctypedef</span> <span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">int64_t</span> <span class=\"n\">DATATYPE</span>\n</pre></div><p>...a pak pou&#x17E;&#xED;vat tento alias.\nNa maticov&#xE9; typy bohu&#x17E;el typedef zat&#xED;m nefunguje.</p>\n<p>Pro pr&#xE1;ci s matic&#xED; ASCII znak&#x16F; lze pou&#x17E;&#xED;t typ <code>numpy.int8_t</code>, ale je t&#x159;eba p&#x159;i zapisov&#xE1;n&#xED; p&#x159;&#xED;mo na konkr&#xE9;tn&#xED; pozice zapisovat &#x10D;&#xED;seln&#xFD; typ <code>char</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">cdef</span> <span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">ndarray</span><span class=\"p\">[</span><span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">int8_t</span><span class=\"p\">,</span> <span class=\"n\">ndim</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">]</span>  <span class=\"n\">directions</span> <span class=\"o\">=</span> <span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">full</span><span class=\"p\">((</span><span class=\"n\">h</span><span class=\"p\">,</span> <span class=\"n\">w</span><span class=\"p\">),</span> <span class=\"sa\">b</span><span class=\"s1\">&apos;#&apos;</span><span class=\"p\">,</span> <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"s1\">&apos;a&apos;</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"n\">directions</span><span class=\"p\">[</span><span class=\"n\">maze</span> <span class=\"o\">&gt;=</span> <span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"sa\">b</span><span class=\"s1\">&apos; &apos;</span>  <span class=\"c1\"># Python level, using b&apos; &apos;</span>\n<span class=\"n\">directions</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">==</span> <span class=\"nb\">ord</span><span class=\"p\">(</span><span class=\"s1\">&apos;x&apos;</span><span class=\"p\">)</span>  <span class=\"c1\"># C level, using char</span>\n</pre></div><div class=\"admonition note\"><p>Pou&#x17E;it&#xED; <code>matrix[a, b]</code> je v Cythonu rychlej&#x161;&#xED; ne&#x17E; <code>matrix[a][b]</code>, proto&#x17E;e se\nuvnit&#x159; d&#x11B;j&#xED; jin&#xE9; v&#x11B;ci. P&#x159;i pou&#x17E;it&#xED; <code>matrix[a, b]</code> u matice deklarovan&#xE9; jako\ndvourozm&#x11B;rn&#xE9; pole n&#x11B;jak&#xE9;ho typu Cython p&#x159;istoup&#xED; p&#x159;&#xED;mo k obsahu na &#xFA;rovni\njazyka C. P&#x159;i pou&#x17E;it&#xED; <code>matrix[a][b]</code> se ale d&#x11B;j&#xED; operace dv&#x11B;, nejprve\n<code>matrix[a]</code> vrac&#xED; jeden &#x159;&#xE1;dek matice a a&#x17E; pot&#xE9; <code>[b]</code> vrac&#xED; jeden prvek z\ntohoto &#x159;&#xE1;dku. Ob&#x11B; operace prob&#xED;haj&#xED; na &#xFA;rovni Pythonu a proto budou pomalej&#x161;&#xED;\na p&#x159;i pou&#x17E;it&#xED; <code>--annotate</code> bude &#x159;&#xE1;dek s takovou operac&#xED; ozna&#x10D;en &#x17E;lut&#x11B;.</p>\n</div><h2>Direktivy</h2>\n<p>Anotac&#xED; typ&#x16F; matic se na&#x161;e demo maticov&#xE9;ho n&#xE1;soben&#xED; dostalo skoro na &#xFA;rove&#x148;\nC, ale ne &#xFA;pln&#x11B;: &#x159;&#xE1;dky, kter&#xE9; pracuj&#xED; s maticemi, jsou ve v&#xFD;stupu <code>--annotate</code>\nst&#xE1;le trochu &#x17E;lut&#xE9;.\nCython toti&#x17E; p&#x159;i ka&#x17E;d&#xE9;m p&#x159;&#xED;stupu k matici kontroluje, jestli ne&#x10D;teme nebo\nnezapisujeme mimo pole a p&#x159;&#xED;padn&#x11B; vyvol&#xE1; <code>IndexError</code>.</p>\n<p>Pokud v&#xED;me &#x2013; jako v na&#x161;em p&#x159;&#xED;pad&#x11B; &#x2013; &#x17E;e je takov&#xE1; kontrola zbyte&#x10D;n&#xE1;,\nm&#x16F;&#x17E;eme Cythonu &#x159;&#xED;ct, aby ji ned&#x11B;lal.\nP&#x159;&#xED;stupy mimo pole pak zp&#x16F;sob&#xED; nedefinovan&#xE9; chov&#xE1;n&#xED; (v&#x11B;t&#x161;inou program spadne,\nnebo h&#x16F;&#x159;, bude pracovat se &#x161;patn&#xFD;mi daty).\nKontrola se vyp&#xED;n&#xE1; direktivou <code>boundscheck</code>, kter&#xE1; se d&#xE1; zadat dv&#x11B;ma hlavn&#xED;mi\nzp&#x16F;soby: dekor&#xE1;torem:</p>\n<div class=\"highlight\"><pre><code>@cython.boundscheck(False)\ncpdef funkce():\n    ...\n\n</code></pre></div><p>... nebo p&#x159;&#xED;kazem <code>with</code>:</p>\n<div class=\"highlight\"><pre><code>with cython.boundscheck(False):\n    ...\n\n</code></pre></div><p>... p&#x159;&#xED;padn&#x11B; i pro cel&#xFD; soubor, viz <a href=\"http://cython.readthedocs.io/en/latest/src/reference/compilation.html#how-to-set-directives\">dokumnetace</a>.</p>\n<p>Dal&#x161;&#xED; zaj&#xED;mav&#xE1; direktiva je <code>cython.wraparound(False)</code>, kter&#xE1; podobn&#xFD;m zp&#x16F;sobem\nvyp&#xED;n&#xE1; pythonn&#xED; zp&#x16F;sob indexov&#xE1;n&#xED; z&#xE1;porn&#xFD;mi &#x10D;&#xED;sly: m&#xED;sto indexov&#xE1;n&#xED; od konce\ns n&#xED; dostaneme nedefinovan&#xE9; chov&#xE1;n&#xED;.</p>\n<p>Seznam dal&#x161;&#xED;ch direktiv najdete v <a href=\"http://cython.readthedocs.io/en/latest/src/reference/compilation.html#compiler-directives\">dokumentaci</a>.</p>\n<p>Cython podporuje je&#x161;t&#x11B; blok <code>with cython.nogil:</code>, kter&#xFD; je podobn&#xFD; direktiv&#xE1;m,\nale d&#xE1; se pou&#x17E;&#xED;t jen s <code>with</code>.\nV r&#xE1;mci tohoto bloku je odem&#x10D;en&#xFD; GIL (glob&#xE1;ln&#xED; z&#xE1;mek).\nSm&#xED; se pou&#x17E;&#xED;t, pouze pokud nepracujeme s pythonn&#xED;mi objekty &#x2013; nap&#x159;&#xED;klad kdy&#x17E;\noperujeme jen na obsahu u&#x17E; existuj&#xED;c&#xED;ch matic&#xED;.\nOpak je <code>with cython.gil:</code>, kter&#xFD;m z&#xE1;mek zase zamkneme &#x2013; nap&#x159;&#xED;klad kdy&#x17E;\npot&#x159;ebujeme vyhodit v&#xFD;jimku.</p>\n<h2>Struktury, ukazatele a dynamick&#xE1; alokace</h2>\n<p>P&#x159;esto&#x17E;e v Cythonu m&#x16F;&#x17E;ete pou&#x17E;&#xED;vat pythonn&#xED; <em>n</em>-tice, slovn&#xED;ky, seznamy a dal&#x161;&#xED;\npodobn&#xE9; nehomogenn&#xED; typy, jejich pou&#x17E;it&#xED; je pomal&#xE9;, proto&#x17E;e v&#x17E;dy pracuj&#xED;\ns&#xA0;pythonn&#xED;mi objekty.</p>\n<p>Pokud m&#xE1;te k&#xF3;d, kter&#xFD; pot&#x159;ebuje do&#x10D;asn&#xE9; pole takov&#xFD;ch z&#xE1;znam&#x16F;,\nje pro &#x10D;asov&#x11B; kritick&#xE9; &#x10D;&#xE1;sti k&#xF3;du lep&#x161;&#xED; k probl&#xE9;mu p&#x159;istoupit sp&#xED;&#x161;e &#x201E;c&#xE9;&#x10D;kovsky&#x201C;,\np&#x159;es alokaci pam&#x11B;ti a ukazatele.</p>\n<p>N&#xE1;sleduj&#xED;c&#xED; p&#x159;&#xED;klad ukazuje, jak naplnit pole heterogenn&#xED;ch z&#xE1;znam&#x16F;:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"c1\"># Import funkc&#xED; pro alokaci pam&#x11B;ti &#x2013; chovaj&#xED; se jako malloc() apod.</span>\n<span class=\"kn\">from</span> <span class=\"nn\">cpython.mem</span> <span class=\"nn\">cimport</span> <span class=\"nn\">PyMem_Malloc</span><span class=\"p\">,</span> <span class=\"n\">PyMem_Realloc</span><span class=\"p\">,</span> <span class=\"n\">PyMem_Free</span>\n\n<span class=\"c1\"># Definice struktury</span>\n<span class=\"n\">cdef</span> <span class=\"n\">struct</span> <span class=\"n\">coords</span><span class=\"p\">:</span>\n    <span class=\"nb\">int</span> <span class=\"n\">row</span>\n    <span class=\"nb\">int</span> <span class=\"n\">column</span>\n    <span class=\"n\">char</span> <span class=\"n\">data</span>\n\n<span class=\"n\">MAXSIZE</span> <span class=\"o\">=</span> <span class=\"o\">...</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">path</span><span class=\"p\">(</span><span class=\"o\">...</span><span class=\"p\">):</span>\n    <span class=\"c1\"># Definice ukazatele, p&#x159;etypov&#xE1;n&#xED;</span>\n    <span class=\"n\">cdef</span> <span class=\"n\">coords</span> <span class=\"o\">*</span> <span class=\"n\">path</span> <span class=\"o\">=</span> <span class=\"o\">&lt;</span><span class=\"n\">coords</span> <span class=\"o\">*&gt;</span><span class=\"n\">PyMem_Malloc</span><span class=\"p\">(</span><span class=\"n\">MAXSIZE</span><span class=\"o\">*</span><span class=\"n\">sizeof</span><span class=\"p\">(</span><span class=\"n\">coords</span><span class=\"p\">))</span>\n    <span class=\"k\">if</span> <span class=\"n\">path</span> <span class=\"o\">==</span> <span class=\"n\">NULL</span><span class=\"p\">:</span>\n        <span class=\"c1\"># nedostatek pam&#x11B;ti</span>\n        <span class=\"k\">raise</span> <span class=\"ne\">MemoryError</span><span class=\"p\">()</span>\n\n    <span class=\"n\">cdef</span> <span class=\"nb\">int</span> <span class=\"n\">used</span> <span class=\"o\">=</span> <span class=\"mi\">0</span>\n    <span class=\"k\">for</span> <span class=\"o\">...</span><span class=\"p\">:</span>\n        <span class=\"o\">...</span>\n\n        <span class=\"c1\">#</span>\n        <span class=\"n\">path</span><span class=\"p\">[</span><span class=\"n\">used</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">coords</span><span class=\"p\">(</span><span class=\"n\">row</span><span class=\"p\">,</span> <span class=\"n\">column</span><span class=\"p\">,</span> <span class=\"n\">data</span><span class=\"p\">)</span>\n        <span class=\"n\">used</span> <span class=\"o\">+=</span> <span class=\"mi\">1</span>\n\n    <span class=\"c1\"># pole m&#x16F;&#x17E;eme pou&#x17E;&#xED;vat</span>\n    <span class=\"o\">...</span>\n\n    <span class=\"c1\"># a mus&#xED;me ho p&#x159;ed vr&#xE1;cen&#xED;m p&#x159;ed&#x11B;lat na list</span>\n    <span class=\"n\">lpath</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n    <span class=\"n\">cdef</span> <span class=\"nb\">int</span> <span class=\"n\">i</span>\n    <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">used</span><span class=\"p\">):</span>\n        <span class=\"n\">lpath</span><span class=\"o\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">path</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">])</span>\n\n    <span class=\"c1\"># a uvolnit</span>\n    <span class=\"n\">PyMem_Free</span><span class=\"p\">(</span><span class=\"n\">path</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">lpath</span>\n</pre></div><p>Pro homogenn&#xED; pole ale doporu&#x10D;ujeme sp&#xED;&#x161;e NumPy matice.</p>\n<p>N&#xE1;sleduj&#xED;c&#xED; p&#x159;&#xED;klad ukazuje, jak lze p&#x159;i&#x159;azovat do struktur:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">cdef</span> <span class=\"n\">struct</span> <span class=\"n\">coord</span><span class=\"p\">:</span>\n    <span class=\"nb\">float</span> <span class=\"n\">x</span>\n    <span class=\"nb\">float</span> <span class=\"n\">y</span>\n    <span class=\"nb\">float</span> <span class=\"n\">z</span>\n\n<span class=\"n\">cdef</span> <span class=\"n\">coord</span> <span class=\"n\">a</span> <span class=\"o\">=</span> <span class=\"n\">coord</span><span class=\"p\">(</span><span class=\"mf\">0.0</span><span class=\"p\">,</span> <span class=\"mf\">2.0</span><span class=\"p\">,</span> <span class=\"mf\">1.5</span><span class=\"p\">)</span>\n\n<span class=\"n\">cdef</span> <span class=\"n\">coord</span> <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">coord</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"o\">=</span><span class=\"mf\">0.0</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"o\">=</span><span class=\"mf\">2.0</span><span class=\"p\">,</span> <span class=\"n\">z</span><span class=\"o\">=</span><span class=\"mf\">1.5</span><span class=\"p\">)</span>\n\n<span class=\"n\">cdef</span> <span class=\"n\">coord</span> <span class=\"n\">c</span>\n\n<span class=\"n\">c</span><span class=\"o\">.</span><span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"mf\">42.0</span>\n<span class=\"n\">c</span><span class=\"o\">.</span><span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"mf\">2.0</span>\n<span class=\"n\">c</span><span class=\"o\">.</span><span class=\"n\">z</span> <span class=\"o\">=</span> <span class=\"mf\">4.0</span>\n\n<span class=\"n\">cdef</span> <span class=\"n\">coord</span> <span class=\"n\">d</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s1\">&apos;x&apos;</span><span class=\"p\">:</span><span class=\"mf\">2.0</span><span class=\"p\">,</span>\n                <span class=\"s1\">&apos;y&apos;</span><span class=\"p\">:</span><span class=\"mf\">0.0</span><span class=\"p\">,</span>\n                <span class=\"s1\">&apos;z&apos;</span><span class=\"p\">:</span><span class=\"o\">-</span><span class=\"mf\">0.75</span><span class=\"p\">}</span>\n</pre></div><h2>Pou&#x17E;it&#xED; knihoven z C</h2>\n<p>Pro pou&#x17E;it&#xED; C knihoven z Pythonu je lep&#x161;&#xED; pou&#x17E;&#xED;t <a href=\"http://cffi.readthedocs.io/en/latest/\">CFFI</a>.\nAle kdy&#x17E; u&#x17E; p&#xED;&#x161;ete k&#xF3;d v Cythonu\na pot&#x159;ebujete zavolat n&#x11B;jakou C funkci, m&#x16F;&#x17E;ete to ud&#x11B;lat takto:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">cdef</span> <span class=\"n\">extern</span> <span class=\"kn\">from</span> <span class=\"s2\">&quot;stdlib.h&quot;</span><span class=\"p\">:</span>\n    <span class=\"nb\">int</span> <span class=\"n\">rand</span><span class=\"p\">()</span>\n    <span class=\"n\">void</span> <span class=\"n\">srand</span><span class=\"p\">(</span><span class=\"nb\">long</span> <span class=\"nb\">int</span> <span class=\"n\">seedval</span><span class=\"p\">)</span>\n\n<span class=\"n\">cdef</span> <span class=\"n\">extern</span> <span class=\"kn\">from</span> <span class=\"s2\">&quot;time.h&quot;</span><span class=\"p\">:</span>\n    <span class=\"n\">ctypedef</span> <span class=\"nb\">long</span> <span class=\"n\">time_t</span>\n    <span class=\"nb\">long</span> <span class=\"nb\">int</span> <span class=\"n\">time</span><span class=\"p\">(</span><span class=\"n\">time_t</span> <span class=\"o\">*</span><span class=\"p\">)</span>\n\n<span class=\"n\">srand</span><span class=\"p\">(</span><span class=\"n\">time</span><span class=\"p\">(</span><span class=\"n\">NULL</span><span class=\"p\">))</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">rand</span><span class=\"p\">())</span>\n</pre></div><p>Deklarace m&#x16F;&#x17E;ete vlo&#x17E;it p&#x159;&#xED;mo do <code>.pyx</code> souboru, ale pokud je chcete pou&#x17E;&#xED;vat\nz r&#x16F;zn&#xFD;ch m&#xED;st, pojmenujte soubor <code>.pxd</code>, to v&#xE1;m umo&#x17E;n&#xED; na n&#x11B;j pou&#x17E;&#xED;t <code>cimport</code>.</p>\n<p>Pro &#x10D;&#xE1;sti standardn&#xED; knihovny jsou takov&#xE9; deklarace ji&#x17E; v Cythonu\np&#x159;edp&#x159;ipraven&#xE9;, m&#x16F;&#x17E;ete tedy pou&#x17E;&#xED;t <code>cimport</code> rovnou:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">libc.stdlib</span> <span class=\"nn\">cimport</span> <span class=\"nn\">rand</span><span class=\"p\">,</span> <span class=\"n\">srand</span>\n<span class=\"kn\">from</span> <span class=\"nn\">libc.time</span> <span class=\"nn\">cimport</span> <span class=\"nn\">time</span>\n\n<span class=\"n\">srand</span><span class=\"p\">(</span><span class=\"n\">time</span><span class=\"p\">(</span><span class=\"n\">NULL</span><span class=\"p\">))</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">rand</span><span class=\"p\">())</span>\n</pre></div><h2>Zkratky: <code>pyximport</code> a <code>%%cython</code></h2>\n<p>Pro interaktivn&#xED; pr&#xE1;ci v&#xA0;Jupyter Notebook m&#xE1; Cython vlastn&#xED; &#x201E;magii&#x201C;.\nNa za&#x10D;&#xE1;tku Notebooku m&#x16F;&#x17E;eme zadat:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"o\">%</span><span class=\"n\">load_ext</span> <span class=\"n\">cython</span>\n</pre></div><p>a potom m&#x16F;&#x17E;eme na za&#x10D;&#xE1;tku kter&#xE9;koli bu&#x148;ky zadat <code>%%cython</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"o\">%%</span><span class=\"n\">cython</span>\n\n<span class=\"n\">cpdef</span> <span class=\"nb\">int</span> <span class=\"n\">mul</span><span class=\"p\">(</span><span class=\"nb\">int</span> <span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"nb\">int</span> <span class=\"n\">b</span><span class=\"p\">):</span>\n    <span class=\"k\">return</span> <span class=\"n\">a</span> <span class=\"o\">*</span> <span class=\"n\">b</span>\n</pre></div><p>K&#xF3;d v takov&#xE9; bu&#x148;ce pak Notebook zkompiluje Cythonem a funkce/prom&#x11B;nn&#xE9; v n&#x11B;m\nnadefinovan&#xE9; d&#xE1; k dispozici.</p>\n<p>M&#x16F;&#x17E;eme pou&#x17E;&#xED;t i <code>%%cython --annotate</code>, co&#x17E; vyp&#xED;&#x161;e anotace p&#x159;&#xED;mo do Notebooku.</p>\n<p>Dal&#x161;&#xED; zkratka je modul <code>pyximort</code>, kter&#xFD; d&#xE1;v&#xE1; mo&#x17E;nost importovat moduly <code>.pyx</code>\np&#x159;&#xED;mo: hledaj&#xED; se podobn&#x11B; jako <code>.py</code> nebo <code>.so</code> a p&#x159;ed importem se zkompiluj&#xED;.\nZap&#xED;n&#xE1; se to n&#xE1;sledovn&#x11B;:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">import</span> <span class=\"nn\">pyximport</span>\n<span class=\"n\">pyximport</span><span class=\"o\">.</span><span class=\"n\">install</span><span class=\"p\">()</span>\n\n<span class=\"kn\">import</span> <span class=\"nn\">matmul</span>\n</pre></div><h2>Video</h2>\n<p>P&#x159;ed ned&#xE1;vnem m&#x11B;l <a href=\"https://github.com/hroncok/\">Miro</a> na St&#x159;edisku unixov&#xFD;ch technologi&#xED; nahr&#xE1;vanou uk&#xE1;zku p&#x159;eps&#xE1;n&#xED;\n&#xFA;lohy ruksaku z p&#x159;edm&#x11B;tu MI-PAA z Pythonu do Cythonu (v&#x10D;etn&#x11B; nep&#x159;&#xED;jemn&#xE9;ho z&#xE1;seku a live\nuk&#xE1;zky debugov&#xE1;n&#xED; probl&#xE9;mu).\nNa <a href=\"https://www.youtube.com/watch?v=Ksv4RA6yhkY\">video</a> se m&#x16F;&#x17E;ete pod&#xED;vat, mohlo by v&#xE1;m prozradit spoustu tip&#x16F;, kter&#xE9; se v&#xE1;m mohou hodit\nke spln&#x11B;n&#xED; &#xFA;lohy.\nK obsahu jen dod&#xE1;me, &#x17E;e m&#xED;sto <code>malloc</code> a <code>free</code> je lep&#x161;&#xED; pou&#x17E;&#xED;t <code>PyMem_Malloc</code> a\n<code>PyMem_Free</code> z uk&#xE1;zky v&#xFD;&#x161;e.</p>\n\n\n        "
    }
  }
}