воскресенье, 30 июня 2013 г.

Python: примеры и тесты, часть 3 - SWIG

Показанная раньше техника создания модулей Python на C/C++ понятна и достаточно хорошо описана в документации в документации: передача объектов от исполняющей системы Python (параметры), преобразование значений переменных C/C++ в объекты Python (возврат значений)... Недостающие детали можно подсмотреть в заголовочном файле <python*/modsupport.h>. Но всё-таки такая ручная техника создания модулей на C/C++ достаточно громоздкая. Для её упрощения создано несколько инструментальных пакетов, автоматизирующих и упрощающих эту деятельность (Cython, SWIG и другие). Мной опробовано (пока?) только:

Использование SWIG.

В качестве упражнения я выбрал для разбирательства весьма простую задачу: подсчитать частоту вхождения в произвольный текст символов (256 символов из основной таблицы ASCII, только 1-байтовых, для упрощения). Реализующий C-код имеет вид (файл freq.c):

#include <stdlib.h>
 
int* frequency( char s[] ) {
    int *freq;
    char *ptr;
    freq = (int*)( calloc( 256, sizeof( int ) ) );
    if( freq != NULL )
        for( ptr = s; *ptr; ptr++ )
            freq[ *ptr ] += 1;
    return freq;
}


Теперь мы не пишем в коде программную реализацию интерфейса к этой  функции C, как в предыдущем рассмотрении, а составляем интерфейсный файл SWIG  (freq.i, расширение i):

%module freq
%typemap(out) int* {
    int i;
    $result = PyTuple_New( 256 );
    for( i = 0; i < 256; i++ )
        PyTuple_SetItem( $result, i, PyLong_FromLong( $1[ i ] ) );
    free( $1 );
}
extern int* frequency( char s[] );


Даже вот такая, несколько усложнённая форма описания, связана только с тем фактом, что С-функция frequency() динамически приобретает память, и после использования её нужно вернуть во избежание утечек памяти. Во многих же практических случаях (см. далее) интерфейсные описания и того проще. После создания мы обрабатываем файл описания вот таким образом:

$ swig -python freq.i

В результате чего генерируются файла исходного кода интерфейса модуля (freq_wrap.c). Из имеющихся файлов уже можно непосредственно собирать динамическую библиотеку, реализующую модуль Python:

$ gcc -c -fpic freq_wrap.c freq.c -DHAVE_CONFIG_H -I/usr/include/python2.7 -I/usr/lib/python2.7/co
$ ld -shared freq_wrap.o freq.o -o _
freq.so

Всё! Нам остаётся только написать тестирующее приложение Python (файл frtest.py) для непосредственного импорта созданного модуля:

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
from sys import argv
import freq
 
sarg = lambda : ( len( argv ) > 1 and argv[ 1 ] ) or str( input( "string?: " ) )
 
F = freq.frequency( sarg() )
s = ""
for i in range( 256 ):
    if F[ i ] != 0: s = s + ( "x%x:%d " % ( i, F[ i ] ) )
print s


В итоге мы получили работоспособный модуль Python (с именем freq), пусть не очень умный, но в достаточной мере иллюстрирующий технику использования SWIG:

$ python frtest.py "1234123121 abc ab a"
x20:3 x31:4 x32:3 x33:2 x34:1 x61:3 x62:2 x63:1


Прямой доступ к библиотекам C/C++.

В этой технике интересно создание модулей прямых интерфейсов к уже  существующим ранее целевым библиотекам (.so) C/C++ (при отсутствии их  аналогов в Python), это вообще практически без написания кода. В порядке иллюстрации сказанного я сделаю интерфейсный модуль к файловым операциям C. Файл интерфейсного описания SWIG (fileio.i) имеет вид:

%module fileio
extern FILE *fopen(char *, char *);
extern int fclose(FILE *);
extern unsigned fread(void *ptr, unsigned size, unsigned nobj, FILE *);
extern unsigned fwrite(void *ptr, unsigned size, unsigned nobj, FILE *);
extern void *malloc(int nbytes);
extern void free(void *);


Больше ничего нам, собственно, и не придётся делать:


$ swig -python fileio.i
$ gcc -c -fpic fileio_wrap.c -DHAVE_CONFIG_H -I/usr/include/python2.7 -I/usr/lib/python2.7/config
$ ld -shared fileio_wrap.o -lc -o _fileio.so


Тестовая задача (fiotest.py) на Python:

#!/usr/bin/python
# -*- coding: utf-8 -*-
from fileio import *
import fileio
from sys import argv
 
def filecopy( source, target ):  # Copy a file
    sum = long( 0 )
    f1 = fopen( source, "r" )
    if f1 == None: return -1
    f2 = fopen( target, "w" )
    buffer = malloc( 8192 )
    nbytes = fread( buffer, 1, 8192, f1 )
    while( nbytes > 0 ):
        fwrite( buffer, 1, nbytes, f2 )
        sum = sum + nbytes
        nbytes = fread( buffer, 1, 8192, f1 )
    free( buffer )
    fclose( f1 )
    fclose( f2 )
    return sum
 
n = filecopy( ( len( argv ) > 1 and argv[ 1 ] ) or "in.txt", \
              ( len( argv ) > 2 and argv[ 2 ] ) or "out.txt" )
print "скопировано %d байт" % n


И вот её функционирование:

$ python fiotest.py in.txt out.txt
скопировано 178 байт
$ ls -l *.txt
-rw-rw-r-- 1 olej olej 178 июня 21 23:48 in.txt
-rw-rw-r-- 1 olej olej 178 июня 22 00:14 out.txt




Комментариев нет: