Hlavní navigace

Moduly pro Python

Jan Němec 8. 10. 2003

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.

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.

Našli jste v článku chybu?

16. 10. 2003 10:49

Jan Němec (neregistrovaný)

Pokud by API vytvářelo kopie, bylo by nepoužitelné pro pomalost. (Řetězec není jen "Ahoj světe", ale třeba taky obrovský text z databáze, z něhož chcete použít jen pár znaků.) C API Pythonu není Python, je to z hlediska tohoto jazyka velmi nízkoúrovňová záležitost. Je celkem přirozené očekávat od nízkoúrovňového programátora základní znalosti a určitou obezřetnost. Psát modul v C je z pohledu Pythonisty totéž jako psát ovladač hardware z pohledu aplikačního programátora. V obou případech ztrác…

15. 10. 2003 14:07

Michal Krause (neregistrovaný)

Možná si tak docela nerozumíme, mně rozhodně nejde o pohodlí programátora modulů. Představme si ale například následující situaci:

===
import priklad

class MyClass:
name = ""
def setName(self, name):
self.name = name

def printName(self):
print self.name

s = "My Name"
m = MyClass()
m.setName(s)
m.printName()
priklad.otocit(s)
m.printName()
===

(odsazování kódu v příspěvku nepřežije, ale myslím, že to i tak bude snad…




















Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Podnikatel.cz: Víme první výsledky doby odezvy #EET

Víme první výsledky doby odezvy #EET

Root.cz: 250 Mbit/s po telefonní lince, když máte štěstí

250 Mbit/s po telefonní lince, když máte štěstí

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

Vitalia.cz: Znáte „černý detox“? Ani to nezkoušejte

Znáte „černý detox“? Ani to nezkoušejte

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Root.cz: Vypadl Google a rozbilo se toho hodně

Vypadl Google a rozbilo se toho hodně

Lupa.cz: Insolvenční řízení kvůli cookies? Vítejte v ČR

Insolvenční řízení kvůli cookies? Vítejte v ČR

Měšec.cz: Air Bank zruší TOP3 garanci a zdražuje kurzy

Air Bank zruší TOP3 garanci a zdražuje kurzy

Podnikatel.cz: Babiš: E-shopy z EET možná vyjmeme

Babiš: E-shopy z EET možná vyjmeme

Podnikatel.cz: EET: Totálně nezvládli metodologii projektu

EET: Totálně nezvládli metodologii projektu

Podnikatel.cz: Na poslední chvíli šokuje výjimkami v EET

Na poslední chvíli šokuje výjimkami v EET

Vitalia.cz: Tesco: Chudá rodina si koupí levné polské kuře

Tesco: Chudá rodina si koupí levné polské kuře

Vitalia.cz: Chtějí si léčit kvasinky. Lék je jen v Německu

Chtějí si léčit kvasinky. Lék je jen v Německu

Root.cz: Certifikáty zadarmo jsou horší než za peníze?

Certifikáty zadarmo jsou horší než za peníze?

Vitalia.cz: Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu

Vitalia.cz: Dáte si jahody s plísní?

Dáte si jahody s plísní?

Lupa.cz: Google měl výpadek, nejel Gmail ani YouTube

Google měl výpadek, nejel Gmail ani YouTube

DigiZone.cz: Rádio Šlágr má licenci pro digi vysílání

Rádio Šlágr má licenci pro digi vysílání