В первой части нашего небольшого цикла статей мы исследовали возможность использования структур данных в Python, которые были созданы с помощью языков программирования C/C++. Для этого мы рассмотрели библиотеку Ctypes.
В этой статье попробуем разобраться, какие еще есть подходы для работы с языками С/С++ и их аналогами, и также рассмотрим возможность анализа С/С++ исходников языков программирования и их компиляции налету. К тому же ответим на вопрос, а можно ли сделать интеграцию C/C++ в языке программирования Python прямо в скрипте. Подобный подход очень размоет границы между языками программирования, но это очень интересный функционал, который возможно может быть полезным.
Библиотека CFFI
Библиотека для работы с С из языка программирования Python. Написана с использованием pycparser, то есть это полноценный парсер языка программирования C и еще несколько дополнительных функций. Как это работает? Библиотека позволяет определять прототипы функций и производить их компиляцию. После проведения этих операций из Python можно вызывать функцию C, как будто она была определена изначально на Python.
Чтобы можно было использовать библиотеку, достаточно ввести вот такую команду:
pip install cffi
Для примера того, как можно работать с этой библиотекой, проведем уже известный из предыдущей статьи эксперимент. Попробуем вызвать MessageBoxA из системной библиотеки. Вот так будет выглядеть код:
from cffi import FFI
def main():
ffi=FFI()
ffi.cdef("""
int MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCation, UINT uType);
""")
_user32 = ffi.dlopen("USER32.DLL")
lpText = bytes("Hello from cffi", "utf-8")
lpCaption = bytes("Test cffi", "utf-8")
MB_OK = 1
if _user32.MessageBoxA(ffi.NULL, lpText, lpCation, MB_OK):
print("MessageBox showed!")
if __name__ == "__main__":
main()
Результат выполнения скрипта будет таким:
Код стал проще и лаконичнее, теперь можно просто определять функции, копируя их из документации. Все типы, перечисления больше не нужно определять самостоятельно: всё это добавлено уже самой cffi. Но если очень хочется, то можно сделать это вручную. В коде ниже представлен пример того, как можно с помощью библиотеки cffi сделать структуру и присвоить значения её полям:
from cffi import FFI
def main():
ffi = FFI()
ffi.cdef("""
typedef struct {
unsigned char one, two;
} test;
""")
testStruct = ffi.new("test[]", 1)
testStruct[0].one = 255
testStruct[0].two = 255
if __name__ == "__main__":
main()
Все работает так же, как если бы мы просто создавали кусочки быстрого и эффективного кода на С. А что же насчет С++? К сожалению, эта библиотека не умеет работать с С++, но есть достойный аналог - cppyy.
cppyy
Библиотека для автоматической компиляции и работы с С++ из Python и наоборот. Компиляция и работа с кодом осуществляется в рантайме, поэтому конструкции языка С++ можно определять так же, как это было для С в cffi.
Cppyy построена поверх интерпретатора cling. Cling умеет работать с C++ благодаря тому, что использует clang и LLVM. По факту, это является способом быстрой разработки и прототипирования для С++.
Библиотека устанавливается достаточно просто:
pip install cppyy
Так как для работы библиотеке нужен так называемый backend, то при установке будут собираться расширения для её работы. Поэтому стоит установить Build Tools для сборки С++ приложений от MS. После установки всего необходимого нужно разобраться с принципом работы библиотеки.
Разработчики уверяют, что библиотеку можно использовать для всех конструкций языка. Однако стоит иметь в виду некоторые особенности. Библиотека для своей работы определяет объекты, которые в Python будут использоваться для корректной работы языка С++:
cppyy определяет один общий namespace - cppyy.gbl. Именно здесь можно будет найти объекты, которые мы будем создавать для тестирования функций языка С++.
Если требуется создать новый namespace, он будет присоединяться к глобальному - cppyy.gbl.newSpace.
Для адаптации конструкций под синтаксис Python можно описывать переменные из класса с помощью lambda выражений.
Кусочки или целый алгоритм можно определять через функцию cppyy.cppdef.
Рассмотрим простой пример класса с конструктором. Этот класс будет на этапе создания объекта присваивать значения для переменной внутри класса. Для знакомства с библиотекой лучше использовать интерактивный шелл от Python, подойдет и IDLE. Ниже кусочки кода, которые можно вводить и изучать работу cppyy. Такой подход используется не случайно, потому чтобы работать с любым объектом из cppyy, нужно каждый раз запускать процедуру отправки данных в cling, что требует времени. Поэтому мы сначала определяем класс или namespace и потом их нужно импортировать в Python для использования.
import cppyy
# Определим простой класс
cppyy.cppdef("""
class Test {
public:
Test(int i) : m_data(i) {}
int m_data;
};""")
# Чтобы им воспользоваться, нужно его импортировать из глобального namespace
from cppyy.gbl import Test
# Теперь можно использовать класс в коде
test = Test(19)
# test - объект, который можно использовать через Python
# Попробуем использовать параметр из объекта
print(test.m_data)
Можно также работать со стандартными типами и библиотеками, например, создать вектор и инициализировать его уже в Python:
from cppyy.gbl import vector
v = vector[int](range(20))
Все обращения к созданному вектору будут контролироваться через Python, но, если необходимо, также работать с низкоуровневым представлением памяти и объектов, то есть отдельный набор функций cppyy.ll. Ниже пример, как можно выделить кусок сырых данных через malloc.
import cppyy.ll
array = cppyy.ll.malloc[int](10)
array[0] = 1
array[1] = 2
cppyy.free(array)
И крайний пример, как можно определить виртуальную функцию класса на С++:
import cppyy
cppyy.cppdef("""
class MyTest {
public:
MyTest(int i) : m_data(i) {}
virtual ~MyTest() {}
virtual int add_int(int i) { return m_data + i; }
int m_data;
};""")
from cppyy.gbl import MyTest
m = MyTest(10)
cppyy.cppdef("""
void say_hello(MyClass* m) {
std::cout << "Hello, the number is: " << m->m_data << std::endl;
}""")
MyTest.say_hello = cppyy.gbl.say_hello
m.say_hello()
Вот такие интересные проекты существуют для стирания границ между языками программирования. Благодаря чему это возможно? Библиотеки, расмотренные в этой статье, по сути просто обертки вокруг функций LLVM и clang. О чем эти технологии и как ими можно пользоваться из Python, разберемся в следующей статье.
Читать еще:
Также приглашаю всех желающих на бесплатный демоурок в рамках которого обсудим различные виды типизации, заглянем в теорию типов, рассмотрим примеры и best practice по аннотированию в Python, а также поговорим про существующие type checker'ы. Регистрация уже доступна по ссылке.
usbstor
Ну вот наконец-то! Очень мне этого не хватало!