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