Две думи за CPython
Защо по * трябва да ползвам C?
Две е повече от едно
Как става това?
Ctypes позволява да викаме C функции директно от Python без да ни се налага да пишем и капка C код.
>>> from ctypes import *
>>>
>>> libc = cdll.LoadLibrary('msvcrt.dll')
>>> libc
<CDLL 'msvcrt.dll', handle 7ffc69b80000 at 6ffffe18ed0>
>>>
>>> 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
libc.wprintf('hello world')
libc.printf(b'hello world')
Вместо пайтънски стойности трябва да използвате обекти от тези типове
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.PyCStructType'>
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_int(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
аргументи по референция
Задължително погледнете за изненадите. (в документацията)
Py_BuildValue("s", "spam") -> 'spam'
Py_BuildValue("i", 42) -> 42
Py_BuildValue("(sii)", 42, "hi", 8) -> (42, 'hi', 8)
Py_BuildValue("{is,is}", 1, "one", 2, "two") -> {1: 'one', 2: 'two'}
Py_BuildValue("") -> None
const char* string;
int number;
PyArg_ParseTuple(args, "si:string_peek", &string, &number)
Още интересни неща на
Истинският програмист: http://www.pbm.com/~lindahl/mel.html