12. Python + C

12. Python + C

12. Python + C

12 май 2014

Въпрос за мъфин 1

Въпрос за мъфин 2

Въпрос за мъфин 3

Въпрос за мъфин 4

Пайтън и C

Две думи за CPython

Защо?

Защо по * трябва да ползвам C?

Python + C > Python

Две е повече от едно

Възможностите

Как става това?

Ctypes

Ctypes позволява да викам C функции директно от Python без да ни се налага да пишем и капка C код.

Ctypes основни моменти

Забележка: Unix vs. Windows

Понеже не ползваме Windows ще дадем примери за Unix

Зареждане на библиотеки

>>> from ctypes import *
>>>
>>> libc = cdll.LoadLibrary('libc.so.6')
>>> libc
<CDLL 'libc.so.6', handle b7fbdd18 at b7a3c74c>
>>> libm = cdll.LoadLibrary('libm.so.6')
>>> libm
<CDLL 'libm.so.6', handle b7fbda88 at b7a5718c>
>>>

Calling conventions

Извикване на функции

>>> libc.time(None)
1243423125

Good morning world

libc.printf(b'good morning world'
            b', the time is %d\n', libc.time(None))
good morning world, the time is 1243423807

Натурални типове

Типове

Вместо пайтънски стойности трябва да използвате обекти от тези типове

ctypes type 	C type 	Python type
c_char 	char 	1-character string
c_wchar 	wchar_t 	1-character unicode string
c_byte 	char 	int
c_ubyte 	unsigned char 	int
c_short 	short 	int
c_ushort 	unsigned short 	int
c_int 	int 	int
c_uint 	unsigned int 	int
c_long 	long 	int
c_ulong 	unsigned long 	int
c_longlong 	__int64 or long long 	int
c_ulonglong 	unsigned __int64 or unsigned long long 	int
c_float 	float 	float
c_double 	double 	float
c_longdouble 	long double 	float
c_char_p 	char * (NUL terminated) 	string or None
c_wchar_p 	wchar_t * (NUL terminated) 	unicode or None
c_void_p 	void * 	int or None

c_* обектите са mutable

Имат поленце value което може да променяте.

>>> i = c_int(42)
>>> print(i, i.value)
c_long(42) 42
>>> i.value = -99
>>> print(i, i.value)
c_long(-99) -99

Пайтън низовете са immutable!

Низовете са immutable! затова когато използвата функции които променят аргумента си трябва да използвате create_string_buffer

>>> buf = create_string_buffer(b'hello', 15)
>>> print(sizeof(buf), repr(buf.raw))
15 b'hello\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> libc.strcat(buf, b', world')
-1214158944
>>> print(sizeof(buf), repr(buf.raw))
15 b'hello, world\x00\x00\x00'

Отново извикване на функции

libc.printf(b'%d bottles of beer\n', 42)
libc.printf(b'%f bottles of beer\n', 42.5)
42 bottles of beer
Traceback (most recent call last):
  File "ctypes-basic.py", line 24, in <module>
    libc.printf(b'%f bottles of beer\n', 42.5)
ctypes.ArgumentError: argument 2: <class 'TypeError'>:
    Don't know how to convert parameter 2

Експлицитно преобразуване

libc.printf(b'%f bottles of beer\n', c_double(42.5))
42.500000 bottles of beer

Типове на резулата

Колко е синус от 1?

>>> libm.sin(c_double(1))
-1082050016

По подразбиране връщаната стойност се интерпретира като int.

>>> libm.sin.restype = c_double
>>> libm.sin(c_double(1))
0.8414709848078965

Сигнатури

Защото нямаме .h фалове.

>>> libm.sin.argtypes = [c_double]
>>> libm.sin(1)
0.8414709848078965

Наши-TE типо-ВЕ

class PentiumBottles:

    def __init__(self, count):
        self.count = count

    @property
    def _as_parameter_(self):
        return c_double(self.count)

libc.printf(b'there are %f bottles on the wall...\n',
        PentiumBottles(3.1415926))
there are 3.141593 bottles on the wall...

Сигнатури и наши-TE типо-ВЕ

Ако искаме да използваме наши типове в .argtypes трябва да имплементираме клас-метода за проверка

OurClass.from_param(object_passed_as_argument)

Kойто трябва да връща ctypes натурален тип, c_* или обект с _as_parameter_ метод.

Лесни аргументи по референция

>>> i = c_int()
>>> f = c_float()
>>> s = create_string_buffer('\000' * 32)
>>> print(i.value, f.value, repr(s.value))
0 0.0 b''
>>> libc.sscanf(b'1 3.14 Hello',
...         b'%d %f %s', byref(i), byref(f), s)
3
>>> print(i.value, f.value, repr(s.value))
1 3.1400001049 b'Hello'

Структури

class POINT(Structure):
    _fields_ = [('x', c_double), ('y', c_double)]
>>> point = POINT(10, 20)
>>> print(point.x, point.y)
10.0 20.0
>>> point = POINT(y=5)
>>> print(point.x, point.y)
0.0 5.0
>>> POINT(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: too many initializers

Структури

Структурите имат метаклас различен от стандартния type:

>>> type(POINT) == type
False
>>> type(POINT)
<class '_ctypes.StructType'>

Влагане на структури

class RECT(Structure):
    _fields_ = [('upperleft', POINT), ('lowerright', POINT)]
>>> rc = RECT(point)
>>> print(rc.upperleft.x, rc.upperleft.y)
0.0 5.0
>>> print(rc.lowerright.x, rc.lowerright.y)
0.0 0.0
>>> rc = RECT((1, 2), (3, 4))
>>> rc = RECT(POINT(1, 2), POINT(3, 4))

Подравняване и byte-order

Две думи -- подразбира native

Масиви

Масивите си имат собствен тип който включва броя елементи. Типа се указва като умножим типа на елементите по техния брой -- (ElementType * element_count).

Получаваме нов тип:

POINT_ARRAY_10 = POINT * 10
for i in POINT_ARRAY_10():
    print(i.x, i. y)
>>> (c_int * 3)(1,2,3)[0]
1

Указатели

Указатели към c_* обекти (срещу указатели към натурални обекти)

>>> i = c_int(10)
>>> p = pointer(i)
>>> p.contents
c_long(10)
>>> j = c_int(10)
>>> p.contents = j
>>> p.contents.value
10
>>> p.contents.value = 11
>>> j.value
11
>>> p[0]
11

Типове на указатели

Указателите към c_* обекти си имат собствен тип:

>>> point_p = pointer(point)
>>> type(point_p)
<class '__main__.LP_POINT'>
>>> type(point_p) == POINTER(POINT)
True
>>> POINTER(POINT)
<class '__main__.LP_POINT'>

POINTER(ctype_type) е типа на указатели към c_* обект или Structure

POINTER(c_char) vs c_char_p

POINTER се използва за указатели към c_* обекти, докато c_{char,wchar,void}_p са указатели към C обекти. Когато функция връща (char *) съответния .restype атрибут трябва да бъде c_char_p:

>>> libc.strstr.restype = POINTER(c_char)
>>> found_p = libc.strstr(b'abc def ghi', b'def')
>>> found_p[:5]
b'\x00\x00\x00\x00\x00' # нищо общо
>>> libc.strstr.restype = c_char_p # strstr връща указател към C памет,
...                                # а не към питонски c_char
>>> found_p = libc.strstr(b'abc def ghi', b'def')
>>> found_p
'def ghi'

Забележете че ctypes прави автоматично преобразуване създавайки нов str обект:

>>> type(found_p)
<class 'str'>

Преобразуване на масиви към указатели

Ctypes е стриктен и рядко прави преобразувания. Затова ни се налага ние да ги правим използвайки функцията cast:

>>> libc.strstr.argtypes = [c_char_p, c_char_p]
>>> byte_array = (c_byte * 12)(*b'abc def ghi')
>>> byte_array[:]
[97, 98, 99, 32, 100, 101, 102, 32, 103, 104, 105, 0]
>>> libc.strstr(byte_array, b'def')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type
>>> libc.strstr(cast(byte_array, c_char_p), b'def')
'def ghi'

Указатели

>>> p[10]
159787148
>>> p[10] = 20
>>> p[10]
20

Incomplete types

struct cell {
    char *name;
    struct cell *next;
};
class CELL(Structure):
    pass
CELL._fields_ = [("name", c_char_p),
                 ("next", POINTER(CELL))]

Callbacks

IntArray5 = c_int * 5
int_array = IntArray5(5, 1, 7, 33, 99)
libc.qsort.restype = None

INT_CMP_FUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))

def py_cmp_func(a, b):
    return a[0] - b[0] # a и b са указатели към c_int

libc.qsort(int_array, len(int_array), sizeof(c_int), INT_CMP_FUNC(py_cmp_func))

The Grim Reaper

Събирачът на боклук (The Grim Reaper) може да събере вашите обекти ако нямате референция към тях, дори и да има такива в C кода. Това важи особено за callbacks.

За и против

ctypes изненади

вложени структури

callbacks

byte ordering

аргументи по референция

Задължително погледнете за изненадите. (в документацията)

Python/C API

В началото бе Python.h …

Префикси

Примери за функции, тип

Cython = Python + (C API) - (C Syntax)

SWIG & Boost.Python

Още въпроси?

Още интересни неща на

Real programmers write in FORTRAN

The Story of Mel

Истинският програмист: http://www.pbm.com/~lindahl/mel.html

Въпроси?