Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Grzegorz 'baziorek' Bazior
Inne artykuły

[C++, Python] Zagnieżdżanie języka Python wewnątrz aplikacji C/C++

[artykuł] Niniejszy artykuł omawia zagnieżdżanie wywołań języka Python w programach napisanych w C/C++ przy użyciu Python C API.

Intro -po co

Mimo pozorów możliwość zagnieżdżania języków skryptowych w programach kompilowanych ma wiele zalet i naprawdę się to przydaje nawet w realnym życiu, powody używania zagnieżdżonych języków skryptowych m.in.:
  • jeśli chcemy użyć pewnych bibliotek napisanych w językach skryptowych, które nie są dostępne bezpłatnie dla danego języka
  • jeśli chcemy napisać jakąś funkcjonalność do naszego projektu szybciej (np. transportowanie plików przez sieć)
  • jeśli nie posiadamy, lub nie możemy/nie chcemy zainstalować sobie dodatkowej biblioteki do zrobienia czegoś w naszym języku programowania (np. transfer plików przez sftp)
  • jeśli chcemy dostarczyć wygodny interfejs do pisania dodatków do naszej aplikacji
  • i inne.

O czym będzie ten artykuł?

W tym artykule zajmę się przykładami użycia interpretera języka python 2.X z poziomu programów napisanych w językach C/C++. Na systemie Windows będę pracował na Visual Studio 2015 Community Edition, chociaż poniższe kody powinny działać również na systemie Linux. W artykule znajdują się opisy użycia Python C Api, nie będę się zajmował opisem biblioteki boost::python http://www.boost.org/doc/libs​/1_58_0/libs/python/doc/. Artykuł skupia się na zagnieżdżaniu wywołań języka python z poziomu C/C++, nie będę opisywał działania w drugą stronę, tj. jak pisać moduły w C/C++, które będą używane z poziomu pythona.

Instalacja na systemie Windows

Na początek konieczne jest pobranie pythona, najlepiej z oficjalnej strony: https://www.python.org​/downloads/ , ja pobrałem python 2.7.10. Oprócz pythona potrzebujemy jakiś kompilator do C++, polecam Visual Studio 2015 Community, gdyż pisze się w nim bardzo wygodnie, oraz jest darmowy https://www.visualstudio.com​/en-us/downloads​/download-visual-studio-vs.aspx

Tworzenie projektu w Visual Studio

Zakładam, że mamy wszystko co powyżej opisałem pobrane i zainstalowane, czas więc na utworzenie nowego projektu do użycia Python C api.
1. Uruchamiamy Visual Studio
2. Wybieramy File > New > Project, następnie wybieramy po prawej stronie: Templates > Visual C++, oraz z listy proponuje Empty Project, pozostaje nam tylko nadać mu jakąś nazwę i kliknąć OK.
3. Dodajmy plik z funkcją main(), oraz kod, który będzie używał interpretera języka python:
wybieramy: File > New > File, następnie po prawej stronie wybieramy Visual C++, C++ File.
4. Pozostaje teraz ustawić ścieżkę do includów z nagłówkami pythona, w tym celu klikamy prawym przyciskiem na projekcie, wybieramy Properties -> C/C++ -> General -> Additional Include Directories i tam dopisujemy ścieżkę gdzie mamy includy do pythona, jeśli mamy zainstalowanego w standardowej lokalizacji będzie to najprawdopodobniej C:\PythonXY (u mnie Python27) , oczywiście możemy również skopiować zainstalowanego pythona do projektu i ustawić ścieżki względne, dzięki temu projekt będzie zajmował więcej, ale za to będzie działał od razu po przeniesieniu na inny komputer (tak naprawdę będzie konieczne jeszcze dokonanie pewnych modyfikacji zmiennych środowiskowych do tego, ale o tym później).
Ja właśnie przekopiowałem sobie pythona do projektu, w tym celu z C:\Python27 skopiowałem do mojego projektu C:\Python27\include oraz C:\Python27\libs do ścieżek odpowiednio $(SolutionDir)Python27\include i $(SolutionDir)Python27\libs (SolutionDir to katalog w którym znajduje się bezpośrednio plik projektu *.sln), a w Additional Include Directories wpisałem ..\Python27\include
5. Kolejną rzeczą jest konieczność ustawienia danych wejściowych do linkera, opiszę dwa sposoby jak to zrobić:
a) Klikamy na projekcie pracym przyciskiem, wybieramy Properties -> Linker -> General -> Additional Library Directories i tam dodajemy ścieżkę do liba języka python, najpewniej będzie to: C:\PythonXY\libs, oczywiście mając pythona w projekcie Visual Studio najlepiej użyć ścieżek względnych. Mając podane ścieżki musimy jeszcze dodać konkretne biblioteki do języka python w ustawieniach linkera: Linker -> Input -> Additional Dependencies i tam dla wersji Release wybieramy pythonXY.lib, gdzie XY to wersja, w moim przypadku jest to python27.lib, dla wersji Debug wybieramy pythonXY_d.lib (chociaż wersja Debug nie jest dostarczana standardowo z językiem python, dlatego musimy działać albo na wersji Release, albo sami sobie zbudować pythona w wersji Debug).
b) Jest jeszcze drugi, prostrzy sposób: przeciągamy pythonXY.lib i pythonXY_d.lib do projektu w VS, wtedy jest on dodawany do Resource Files i kompilacja wraz z linkowaniem powinna zakończyć się sukcesem.
6. Kompilacja przebiegnie pomyślnie, potrzebujemy jeszcze uporać się z współdzielonymi bibliotekami, m.in. pythonXY.dll, mamy wiele możliwości, najprościej skopiować wszystkie wymagane dllki do Windows\system32, jeśli nie mamy tam dostępu możemy również skopiować ten plik do katalogu z plikami exe $(SolutionDir)Debug lub $(SolutionDir)Release.
Jeśli ktoś chce mieć porządek w projekcie może zamiast dodawać do katalogów z binarkami przekopiować ten plik gdzie indziej do projektu, ja np. skopiowałem C:\Windows\SysWOW64\python27.dll -> $(SolutionDir)Python27/, następnie trzeba dodać tę ścieżkę do zmiennej środowiskowej PATH dla projektu:
Properties -> Debugging -> Environment, dodałem następującą treść: PATH=%PATH%;$(SolutionDir)Python27.
7. Jeśli już nam działa zagnieżdżony python to się cieszymy, natomiast jeśli pojawi się komunikat w konsoli: "ImportError: No module named site" -przyczyną tego błędu jest to, że brakuje zmiennych środowiskowych z modułami, potrzebujemu zmienić zmienne środowiskowe: PATH, PYTHONHOME, PYTHONPATH tak aby wskazywały na lokalizację pythona, ja również skopiowałem do swojego projektu moduły pythona:
C:\Python27\Lib -> $(SolutionDir)Python27\Lib
następnie ustawiłem zmienne środowiskowe (ważne jest aby nie dawać slashy "\" na koniec zmiennych gdyż wynikają z tego problemy:
Properties -> Debugging -> Environment

PYTHONHOME=$(SolutionDir)Python27
PYTHONPATH=$(SolutionDir)Python27\Lib
PATH=%PATH%;$(SolutionDir)Python27
*8. Jeśli już kopiujemy do projektu pythona dobrze jeszcze skopiować folder C:\Python27\DLLs -> $(SolutionDir)Python27\DLLs

Instalacja na systemie Linuxie

Dla tego systemu pokażę najprostszy scenariusz:
1. Instalujemy pythona dla naszego systemu (aby była zgodność z tutorialem wersję 2.X.Y). Oprócz tego potrzebne nam będzie pythonX.Y-dev
2. Po instalacji mamy również narzędzie zwracające flagi kompilacji. Zacznijmy od wersji pythona, która można pobrać np. tak:

PYTHON_VERSION=$(echo python$(python -c 'import_ sys; print sys.version[:3]'))
w moim przypadku komenda: echo $PYTHON_VERSION da w wyniku python2.7
includy do kompilacji otrzymujemy w następujący sposób:

$(PYTHON_VERSION)-config --cflags
oraz flagi do linkera:

$(PYTHON_VERSION)-config --ldflags

Przykłady użycia Python C Api

Po tych wszystkich dłuuugich konfiguracjach w końcu możemy przejść do praktyki.

Pierwszy przykład -pobranie ścieżki roboczej

Poniżej przykładowy kod, który pobiera aktualną ścieżkę roboczą, a pod nim wyjaśnienie pewnych miejsc.
C/C++
#include <Python.h> // 1

using namespace std;

int main( int argc, char * argv[] )
{
    Py_Initialize(); // 2
   
    PyRun_SimpleString(
    "import os\n"
    "print os.getcwd()\n"
    ); // 3
    Py_Finalize(); // 4
}

Teraz wyjaśnienie

1. W dokumentacji zalecane jest includowanie Python.h jako pierwszy z includów gdyż przedefiniowywuje niektóre standardowe makra.
2. Inicjacja interpretera języka python
3. Wywołanie skryptu języka python.
4. "Odinicjowanie" interpretera języka python.

Na uwagę zasługuję również fakt, że funkcje języka python zaczynają się na Py lub _Py, wysoce zalecane jest nie definiować własnych funkcji z takimi prefiksami.

Przekazanie argumentów uruchomienia programu

C/C++
#include <Python.h>
#include <iostream>

using namespace std;

int main( int argc, char * argv[] )
{
    Py_Initialize();
   
    PySys_SetArgv( argc, argv ); // 1
   
    PyRun_SimpleString(
    "import sys\n"
    "print sys.argv\n"
    );
    Py_Finalize();
}
1. Przekazanie argumentów uruchomienia programu do interpretera języka python, funkcja wywoływana po Py_Initialize().

Wydruk z konsoli


['C:\\Users\\Grzegorz\\Desktop\\PythonArtykul\\Release\\PythonArtykul.exe']

Pobieranie zmiennych z modułów

Teraz czas na pobranie jakiejś stałej, np. popularnej liczby PI lub eksponenty, której nie ma w standardzie języka C++.
Chcę odpalić następujący kod, ale tym razem bez wstawiania całego tekstu do interpretera, lecz bezpośrednio wywołując kod pythona:

import math
math.pi
Oto kod wywołujący powyższe linie w C++:
C/C++
#include <Python.h>
#include <iostream>
#include <iomanip> // setprecision()

using namespace std;

double GetConstantFromModule( const char * pyModuleName, const char * pyConstantName )
{
    PyObject * pString = PyString_FromString( pyModuleName );
    PyObject * pModule = PyImport_Import( pString );
    PyObject * pDouble = PyObject_GetAttrString( pModule, pyConstantName );
   
    double returnConstant = PyFloat_AsDouble( pDouble );
   
    Py_DECREF( pString );
    Py_DECREF( pModule );
    Py_DECREF( pDouble );
   
    return returnConstant;
}

int main( int argc, char * argv[] )
{
    Py_Initialize();
   
    cout << setprecision( 10 ) << fixed;
    cout << "pi = " << GetConstantFromModule( "math", "pi" ) << endl;
    cout << "e  = " << GetConstantFromModule( "math", "e" ) << endl;
   
    Py_Finalize();
   
    std::cin.get(); // for Windows
}

Wydruk:


pi = 3.1415926536
e  = 2.7182818285

Sprawdzanie czy nie wystąpił błąd

Oczywiście powyższy kod jest całkowicie pozbawiony sprawdzania błędów, dlatego takowe dodamy, poniżej powyższa funkcja dodatkowo ze sprawdzeniami wywołania funkcji pythonicznych:
C/C++
double GetConstantFromModule( const char * pyModuleName, const char * pyConstantName ) // 1
{
    try
    {
        PyObject * pString = PyString_FromString( pyModuleName );
        if( NULL == pString )
             throw runtime_error( "Nie mozna przerobic podanego tekstu na pyString" );
       
        PyObject * pModule = PyImport_Import( pString );
        if( NULL == pModule )
        {
            Py_DECREF( pString );
            throw runtime_error( "Nie mozna zaimportowac modulu" );
        }
       
        PyObject * pConstant = PyObject_GetAttrString( pModule, pyConstantName );
        if( NULL == pConstant )
        {
            Py_DECREF( pString );
            Py_DECREF( pModule );
            throw runtime_error( "Nie mozna odwolac sie do stalej" );
        }
       
        double returnConstant = PyFloat_AsDouble( pConstant );
       
        Py_DECREF( pString );
        Py_DECREF( pModule );
        Py_DECREF( pConstant );
       
        return returnConstant;
    }
    catch( const runtime_error & e )
    {
        PyErr_Print(); // 2
        throw;
    }
}

Wyjaśnienia

1. Nie sprawdzam czy podany const char* nie jest NULLem, we własnych programach dobrze jest sprawdzać również to.
2. Tutaj tylko wypisujemy błąd, aby pobrać treść błędu musimy powyższego catcha zrobić tak:
C/C++
catch( const runtime_error & e )
{
    PyObject * pType, * pValue, * pTraceback;
    PyErr_Fetch( & pType, & pValue, & pTraceback );
   
    string newErrorMessage( e.what() );
    newErrorMessage = newErrorMessage + ", poniewaz: " + PyString_AsString( pValue );
   
    if( ptype )
         Py_DECREF( ptype );
   
    if( pvalue )
         Py_DECREF( pvalue );
   
    if( ptraceback )
         Py_DECREF( ptraceback );
   
    throw runtime_error( newErrorMessage );
}
Szczegóły, a zarazem źródło: http://stackoverflow.com/a​/1418703/1350091

Wywoływanie funkcji z modułów

Teraz czas na przykład jak wywoływać funkcje. Oczywiście przerobiłem powyższy przykład tak aby przyjmował nie tylko funkcję, ale również tak jak do wcześniej także stałe:
C/C++
#include <Python.h>
#include <iostream>
#include <string>
#include <iomanip> // setprecision()
#include <ctime>
#include <stdexcept>

using namespace std;

double CallArgumentlessFunctionFromModule( const char * pyModuleName, const char * pyFunctionName )
{
    try
    {
        PyObject * pString = PyString_FromString( pyModuleName );
        if( NULL == pString )
             throw runtime_error( "Nie mozna przerobic podanego tekstu na pyString" );
       
        PyObject * pModule = PyImport_Import( pString );
        Py_DECREF( pString ); // 1
        if( NULL == pModule )
             throw runtime_error( "Nie mozna zaimportowac modulu" );
       
        PyObject * pFunctionObject = PyObject_GetAttrString( pModule, pyFunctionName );
        Py_DECREF( pModule );
        if( NULL == pFunctionObject )
             throw runtime_error( "Nie mozna pobrac obiektu funkcji" );
       
        double returnValue;
        if( PyCallable_Check( pFunctionObject ) ) // 2
        {
            PyObject * pFunctionResult = PyObject_CallFunction( pFunctionObject, /*format*/ NULL ); // 3
            Py_XDECREF( pFunctionObject );
            if( NULL == pFunctionResult )
            {
                Py_XDECREF( pFunctionObject ); // 4
                throw runtime_error( "Nie mozna wywolac funkcji" );
            }
           
            returnValue = PyFloat_AsDouble( pFunctionResult );
           
            Py_DECREF( pFunctionResult );
        }
        else
        {
            returnValue = PyFloat_AsDouble( pFunctionObject );
            Py_DECREF( pFunctionObject );
        }
       
        return returnValue;
    }
    catch( const runtime_error & e )
    {
        PyObject * ptype, * pvalue, * ptraceback;
        PyErr_Fetch( & ptype, & pvalue, & ptraceback );
       
        string newErrorMessage( e.what() );
        newErrorMessage = newErrorMessage + ", poniewaz: " + PyString_AsString( pvalue );
       
        if( ptype )
             Py_DECREF( ptype );
       
        if( pvalue )
             Py_DECREF( pvalue );
       
        if( ptraceback )
             Py_DECREF( ptraceback );
       
        throw runtime_error( newErrorMessage );
    }
}

int main( int argc, char * argv[] )
{
    Py_Initialize();
   
    cout << setprecision( 10 ) << fixed;
    try
    {
        cout << "time.time()     = " << CallArgumentlessFunctionFromModule( "time", "time" ) << endl;
        cout << "time() from C++ = " << time( NULL ) << endl << endl;
       
        cout << "math.pi         = " << CallArgumentlessFunctionFromModule( "math", "pi" ) << endl;
       
        cout << "math.brakujaca  = " << CallArgumentlessFunctionFromModule( "math", "brakujacaStala" ) << endl;
    }
    catch( const runtime_error & e )
    {
        cerr << "Nie udalo sie pobrac stalej, gdyz: " << e.what() << endl;
    }
   
    Py_Finalize();
}

A oto wydruk programu, poniżej wyjaśnienia


time.time()     = 1449703324.1809999943
time() from C++ = 1449703324

math.pi         = 3.1415926536
Nie udalo sie pobrac stalej, gdyz: Nie mozna pobrac obiektu funkcji, poniewaz: '
module' object has no attribute 'brakujacaStala'

Wyjaśnienie pewnych fragmentów powyższego kodu:

1. Zwalnianie referencji do obiektów można dokonać gdy już wiemy, że nie będą nam potrzebny dany obiekt, nie jest konieczne czekanie na sam koniec funkcji.
2. Sprawdzam czy pytoniczny obiekt jest funkcją.
3. Przy użyciu tej funkcji wywołuję funkcję pytoniczną, drugi argument to format podanych argumentów, po nim może nastąpić zmienna liczba argumentów.
4. Zwalnianie referencji do obiektu funkcyjnego odbywa się w inny sposób niż zwykłego obiektu.

Zwracanie typów pytona do C++

Powyższą funkcję języka C++ można by tak przerobić żeby przyjmowała zmienną liczbę argumentów do funkcji pythonicznej, oraz zwracała inny typ. Do zwracania typu przydatne okazały by się szablony i specjalizacje szablonów:
C/C++
template < typename ReturnType >
ReturnType ReturnValueAs( PyObject * pValue ); // 1

template <>
double ReturnValueAs < double >( PyObject * pValue ) // 2
{
    if( 0 == PyFloat_Check( pValue ) )
         throw invalid_argument( "Invalid type of argument" );
   
    return PyFloat_AsDouble( pValue );
}

template <>
string ReturnValueAs < string >( PyObject * pValue )
{
    if( 0 == PyString_Check( pValue ) )
         throw invalid_argument( "Invalid type of argument" );
   
    char * result = PyString_AsString( pValue ); // 3
    if( NULL == result )
         throw invalid_argument( "Invalid type of argument" );
   
    return result;
}

template <>
long ReturnValueAs < long >( PyObject * pValue )
{
    if( 0 == PyLong_Check( pValue ) )
         throw invalid_argument( "Invalid type of argument" );
   
    return PyInt_AsLong( pValue );
}

template <>
int ReturnValueAs < int >( PyObject * pValue )
{
    return ReturnValueAs < long >( pValue );
}

template <>
vector < PyObject *> ReturnValueAs < vector < PyObject *>>( PyObject * pValue ) // 4
{
    if( 0 == PyList_Check( pValue ) )
         throw invalid_argument( "Invalid type of argument" );
   
    const unsigned pythonListSize = PyList_Size( pValue );
    vector < PyObject *> returnVector;
    returnVector.reserve( pythonListSize );
   
    for( unsigned i = 0; i < pythonListSize; ++i )
    {
        PyObject * pCurrentValue = PyList_GetItem( pValue, i );
        if( NULL == pCurrentValue )
             throw invalid_argument( "Invalid type of argument" );
       
        returnVector.push_back( pCurrentValue );
    }
    return returnVector;
}

template <>
map < int, PyObject *> ReturnValueAs < map < int, PyObject *>>( PyObject * pValue ) // 5
{
    if( 0 == PyDict_Check( pValue ) )
         throw invalid_argument( "Invalid type of argument" );
   
    PyObject * dictionaryKeys = PyDict_Keys( pValue );
    vector < PyObject *> keysAsVector = ReturnValueAs < vector < PyObject *>>( dictionaryKeys );
   
    map < int, PyObject *> returnDictionary;
    for( unsigned i = 0; i < keysAsVector.size(); ++i )
    {
        PyObject * valueForTheKey = PyDict_GetItem( pValue, keysAsVector[ i ] );
        int keyAsInt = ReturnValueAs < int >( keysAsVector[ i ] );
       
        if( valueForTheKey )
             returnDictionary.insert( make_pair( keyAsInt, valueForTheKey ) );
       
    }
    return returnDictionary;
}

Słowem wyjaśnienia

1. Deklarujemy szablon funkcji bez definicji, wszak po co nam definicja jeżeli nie ma ogólnego sposobu dla wszystkich typów.
2. Specjalizacja całkowita dla konkretnego typu -przypominam, że są to szablony funkcji, których nie można specjalizować częściowo.
3. Dla niektórych typów dobrze jest dokonać jeszcze dodatkowego sprawdzenia, w tym przypadku PyString_AsString() może zwrócić NULL, a konstruktor std::string w standardzie nie ma zdefiniowanego zachowania jeśli konstruuje się go z NULLa.
4. W języku python mamy również kontenery takie jak listę, słownik, tuplę. Niestety używając szablonów funkcji nie mogę dokonać specjalizacji częściowej, dlatego typem w pojemniku vector jest typ PyObject*, zresztą w języku python nie ma tak silnej typizacji jak w C++, dlatego pytoniczna lista może zawierać różne typów obiektów.
5. Podobnie jak powyżej z powodu braku specjalizacji częściowej dla szablonów funkcji przyjąłem, że kluczem jest typ int.

W pythonie mamy również tuplę, ale specjalizacja dla niej byłaby taka sama jak dla listy z użyciem analogicznych funkcji:
PyTuple_Check(), PyTuple_Size(), PyTuple_GetItem().

Konwersja typów C++ na PyObject

Tak jak mieliśmy konwertowanie PyObject* na typ, tak przyda się nam analogiczna konwersja w drugą stronę:
C/C++
template < typename T >
PyObject * ValueAsPyObject( const T & value );

template <>
PyObject * ValueAsPyObject < double >( const double & value )
{
    return PyFloat_FromDouble( value );
}

template <>
PyObject * ValueAsPyObject < long >( const long & value )
{
    return PyLong_FromLong( value );
}

template <>
PyObject * ValueAsPyObject < int >( const int & value )
{
    return ValueAsPyObject < long >( value );
}

template <>
PyObject * ValueAsPyObject < string >( const string & value )
{
    return PyString_FromString( value.c_str() );
}

template <>
PyObject * ValueAsPyObject < char >( const char & value )
{
    char arr[] = { value, '\0' };
    return PyString_FromString( arr );
}

Wywoływanie funkcji z argumentami

Funkcje częściej wywołuję się z argumentami niż bez. W przykładowym kodzie rozbiłem bardziej pewne funkcjonalności na funkcje celem zwiększenia czytelności:
C/C++
PyObject * ImportPythonModule( const char * pyModuleName ) // throw(invalid_argument, runtime_error)
{
    if( NULL == pyModuleName )
         throw invalid_argument( "Podano wartosc NULL, to niedopuszczalne!" );
   
    PyObject * pString = PyString_FromString( pyModuleName );
    if( NULL == pString )
         throw runtime_error( "Nie mozna przerobic podanego tekstu na pyString" );
   
    PyObject * pModule = PyImport_Import( pString );
    Py_DECREF( pString );
    if( NULL == pModule )
         throw runtime_error( "Nie mozna zaimportowac modulu" );
   
    return pModule;
}

PyObject * GetFunctionFromPythonModule( PyObject * pModule, const char * pyFunctionName )
{
    PyObject * pFunctionObject = PyObject_GetAttrString( pModule, pyFunctionName );
    if( NULL == pFunctionObject )
         throw runtime_error( "Nie mozna pobrac obiektu funkcji" );
   
    if( !PyCallable_Check( pFunctionObject ) ) // 1
         throw runtime_error( "Pobrany obiekt nie jest funkcja" );
   
    return pFunctionObject;
}

template < typename Arg >
PyObject * PrepareArgumentsToFunction( const initializer_list < Arg >& args )
{
    PyObject * pTupleWithArguments = PyTuple_New( args.size() );
   
    unsigned counter = 0;
    for( auto i: args )
    {
        PyObject * pArgument = ValueAsPyObject( i );
        PyTuple_SetItem( pTupleWithArguments, counter, pArgument ); // 2
        ++counter;
    }
    return pTupleWithArguments;
}

void HandlePythonException( string errorMessagePrefix ) // throw(runtime_error)
{
    PyObject * ptype, * pvalue, * ptraceback;
    PyErr_Fetch( & ptype, & pvalue, & ptraceback );
   
    if( ptype )
         Py_DECREF( ptype );
   
    if( pvalue )
    {
        errorMessagePrefix = errorMessagePrefix + ", poniewaz: " + PyString_AsString( pvalue );
        Py_DECREF( pvalue );
    }
    if( ptraceback )
         Py_DECREF( ptraceback );
   
    throw runtime_error( errorMessagePrefix );
}

template < typename ReturnType, typename Arg >
ReturnType CallFunctionFromModule( const char * pyModuleName, const char * pyFunctionName, initializer_list < Arg > args )
{
    try
    {
        PyObject * pModule = ImportPythonModule( pyModuleName );
       
        PyObject * pFunctionObject = GetFunctionFromPythonModule( pModule, pyFunctionName );
        Py_DECREF( pModule );
       
        PyObject * pArgs = PrepareArgumentsToFunction( args );
       
        PyObject * pFunctionResult = PyObject_Call( pFunctionObject, pArgs, NULL );
        Py_XDECREF( pFunctionObject );
       
        if( NULL == pFunctionResult )
             throw runtime_error( "Nie mozna wywolac funkcji" );
       
        ReturnType returnValue = ReturnValueAs < ReturnType >( pFunctionResult );
       
        Py_DECREF( pFunctionResult );
       
        return returnValue;
    }
    catch( const runtime_error & e )
    {
        HandlePythonException( e.what() );
    }
}

template < typename ReturnType >
ReturnType CallFunctionFromModule( const char * pyModuleName, const char * pyFunctionName ) // 3
{
    return CallFunctionFromModule < ReturnType >( pyModuleName, pyFunctionName, initializer_list < int >() );
}

A wykorzystujemy powyższy kod w następujący sposób:
C/C++
int main( int argc, char * argv[] )
{
    Py_Initialize();
   
    cout << setprecision( 10 ) << fixed;
   
    try
    {
        cout << "time.time()        = " << CallFunctionFromModule < double >( "time", "time" ) << endl;
        cout << "math.factorial(10) = " << CallFunctionFromModule < int >( "math", "factorial", { 10 } ) << endl;
        cout << "math.log(8, 2)     = " << CallFunctionFromModule < double >( "math", "log", { 8, 2 } ) << endl;
    }
    catch( const exception & e )
    {
        cerr << "Nie udalo sie wywolac funkcji, gdyz: " << e.what() << endl;
    }
   
    Py_Finalize();
}

Wydruk


time.time()        = 1449763403.8710000515
math.factorial(10) = 3628800
math.log(8, 2)     = 3.0000000000

Wielokrotnie zagnieżdrzane moduły

Teraz kolejny przykład -jak odnieść się do elementów gdy mamy wielokrotne zagnieżdżenie, oraz jak przekazać/pobrać string, szczegóły w poniższej funkcji, która dostarcza funkcjonalność, której nie ma w standardzie języka C/C++:
C/C++
string GetAbsolutePathOfFile( const string & fileName )
{
    /* import os
       os.path.abspath(fileName) */
   
    try
    {
        PyObject * pModule = ImportPythonModule( "os" );
        PyObject * pPath = PyObject_GetAttrString( pModule, "path" );
        Py_DECREF( pModule );
        if( NULL == pPath )
             throw runtime_error( "Nie mozna odwolac sie do atrybutu path" );
       
        PyObject * pFunctionObject = GetFunctionFromPythonModule( pPath, "abspath" );
        Py_DECREF( pPath );
       
        PyObject * pArgs = PrepareArgumentsToFunction( { fileName } );
       
        PyObject * pFunctionResult = PyObject_Call( pFunctionObject, pArgs, NULL );
        Py_XDECREF( pFunctionObject );
        Py_DECREF( pArgs );
        if( NULL == pFunctionResult )
             throw runtime_error( "Nie mozna wywolac funkcji" );
       
        string returnValue = ReturnValueAs < string >( pFunctionResult );
       
        Py_DECREF( pFunctionResult );
       
        return returnValue;
    }
    catch( const runtime_error & e )
    {
        HandlePythonException( e.what() );
    }
}
Możemy użyć tego np. tak:
C/C++
cout << GetAbsolutePathOfFile( __FILE__ ) << endl;

Użycie funkcji nieprzynależącej do jakiegokolwiek modułu

Nie zawsze musimy używać modułów, oto przykład bez modułu -powiększanie znaków:
C/C++
string ToUpper( const string & s )
{
    // s.upper()
    PyObject * pString = PyString_FromString( s.c_str() );
    if( NULL == pString )
         throw runtime_error( "Nie mozna przerobic podanego tekstu na pyString" );
   
    PyObject * pUpperAsPyString = PyString_FromString( "upper" );
    Py_DECREF( pString );
    if( NULL == pUpperAsPyString )
         throw runtime_error( "Nie mozna przerobic podanego tekstu na pyString" );
   
    PyObject * pResult = PyObject_CallMethodObjArgs( pString, pUpperAsPyString, NULL );
    Py_DECREF( pUpperAsPyString );
    if( NULL == pResult )
         throw runtime_error( "Nieudane wywolanie funkcji" );
   
    string result = ReturnValueAs < string >( pResult );
    Py_DECREF( pResult );
    return result;
}

Listowanie plików

Łatwo można również wyświetlić wszystkie pliki i foldery z aktualnej ścieżki:
C/C++
vector < string > ListFilesAndFoldersInCurrentDirectory()
{
    /* import glob
       glob.glob('*') */
    vector < PyObject *> filesAsPyObjects = CallFunctionFromModule < vector < PyObject *>>( "glob", "glob", { string( "*" ) } );
    vector < string > result;
    transform( filesAsPyObjects.begin(), filesAsPyObjects.end(), back_inserter( result ), ReturnValueAs < string > );
    return result;
}

Używanie skryptu bezpośrednio z pliku

Mamy również możliwość odpalenia skryptu bezpośrednio z pliku, służy do tego funkcja PyRun_SimpleFile(). Jako ciekawy przykład pobrałem bibliotekę:
https://hg.python.org/cpython​/file/2.7/Lib/poplib.py do pobierania wiadomości e-mail przy użyciu protokołu pop3.
Ze względu na niechęć do wyświetlania pełnych treści wiadomości w konsoli zmieniłem w popranym pliku funkcję main żeby wyświetlało tylko temat:

        for line in msg:
    if('Subject' in line ): # ta linijke dodalem
        print '   ' + line
uruchomienie z pliku na Windowsie jest troszeczkę zbugowane, dlatego trzeba użyć pewnego obejścia:
C/C++
int main( int argc, char * argv[] )
{
    Py_Initialize();
   
    char * myArgs[] = {
        argv[ 0 ],
        "pop3.wp.pl",
        "mail@wp.pl",
        "hasloDoPocztyWp",
    };
    unsigned numberOfArgs = sizeof( myArgs ) / sizeof( myArgs[ 0 ] );
    PySys_SetArgv( numberOfArgs, myArgs );
    string libName = "poplib.py"; // ten plik musi byc w odpowiednim miejscu aby zostal znaleziony
   
    PyObject * PyFileObject = PyFile_FromString( const_cast < char *>( libName.c_str() ), "r" );
    PyRun_SimpleFile( PyFile_AsFile( PyFileObject ), libName.c_str() );
    Py_Finalize();
}
Jako wydruk zostało mi listować tematy wiadomości, niektóre tematy są zakodowane, dlatego pojawiają się ciekawe znaczki zamiast ogonków.

Dodatkowe przydatne rzeczy

Jeśli chcemy szybko utworzyć listę, tuplę z wieloma argumentami możemy wykorzystać funkcje:
C/C++
PyObject * tuple = Py_BuildValue( "(iis)", 1, 2, "three" );
PyObject * list = Py_BuildValue( "[iis]", 1, 2, "three" );
Sprawdzenie czy wystąpił błąd można wykonać ręcznie:
C/C++
PyErr_Occurred()

Na zakończenie ważny przykład -importowanie własnych modułów

Jest to trudne zagadnienie o tyle, że nie wystarczy wrzucić pliku z modułem obok pliku binarnego, trzeba zmodyfikować ścieżkę wyszukiwania bibliotek.
Można to zrobić najprościej wywołując prostą komendę pythona:
C/C++
void AppendTheDirectoryToPythonSysPatch( const char * curentPath )
{
    string commandToCall( "from sys import path\n" );
    commandToCall = commandToCall + "if not '" + curentPath + "' in path:";
    commandToCall = commandToCall + "\tpath.append('" + curentPath + "')";
    PyRun_SimpleString( commandToCall.c_str() );
}
W poniższym przykładzie użyłem jednak innego sposobu na dokonanie powyższej czynności. Wybrałem bibliotekę do statycznej analizy kodu: https://pypi.python.org/pypi​/lizard, przy pomocy której pobieram złożoność cyklomatyczną aktualnego pliku. Oto kompletny przykład ze wszystkimi koniecznymi funkcjami.
C/C++
#include <Python.h>
#include <iostream>
#include <string>
#include <initializer_list>
#include <vector>
#include <map>
#include <stdexcept>

using namespace std;

template < typename ReturnType >
ReturnType ReturnValueAs( PyObject * pValue );

template <>
double ReturnValueAs < double >( PyObject * pValue )
{
    if( 0 == PyFloat_Check( pValue ) )
         throw invalid_argument( "Invalid type of argument" );
   
    return PyFloat_AsDouble( pValue );
}

template <>
string ReturnValueAs < string >( PyObject * pValue )
{
    if( 0 == PyString_Check( pValue ) )
         throw invalid_argument( "Invalid type of argument" );
   
    char * result = PyString_AsString( pValue );
    if( NULL == result )
         throw invalid_argument( "Invalid type of argument" );
   
    return result;
}

template <>
long ReturnValueAs < long >( PyObject * pValue )
{
    if( 0 == PyLong_Check( pValue ) )
         throw invalid_argument( "Invalid type of argument" );
   
    return PyInt_AsLong( pValue );
}

template <>
int ReturnValueAs < int >( PyObject * pValue )
{
    if( 0 == PyInt_Check( pValue ) )
         throw invalid_argument( "Invalid type of argument" );
   
    return PyInt_AsLong( pValue );
}

template <>
vector < PyObject *> ReturnValueAs < vector < PyObject *>>( PyObject * pValue )
{
    if( 0 == PyList_Check( pValue ) )
         throw invalid_argument( "Invalid type of argument" );
   
    const unsigned pythonListSize = PyList_Size( pValue );
    vector < PyObject *> returnVector;
    returnVector.reserve( pythonListSize );
   
    for( unsigned i = 0; i < pythonListSize; ++i )
    {
        PyObject * pCurrentValue = PyList_GetItem( pValue, i );
        if( NULL == pCurrentValue )
             throw invalid_argument( "Invalid type of argument" );
       
        returnVector.push_back( pCurrentValue );
    }
    return returnVector;
}

template <>
map < int, PyObject *> ReturnValueAs < map < int, PyObject *>>( PyObject * pValue )
{
    if( 0 == PyDict_Check( pValue ) )
         throw invalid_argument( "Invalid type of argument" );
   
    PyObject * dictionaryKeys = PyDict_Keys( pValue );
    vector < PyObject *> keysAsVector = ReturnValueAs < vector < PyObject *>>( dictionaryKeys );
   
    map < int, PyObject *> returnDictionary;
    for( unsigned i = 0; i < keysAsVector.size(); ++i )
    {
        PyObject * valueForTheKey = PyDict_GetItem( pValue, keysAsVector[ i ] );
        int keyAsInt = ReturnValueAs < int >( keysAsVector[ i ] );
       
        if( valueForTheKey )
             returnDictionary.insert( make_pair( keyAsInt, valueForTheKey ) );
       
    }
    return returnDictionary;
}

template < typename T >
PyObject * ValueAsPyObject( const T & value );

template <>
PyObject * ValueAsPyObject < double >( const double & value )
{
    return PyFloat_FromDouble( value );
}

template <>
PyObject * ValueAsPyObject < long >( const long & value )
{
    return PyLong_FromLong( value );
}

template <>
PyObject * ValueAsPyObject < int >( const int & value )
{
    return ValueAsPyObject < long >( value );
}

template <>
PyObject * ValueAsPyObject < string >( const string & value )
{
    return PyString_FromString( value.c_str() );
}

template <>
PyObject * ValueAsPyObject < char >( const char & value )
{
    char arr[] = { value, '\0' };
    return PyString_FromString( arr );
}

PyObject * ImportPythonModule( const char * pyModuleName ) // throw(invalid_argument, runtime_error)
{
    if( NULL == pyModuleName )
         throw invalid_argument( "Podano wartosc NULL, to niedopuszczalne!" );
   
    PyObject * pString = PyString_FromString( pyModuleName );
    if( NULL == pString )
         throw runtime_error( "Nie mozna przerobic podanego tekstu na pyString" );
   
    PyObject * pModule = PyImport_Import( pString );
    Py_DECREF( pString );
    if( NULL == pModule )
         throw runtime_error( "Nie mozna zaimportowac modulu" );
   
    return pModule;
}

PyObject * GetFunctionFromPythonModule( PyObject * pModule, const char * pyFunctionName )
{
    PyObject * pFunctionObject = PyObject_GetAttrString( pModule, pyFunctionName );
    if( NULL == pFunctionObject )
         throw runtime_error( "Nie mozna pobrac obiektu funkcji" );
   
    if( !PyCallable_Check( pFunctionObject ) )
         throw runtime_error( "Pobrany obiekt nie jest funkcja" );
   
    return pFunctionObject;
}

template < typename Arg >
PyObject * PrepareArgumentsToFunction( const initializer_list < Arg >& args )
{
    PyObject * pTupleWithArguments = PyTuple_New( args.size() );
   
    unsigned counter = 0;
    for( auto i: args )
    {
        PyObject * pArgument = ValueAsPyObject( i );
        PyTuple_SetItem( pTupleWithArguments, counter, pArgument );
        ++counter;
    }
    return pTupleWithArguments;
}

void HandlePythonException( string errorMessagePrefix ) // throw(runtime_error)
{
    PyObject * ptype, * pvalue, * ptraceback;
    PyErr_Fetch( & ptype, & pvalue, & ptraceback );
   
    if( ptype )
         Py_DECREF( ptype );
   
    if( pvalue )
    {
        errorMessagePrefix = errorMessagePrefix + ", poniewaz: " + PyString_AsString( pvalue );
        Py_DECREF( pvalue );
    }
    if( ptraceback )
         Py_DECREF( ptraceback );
   
    throw runtime_error( errorMessagePrefix );
}

string GetAbsolutePathOfFile( const string & fileName )
{
    /* import os
       os.path.abspath(fileName) */
   
    try
    {
        PyObject * pModule = ImportPythonModule( "os" );
        PyObject * pPath = PyObject_GetAttrString( pModule, "path" );
        Py_DECREF( pModule );
        if( NULL == pPath )
             throw runtime_error( "Nie mozna odpolac sie do atrybutu path" );
       
        PyObject * pFunctionObject = GetFunctionFromPythonModule( pPath, "abspath" );
        Py_DECREF( pPath );
       
        PyObject * pArgs = PrepareArgumentsToFunction( { fileName } );
       
        PyObject * pFunctionResult = PyObject_Call( pFunctionObject, pArgs, NULL );
        Py_XDECREF( pFunctionObject );
        Py_DECREF( pArgs );
        if( NULL == pFunctionResult )
             throw runtime_error( "Nie mozna wywolac funkcji" );
       
        string returnValue = ReturnValueAs < string >( pFunctionResult );
       
        Py_DECREF( pFunctionResult );
       
        return returnValue;
    }
    catch( const runtime_error & e )
    {
        HandlePythonException( e.what() );
    }
}

void AddDirectoryToModulePath( const char * directoryPath )
{
    PyObject * pCurrentPath = PySys_GetObject( "path" );
    if( NULL == pCurrentPath )
         throw runtime_error( "Nieudane odwolanie do zmiennej path" );
   
    string currentPythonSyspathValue;
   
    #ifdef _WIN32
    char DELIMITER = ';';
    #elif defined(__linux__)
    char DELIMITER = ':';
    #else
    #error "Nieznany delimiter fla obecnego systemu!"
    #endif
   
    bool directoryFoundInPath = false;
    for( int i = 0; i < PyList_Size( pCurrentPath ); ++i )
    {
        PyObject * pItem = PyList_GetItem( pCurrentPath, i );
        if( pItem )
        {
            const string & foundPath = ReturnValueAs < string >( pItem );
            Py_DECREF( pItem );
            if( foundPath == directoryPath )
                 directoryFoundInPath = true;
           
            currentPythonSyspathValue += foundPath + DELIMITER;
        }
    }
   
    if( !directoryFoundInPath )
    {
        PyList_Append( pCurrentPath, ValueAsPyObject < string >( directoryPath ) );
        currentPythonSyspathValue += directoryPath;
        PySys_SetPath( const_cast < char *>( currentPythonSyspathValue.c_str() ) ); // 1
    }
}

int main( int argc, char * argv[] )
{
    Py_Initialize();
    try
    {
        string lizardPath = GetAbsolutePathOfFile( "." ) + "\\";
        AddDirectoryToModulePath( lizardPath.c_str() );
       
        /** in the C++ lines below I execute python code:
        import lizard
        return lizard.analyze_file(argv[0]).function_list[0].__dict__['cyclomatic_complexity']
        **/
        PyObject * pLizardModule = PyImport_ImportModule( "lizard" );
        if( NULL == pLizardModule )
             cerr << "Nieudany import: " << endl;
       
        PyObject * pAnaliseFileFunction = PyObject_GetAttrString( pLizardModule, "analyze_file" );
        Py_DECREF( pLizardModule );
        if( NULL == pAnaliseFileFunction )
             throw runtime_error( "Nie mozna odwolac sie do atrybutu path" );
       
        PyObject * pArgs = Py_BuildValue( "(s)", __FILE__ ); // 2
       
        PyObject * pFunctionResult = PyObject_Call( pAnaliseFileFunction, pArgs, NULL );
        Py_DECREF( pArgs );
        Py_XDECREF( pAnaliseFileFunction );
        if( NULL == pFunctionResult )
             throw runtime_error( "Nie udalo sie poprawnie wywolac funkcji" );
       
        PyObject * pFunctionList = PyObject_GetAttrString( pFunctionResult, "function_list" );
        Py_DECREF( pFunctionResult );
        if( NULL == pFunctionList )
             throw runtime_error( "Nie udalo sie pobrac podelementu function_list" );
       
        PyObject * pFirstElementOfList = PyList_GetItem( pFunctionList, 0 );
        Py_DECREF( pFunctionList );
        if( NULL == pFirstElementOfList )
             throw runtime_error( "Nie udalo sie pobrac pierwszego elementu z function_list" );
       
        PyObject * pInnerFunctionDictionary = PyObject_GetAttrString( pFirstElementOfList, "__dict__" );
        Py_DECREF( pFirstElementOfList );
       
        PyObject * pCyclomaticComplexityValue = PyDict_GetItemString( pInnerFunctionDictionary, "cyclomatic_complexity" );
        Py_DECREF( pInnerFunctionDictionary );
       
        cout << "Zlozonosc cyklomatyczna tego pliku wynosi = " << ReturnValueAs < int >( pCyclomaticComplexityValue ) << endl;
        Py_DECREF( pCyclomaticComplexityValue );
    }
    catch( const exception & e )
    {
        cerr << "Blad: " << e.what() << endl;
    }
   
    Py_Finalize();
}

Opis ciekawszych miejsc programu

1. Funkcja PySys_SetPath() przyjmuje nową ścieżkę z elementami oddzielonymi odpowiednim dla systemu delimiterem. Funkcja ta przyjmuje char*, dlatego muszę dokonać brzydkiego rzutowania.
2. Tutaj jest prostrzy sposób tworzenie tupli argumentów do funkcji.

Dla poszukujących jeszcze więcej

Powyżej opisałem zagnieżdżanie wywołań języka python w języku C/C++ przy użyciu Python C Api. Jest możliwe jeszcze pisanie rozszerzeń do języka python.
Dla osób chcących tworzyć zaawansowane hybrydy tego typu polecam bardziej:
http://www.boost.org/doc/libs​/1_59_0/libs/python/doc​/tutorial/doc/html/index.html

Bibliografia:

https://docs.python.org/2​/c-api/intro.html
https://docs.python.org/2​/extending/embedding.html
https://docs.python.org/2​/genindex-all.html
http://stackoverflow.com/
https://duckduckgo.com/
https://www.diki.pl/