Hlavní navigace

Moduly pro Python

8. 10. 2003
Doba čtení: 6 minut

Sdílet

Před časem vycházel na ROOTu zajímavý seriál o Pythonu jménem Létající cirkus. Autor probral skoro vše od základní syntaxe až po pokročilá témata, nakonec však vynechal psaní rozšiřujících modulů pro Python v jiných programovacích jazycích. Přesně o tom bude náš dvoudílný seriálek, dnešní povídání by mělo být obecně užitečné a příště se zaměříme na objekty.

Python je jazyk, se kterým se počítá. Je stále oblíbenější a má spoustu zajímavých vlastností. A také dvě vlastnosti omezující. Tou první je, že se jedná o jazyk interpretovaný, takže v něm nemá smysl psád kód, kde opravdu hodně záleží na rychlosti. Druhá nepříjemnost vyplývá z prostého faktu, že Python není Céčko, a nemáme proto přímý přístup k obrovskému množství nejrůznějších C/C++ knihoven, které už napsal někdo před námi. V obou případech stačí napsat rozšiřující modul v C. Od laskavého čtenáře? očekávám základní znalost Pythonu a Céčka, v příštím dílu i trochu C++.

Začnu jednoduchým příkladem, modulem s jednou funkcí jménem funkce bez parametrů a bez návratové hodnoty, která napíše „Ahoj světe“ na standardní výstup.

#include <Python.h>

Modul bude využívat pythonské API, proto je třeba nainkludovat příslušný hlavičkový soubor. Pozor na velké ‚P‘.

PyObject* naseFunkce(PyObject* self, PyObject* args)
/* nikoliv void funkce(void), jak možná čekáte */

Takhle vypadá hlavička funkce, kterou budeme volat z Pythonu. PyObject je univerzální typ pro cokoli, a kdyby byl Python napsaný v C++ a ne v C, byl by to určitě kořen dědičnosti celého velkého stromu třid. Argument self je objekt, jehož metodu voláme. V našem případě se jedná o globální funkci, takže self bude vždy NULL. Parametr args je tuple argumentů funkce, k návratové hodnotě se dostanu později.

{
    if(!PyArg_ParseTuple(args,"")) return NULL;
    puts("Ahoj světe!");
    /* Tady bude náš kýžený C kód */
    Py_INCREF(Py_None);
    return Py_None;
}

Funkce volaná z Pythonu vrací pointer na PyObject. Má-li hodnotu NULL, znamená to, že došlo k výjimce. Na prvním řádku jen kontrolujeme, zda byla naseFunkce zavolána z Pythonu správně, tj. bez parametrů. PyArg_ParseTuple se podobá funkci sscanf, první parametr je typ, z něhož chci vyparsovat hodnoty (zde *PyObject, u sscanf řetězec), následuje formátovací řetězec, kde je nějak zakódován počet a typ hodnot. Nakonec přijdou ukazatele na proměnné, které chceme vyplnit, zde jich je celkem 0, neboť formátovací řetězec je prázdný. Když jsme zjistili, že je funkce správně zavolaná, můžeme spustit C kód, kvůli kterému celý ten cirkus provádíme: zapípat na zvukovou kartu, spočítat nejlepší tah v šachovém programu nebo třeba naformátovat disk. Já jsem pro jednoduchost jen vypsal „Ahoj světe“ na stdout. Nakonec je třeba funkci ukončit. Říkal jsem, že funkce nebude nic vracet. Hodnota NULL je rezervovaná pro výjimky, musíme tedy vrátit None, neboli v zápisu pythonského C API Py_None. Onomu nic je třeba ještě před odesláním inkrementovat počet referencí, neboť bude po návratu z funkce zapomenuto, tím se mu sníží počet referencí o 1 a objekty, na které nikdo neukazuje, se, jak známo, v Pythonu mažou.

Modul musí mít navíc inicializační rutinu, která se automaticky zavolá při jeho načtení, my v ní jen interpretru sdělíme mapování funkcí.

PyMethodDef priklad_methods[] = {
    {"funkce", (PyCFunction)naseFunkce, METH_VARARGS, "komentar"},
    {NULL, NULL} /* NULLy pro ukončení bloku*/ };
/* v C++ nezapomenout na extern "C" */
void initpriklad(void)
{     Py_InitModule("priklad", priklad_methods);
}

Pole priklad_methods obsahuje globální funkce modulu, pro každou název v Pythonu, adresu v Céčku a typ a komentář. METH_VARARGS znamená, že funkce nemá klíčové parametry. Od verze Pythonu 2.2 bychom mohli užít i METH_NOARGS, neboť nemá vůbec žádné argumenty, a nemuseli uvnitř funkce volat PyArg_ParseTuple.

Teď už zbývá jen modul přeložit:

$ gcc prikladmodule.c -I/usr/include/python2.2 -shared -o prikladmodule.so

Adresář s hlavičkovými soubory (parametr -I) se samozřejmě může lišit, například verze nebude vždy 2.2 atd. Důležité je, že verze interpretru, na němž funkce z modulu nakonec poběží, a hlavičkových souborů při překladu musí být stejná.

Konečně je vše připraveno a můžeme se pokochat:

$ python
>>> import priklad
>>> priklad.funkce()
>>> Ahoj světe!
>>>

S funkcemi bez parametrů a bez návratových hodnot se toho moc dělat nedá, proto teď náš modul rozšířím o cvičnou funkci otocit, která má jeden povinný parametr – řetězec, jež vypíše na stdout. Druhý nepovinný parametr může být None, int nebo i string, otočí ho a vrátí jako výsledek. None nechá být, číslo vynásobí –1 a string vrátí pozpátku.

PyObject* otocit(PyObject* self, PyObject* args)
{
    char *s;
    int i;
    PyObject *o=NULL;

    if(!PyArg_ParseTuple(args, "s|O", &s, &o)) return NULL;

Parsování parametrů by mělo být každému céčkaři jasné, ořítko (znak ‚|‘) je hranice mezi povinnými a volitelnými parametry, význam písmenek je poměrně intuitivní, za zmínku stojí zejména

  • i – int
  • s – string
  • O – obecný PyObject

a zbytek je v dokumentaci. Při parsování se string nikde nealokuje, vrací se odkaz na existující kopii, takže nevolejte ani předem malloc ani později free.

    puts(s); /* Vypsání povinného parametru */

        if(!o || o==Py_None){
        Py_INCREF(Py_None);
        return Py_None;
    }

Pokud uživatel modulu nezadal při volání funkce druhý parametr, máme v proměnné o NULL, neboť jsme ji tak inicializovali. V tom případě jen korektně ukončíme funkci. Stejně se zachováme, pokud jej explicitně zadal jako None. Test typu o==Py_None je možný, neboť objekt None je v systému jen jednou, u jiných typů je třeba zavolat specifickou funkci.

V případě integeru je onou funkcí PyInt_Check. Tu zavoláme hned po testu na None, a pokud se opravdu jedná o integer, zkonvertujeme objekt na céčkový int, obrátíme znaménko a vrátíme jeho pythonovskou reprezentaci. Názvy funkcí PyInt_AsLong a Py_BuildValue jsou celkem intuitivní:

    if(PyInt_Check(o)){
        i=(int)PyInt_AsLong(o);
        return Py_BuildValue("i", -i);
    }

Ještě musíme ošetřit případ řetězce. To je jen o malinko složitější:

    /* Pokud to je řetězec */
    if(PyString_Check(o)){
        const char *s;
        char *r, *p;
        int l;      /* Převedeme ho na C řetězec */
        s=PyString_AsString(o);      /* Otočíme ho do r */
        l=strlen(s);
        r=(char *)malloc(l+1);         p=r+l;
        *p--=0;
        while(p>=r) *p-- = *s++;
     /* Vytvoříme pythonovský řetězec */
        o=Py_BuildValue("s", r);
        free(r);
     /* ... který vrátíme */
        return o;
    }

Otáčení řetězce tedy také není příliš obtížné. Musíme dát pozor, abychom nemodifikovali návratovou hodnotu PyString_AsString, to jsou interní data Pythonu. Vlastní otáčení (kterému jistě každý céčkař rozumí) proto provádím do řetězce r. Předpokládám, že v řetězci jsou jen jednobytové znaky, což například při kódování národních znaků utf-8 může být problém. Funkce Py_BuildValue si r byte po bytu zkopíruje, takže dealokace řetězce r je na nás.

Poslední možností je, že uživatel našeho modulu se pokouší funkci vnutit parametr nepodporovaného typu. Pomsta bude krutá, vyvoláme výjimku TypeError:

    PyErr_SetString(PyExc_TypeError, "Mohu
     otáčet jen nic, int nebo string");
    return NULL;
} /* konec funkce */

V prvním jednoduchém příkladu s jednou funkcí bez parametrů jsme výjimku vyvolávaliprostým return NULL. Bylo to ovšem pokaždé po neúspěšném volání nějaké funkce pythonovského API, takže jsme nemuseli sami uvádět, o jakou výjimku šlo. Tady jsme chybový stav detekovali přímo v naší funkci, takže nezbývá než „ručně“ zavolat PyErr_SetString.

root_podpora

A to je vlastně vše, neboť zbytek už je stejný jako v našem prvním příkladě. Zbývá jen modul důkladně otestovat:

$ python
>>> import priklad
>>> priklad.otocit("string")
string
>>> priklad.otocit("string", 1)
string
-1
>>> priklad.otocit("string", "Ahoj !")
string
'! johA'
>>> priklad.otocit("string", ["jiny typ"])
string
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: Mohu otáčet jen nic, int nebo string
>>>

Uzavřít článek výjimkou TypeError by bylo jistě demotivující, proto ještě uvádím odkaz na zdrojový kód dnešního příkladu. Příště se můžete těšit na objekty.

Byl pro vás článek přínosný?