Python и D
Доброго времени суток, хабр! Здесь мы не будем рассуждать о плюсах и минусах языков.
Мы будем использовать их вместе!
В этом нам поможет замечательная библиотека pyd. С её помощью можно как вызывать код на python из кода на d, так и наоборот.
Рассмотрим первый вариант. Заполняем файл dub.json
{ «name»: «pydtest», «targetType»: «executable», «dependencies»: { «pyd»:»~>0.9.7» }, «subConfigurations»: { «pyd»: «python34» } } subConfigurations указывает, что мы будем использовать python 3.4Создаём source/main.d
import std.stdio; import pyd.pyd, pyd.embedded;
void main () { py_init ();
auto script = new InterpContext; // следующие 2 строки позволяют искать модули в текущей директории // это нужно чтобы не устанавливать наш пакет myscript.py в систему script.py_stmts («import sys»); script.py_stmts («sys.path.append ('.')»);
script.py_stmts («import myscript»);
writeln (script.py_eval! string («myscript.func ()») ~ » from pyd»); } Создаём myscript.py def func (): return «hello habr!» Запускаем сборку и её результат dub build && ./pydtest И всё! hello habr! from pyd Всё настолько просто! Попробуем немного усложнить. Добавим функцию сложения чисел в myscript.py
def sum (a, b): return a + b И вызовем её из кода на D. Добавим это в функцию main.main
… script.x = 13; script.y = 21;
writefln («result: %d», script.py_eval! int («myscript.sum (x, y)»)); … Усложнить не получилось =)Класс InterpContext олицетворяет контекст интерпретатора (как ни странно) и мы можем добавлять туда переменные таким простым способом. Поля x и y не являются частью объекта script — таких полей нет, но это работает потому, что в языке D есть возможность конвертировать вызовы несуществующих методов класса (или структуры) в вызов метода opDispatch, который, как в данном случае, может быть свойством.Код метода InterpContext.opDispatch @property PydObject opDispatch (string id)() { // возвращает значение из контекста return this.locals[id]; }
@property void opDispatch (string id, T)(T t) { // записывает значение в контекст static if (is (T == PydObject)) { alias t s; }else{ PydObject s = py (t); } this.locals[id] = py (s); } Таким же способом мы можем взять объект из контекста: … script.py_stmts («z = myscript.sum (8,7)»); writefln («result2: %d», script.z.to_d! int); … Да и функции можно вызывать практически так же: … auto sum = script.myscript.sum; writefln («result3: %d», sum (14,15).to_d! int); … некоторые моменты синтаксис property в языке D является давно обсуждаемой темой, а конкретно вопрос связан с ситуацией, когда property возвращает объект с методом opCall script.myscript.sum (14,15).to_d! int; // сработает, что странно, было бы логично запретить script.myscript.oneargfunc (12).to_d! int; // не скомпилируется, так как oneargfunc (12) это вызов opDispatch с параметром 12 script.myscript.oneargfunc ()(12).to_d! int; // тут всё в порядке: явно вызывается oneargfunc (), затем у результата вызывается opCall (12) Теперь попробуем наоборот из кода на python вызвать код на D. Создадим новую папку для этого.Создадим файл dcode.d с содержанием: module dcode;
import pyd.pyd; import std.math;
float[] calc (float x, float y) { return [ sqrt (x*y), x^^y, x/y ]; }
extern© void PydMain () { def!(calc)(); module_init (); } и файл setup_my_dcode.py (имя никак не влияет) from pyd.support import setup, Extension projName = 'dcode' setup ( name=projName, version='0.1', ext_modules=[ Extension (projName, ['dcode.d'], extra_compile_args=['-w'], build_deimos=True, d_lump=True ) ], ) соберём наше расширение (именно build, а не install, чтобы не засорять систему тестовыми файлами) python3 setup_my_dcode.py build создасться папочка build такого содержания build ├── lib.linux-x86_64–3.4 │ └── dcode.cpython-34m.so └── temp.linux-x86_64–3.4 └── infra ├── pydmain.d ├── so_ctor.o └── temp.o Нас интересует build/lib.linux-x86_64–3.4/dcode.cpython-34m.so. Копируем его в текущую директорию или переходим в папку с ним и можем проверять прямо в интерактивном интерпретаторе: python3 Python 3.4.1 (default, Nov 3 2014, 14:38:10) [GCC 4.9.1 20140930 (Red Hat 4.9.1–11)] on linux Type «help», «copyright», «credits» or «license» for more information. >>> import dcode >>> dcode.calc (5, 12) [7.745966911315918, 244140624.0, 0.4166666567325592] >>> И опять всё достаточно просто! И опять попробуем всё усложнить — добавим класс в dcode.d
class Foo { float a = 0, b = 0; static string desc () { return «some ops»; } this (float A, float B) { a = A; b = B; } float sum () const { return a + b; } float div () const { return a / b; } }
extern© void PydMain () { def!(calc)(); // сначала функции module_init (); // затем инициализация модуля wrap_class!(// только потом классы Foo, Init!(float, float), Repr!(Foo.toString), // как python будет это переводить в строку Def!(Foo.sum), Def!(Foo.div), StaticDef!(Foo.desc) )(); } К сожалению в этой ситуации всё действительно немного усложнилось. Для работы с классами D в python нужно объявлять конструкторы, функции, и т.д. Собираем, проверяем: python3 Python 3.4.1 (default, Nov 3 2014, 14:38:10) [GCC 4.9.1 20140930 (Red Hat 4.9.1–11)] on linux Type «help», «copyright», «credits» or «license» for more information. >>> from dcode import Foo >>> Foo.desc () 'some ops' >>> a = Foo (1,2) >>> a.div () 0.5 >>> a.sum () 3.0 >>> Работает! О вариантах применения говорить не нужно: их много и они интересны. Стоит упомянуть, что библиотека ещё не дошла до стабильной версии 1.0.0 и могут встречаться ошибки.Я нашёл только одну проблему: нельзя запустить код на D из кода на python, встроенного в код на DНо мне кажется это не фундаментальная проблема и автор её сможет легко исправить.
Очень приятная документация по проекту находится здесь и ещё примеры здесь