Две думи за CPython
Защо по * трябва да ползвам C?
Две е повече от едно
Как става това?
Ctypes позволява да викам C функции директно от Python без да ни се налага да пишем и капка C код.
Понеже не ползваме 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>
>>>
>>> libc.time(None)
1243423125
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
Имат поленце 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! затова когато използвата функции които променят аргумента си трябва да използвате 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
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...
Ако искаме да използваме наши типове в .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))
Две думи -- подразбира 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_* обекти, докато 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
struct cell {
char *name;
struct cell *next;
};
class CELL(Structure):
pass
CELL._fields_ = [("name", c_char_p),
("next", POINTER(CELL))]
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) може да събере вашите обекти ако нямате референция към тях, дори и да има такива в C кода. Това важи особено за callbacks.
вложени структури
callbacks
byte ordering
аргументи по референция
Задължително погледнете за изненадите. (в документацията)
Още интересни неща на
Истинският програмист: http://www.pbm.com/~lindahl/mel.html