V úvodních řádkách jsem nastínil docela praktický problém. Někdo před námi napsal knihovnu v C++ a my ji chceme používat z Pythonu. Celkem přirozeně požadujeme, abychom k ní i z Pythonu přistupovali jako ke třídě a public metody si jedna k jedné odpovídaly.
Pozorný a přemýšlivý čtenář už jedno řešení vlastně zná: k C++ třídě si napíše rozhraní ve stylu C, vytvoří modul (přesně podle předchozího dílu) využívající toto rozhraní a přímo v Pythonu (nikoli jeho C API) je obalí objektově orientovanou obálkou. Řešení bude sice fungovat, ale počet mezivrstev, čistota i efektivita návrhu by mohly sloužit jako odstrašující příklad v učebnicích programování. Mnohem lepší je smrsknout celé rozhraní na jediný pythonovský modul v C++.
Dejme tomu, že třída, kterou chceme z Pythonu využívat, vypadá asi takhle:
class Vypisovac{
public:
// přiřadí data do private položek,
//řetězec naalokuje a zkopíruje
Vypisovac(int pocet = 1, char *str = "Ahoj světe !");
// vypíše pocet * str
void vypis();
// uvolní paměť řetězce
~Vypisovac();
private:
int pocet;
char *str;
};
Její praktické využití je sice diskutabilní, ale pro nás je důležitější, že má aspoň základní atributy tříd, jak je známe z C++, tj. proměnné, konstruktor s volitelnými parametry, metodu a destruktor.
Nyní už k tomu hlavnímu, našemu modulu. Tentokrát jej napíšeme pochopitelně v C++, důvodem ovšem není to, že jde o implementaci pythonovské třídy (objektově orientované programování lze realizovat i bez speciální podpory jazyka – s čistým céčkem bychom si vystačili), musíme ale používat C++ třídu Vypisovac, což by z C nešlo. Zdroják modulu uložíme jako vypisovacmodule.cc.
#include <Python.h> #include "vypisovac.h"
Hlavičkové soubory asi nikoho nepřekvapily.
typedef struct { // Standardní hlavička objektu. PyObject_HEAD // Instance C++ Vypisovace Vypisovac *vypisovac; } VypisovacObject;
Takhle budou v Pythonu vypadat data jedné instance naší třídy. Makro PyObjectHEAD se rozvine na interní data Pythonu, konkrétně počet referencí na objekt a jeho typ, ale to pro nás teď není důležité. Přímo budeme využívat pouze pointer na C++ Vypisovac.
static PyObject* Vypisovac_Konstruktor(PyObject *self, PyObject *args, PyObject *kwd);
static void Vypisovac_Destruktor(PyObject *self);
static PyObject* Vypisovac_Getattr(PyObject *self,char *name);
static PyObject* Vypisovac_Vypis(PyObject *self, PyObject *args);
Pythonovské třídě Vypisovac musíme napsat konstruktor, destruktor, metodu Vypis a getattr. Připomínám, že getattrpoužije interpret Pythonu, když potřebuje přistoupit k nějakému atributu objektu, například metodě Vypis.
static PyTypeObject VypisovacType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "Vypisovac", /*tp_name*/ sizeof(VypisovacObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ Vypisovac_Destruktor, /*tp_dealloc*/ 0, /*tp_print*/ Vypisovac_Getattr, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ };
I samotné třídě odpovídá datová struktura (společná pro všechny instance), má spoustu položek, které naštěstí většinou nepotřebujeme a můžeme je beztrestně nastavit na nulu. Zadali jsme pouze jméno třídy, velikost její instance a dále pointery na destruktor a metodu getattr. Teď napíšeme čtyři funkce: konstruktor, destruktor, getattr a metodu vypis. To už téměř umíme, jde spíš o to, jak říci Pythonu, čemu odpovídá jaká C++ funkce. Konstruktor je běžná globální funkce vracející objekt, na destruktor a getattr máme pointery ve struktuřeVypisovacType a metodou vypis bude za běhu hledat getattr. Konstruktor vypadá takhle:
static PyObject* Vypisovac_Konstruktor(PyObject *self, PyObject *args, PyObject *kwd)
{
static char *kwdlist[] = {"pocet", "str", NULL};
int pocet = 1;
char *str = "Ahoj světe!";
VypisovacObject *v;
// vyparsujeme argumenty
if(!PyArg_ParseTupleAndKeywords(args, kwd, "|is:konstruktor",
kwdlist, &pocet, &str))
return NULL;
// vytvoříme objekt Pythonu
v = PyObject_New(VypisovacObject, &VypisovacType);
// a C++
v -> vypisovac = new Vypisovac(pocet, str);
return (PyObject *)v;
}
Konstruktor má volitelné parametry, proto je v Pythonu přirozené připustit i keyword argumenty, to zajistíme voláním PyArg_ParseTupleAndKeywords místo PyArg_ParseTuple. Interpret musí znát i jména argumentů, takže PyArg_ParseTupleAndKeywords má navíc ještě parametr kwdlist. Text konstruktor za dvojtečkou ve formátovacím parametru se použije při chybovém hlášení, například když uživatel zavolá konstruktor se špatným počtem parametrů. Makro PyObject_New vytvoří nový pythonovský objekt třídyVypisovacType a přetypuje ho na VypisovacObject *.
static void Vypisovac_Destruktor(PyObject *self) { delete ((VypisovacObject *)self) -> vypisovac; PyObject_Del(self); }
Destruktor asi nikoho nepřekvapí. Nejprve smažeme C++ objekt a hned poté jeho pythonovský protějšek. Přejděme ke getattr, v našem případě hledá jen jediný atribut – metodu vypis.
static PyMethodDef MetodyVypisovace[] = {
{"vypis", (PyCFunction)Vypisovac_Vypis, METH_VARARGS, "komentar"},
{NULL, NULL}
};
static PyObject *Vypisovac_Getattr(PyObject *self,char *name)
{
return Py_FindMethod(MetodyVypisovace, self, name);
}
Py_FindMethod projde pole MetodyVypisovace a zkusí najít metodu vypis. V případě neúspěchu (chybné volání z Pythonu) vrátí NULL a vše skončí výjimkou.
static PyObject* Vypisovac_Vypis(PyObject *self, PyObject *args)
{
// vyparsujeme 0 argumentů
if(!PyArg_ParseTuple(args, "")) return NULL;
((VypisovacObject *)self) -> vypisovac -> vypis();
Py_INCREF(Py_None);
return Py_None;
}
Na implementaci metody vypis není nic zajímavého, vše už známe z prvního dílu našeho seriálku. V parametru self máme pointer na VypisovacObject, ovšem je třeba ho přetypovat, neboť prototyp vyžaduje PyObject *.
Zbytek už také známe z minula:
static PyMethodDef MetodyModulu[] = { {"Vypisovac", (PyCFunction) Vypisovac_Konstruktor, METH_VARARGS | METH_KEYWORDS, "komentar"}, {NULL, NULL} }; extern "C" void initvypisovac(void) { Py_InitModule("vypisovac", MetodyModulu); }
Modul přeložíme stejně jako minule, jen místo gcc zavoláme g++ a musíme přilinkovat vypisovací knihovnu.
Teď už se můžeme pokochat:
>>> import vypisovac
>>> v = vypisovac.Vypisovac(str = "retezec", pocet = 3)
>>> v.vypis()
retezec
retezec
retezec
>>>
A to je vše. Kompletní zdrojáky dnešního příkladu (včetně jednoduchého Makefilu) si můžete stáhnout zde.
Tímto bych náš dvoudílný seriál uzavřel. Sám jsem se s Pythonem seznámil spíše letmo a netroufám si na další pokračování. Ostatně není to ani zapotřebí. Myslím, že absolvent těchto dvou lekcí by již měl bez problémů strávit dokumentaci k C API na stránkách Pythonu.