Python vs. C
„ Програмиране с Python“, ФМИ
10.05.2011
Пайтън и C
Две думи за CPython
- стандартната реализация на Пайтън (CPython) е писана на C
- съответно можем да разширяваме интерпретатора със C
- както и да комуникираме със C код
- или пък да вградим Пайтън в наше приложение, за което няма да говорим днес
Защо?
Защо по * трябва да ползвам C?
- искам си указателите!
- Пайтън е бавеееен! (Numpy, Mercurial)
- Три пъти мери, един път режи! (profile, cProfile)
- имаме готов C код който искаме да преизползваме (Numpy, PyQt, PySide, PyGTK+, PyObjC и Cocoa)
- Пайтън е интересен език но искаме да е още по-интересен (Stackless)
Python + C > Python
Две е повече от едно
- Пайтън не е подходящ за всичко
- просто понякога е по-доборе да напишем част от програмата си на C
Възможностите
Как става това?
- ctypes (стандартен модул)
- SWIG
- Boost.Python
- Пайтън C API (утвърдено)
- Cython
Ctypes
Ctypes позволява да викам C функции директно от Python без да ни се налага да
пишем и капка C код.
- Това е може би най-лесният начин
Ctypes основни моменти
- предназначено за комуникация с чист C код — lean&mean
- стреми се да бъде минимален и да не „пречи“, съответно е от сравнително ниско ниво
- зареждане на динамични библиотеки (shared objects, dynamic link libraries a.k.a. DLL)
- викане на функции от тези библиотеки и даже достъпване на данни
- позволява да описваме интерфейса на функциите който ще ползваме
- дефиниране на потребителски структури и обединения
Зареждане на библиотеки
>>> from ctypes import *
>>>
>>> libc = cdll.LoadLibrary('libc.so.6')
>>> libc
>>> libm = cdll.LoadLibrary('libm.so.6')
>>> libm
>>>
Calling conventions
- cdll - cdecl
- windll - stdcall, oledll — само за Windows
Извикване на функции
>>> libc.time(None)
1243423125
Marshalling
- Различните езици имат различен формат за метаданни
- Някои дори си нямат
- Marshalling е процесът на трансформиране от един формат към друг
Marshalling (примери)
- XPCOM(Mozilla)
- Microsoft COM
- Java Native Interface
- .NET P/Invoke
Good morning world
libc.printf(b'good morning world, my name is %s\n', \
b'Mityo')
good morning world, the time is 1243423807
Натурални типове
- None → NULL
- int → int
- bytes → const char *
- str → const wchar_t *
- останалите трябва да се конвертират
Отново извикване на функции
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
libc.printf(b'%f bottles of beer\n', 42.5)
ctypes.ArgumentError: argument 2: :
Don't know how to convert parameter 2
Типове
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 * (NULL terminated) string or None
c_wchar_p wchar_t * (NULL terminated) unicode or None
c_void_p void * int or None
Експлицитно преобразуване
libc.printf(b'%f bottles of beer\n', c_double(42.5))
42.500000 bottles of beer
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
Структури
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)
Масиви
Масивите си имат собствен тип който включва броя елементи. Типа се указва
като умножим типа на елементите по техния брой
>>> (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_* обект
- p.contents is p.contents == False
- но реално C обектите които те представят са един и същи
Указатели
>>> p[10]
159787148
>>> p[10] = 20
>>> p[10]
20
The Grim Reaper
Събирачът на боклук (The Grim Reaper) може да събере вашите обекти ако нямате
референция към тях, дори и да има такива в C кода.
За и против
- + преносимо — работи за Linux, Mac OS X и Windows
- + по-лесно и безболезнено отколкото Пайтън C API
- + особено когато комуникираме с чист C код
- - от ниско ниво е
- - трябва да пишем доста Пайтън код
- - трябва да се грижим за marshalling
Ctyeps изненади
- mutability в C
- вложени структури
- callbacks
- byte ordering
- аргументи по референция
- Задължително погледнете за изненадите. (в документацията)
SWIG & Boost.Python
- Метаданни
- Трябва да работите със source файлове
- Автоматичен marshalling, който всъщност…работи
- Човечна C++ поддръжка
Python/C API
- Хедърите му, в общия случай, се намират в
/usr/include/python{version}/
- Можем да го раздаваме и на C++ през това API
В началото бе Python.h …
stdio.h
string.h
errno.h
limits.h
assert.h
stdlib.h
(евентуално)
- Разни пре-процесори, които трябват на Python
Префикси
- Повечето имена, които ни трябват, използват
Py
- Тези, за „вътрешно“ ползване -
_Py
- Structure member names си нямат префикс(колко тъжно…)
Примери за функции, тип
- PyDict_New, PyDict_Contains, PyDict_SetItem
- PyDict_SetItemString, PyDict_GetItem
- PyDict_GetItemString, PyDict_Size, PyDict_Merge, PyDict_Update
- PyList_ New, PyList_Size, PyList_GetItem
- PyList_SetItem, PyList_Insert, PyList_Append
- PyList_GetSlice, PyList_Sort, PyList_Reverse
- и т.н.
Примери за duck-typing
- PyMapping_Length, PyMapping_SetItemString, PyMapping_GetItemString, PyMapping_Keys
- PySequence_ Index, PySequence_Contains, PySequence_Count
Marshalling към Python
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
Marshalling към C
const char* string;
int number;
PyArg_ParseTuple(args, "si:string_peek", &string, \
&number)
Функции на C
>>> print
<built-in function print>
>>>
Функции, връщащи PyObject*
- NULL: възникнало е изключение
- всичко останало -> питонски обкекти (вкл. None)
Функции, връщащи int
- 0: ОК
- -1: not OK
- unless it's true and false
Cython = Python + (C API) - (C Syntax)
- fork на Pyrex
- може да се нарече език за програмиране
- „Пайтън със C типове“
- не е самостоятелен, трябва му Пайтън и C компилатор
- пишете по-малко код и най-вече не се грижите за особеностите на Пайтън C API (marshalling, GC ref count)
Още въпроси?
Още интересни неща на
Real programmers write in FORTRAN
- Real Programmers do List Processing in Fortran.
- Real Programmers do String Manipulation in Fortran.
- Real Programmers do Accounting (if they do it at all) in Fortran.
- Real Programmers do Artificial Intelligence programs in Fortran.
- Real Programmers aren't afraid to use GOTOs.
- Real Programmers can write five page long DO loops without getting confused.
- Real Programmers like Arithmetic IF statements— they make the code more interesting.
- Real Programmers write self-modifying code, especially if they can save 20 nanoseconds in the middle of a tight loop.
- Real Programmers don't need comments— the code is obvious.
- Since Fortran doesn't have a structured IF, REPEAT … UNTIL, or CASE statement, Real Programmers don't have to worry about not using them. Besides, they can be simulated when necessary using assigned GOTOs.