python

Python и D

  • понедельник, 8 июня 2015 г. в 02:13:02
http://habrahabr.ru/post/259727/

Доброго времени суток, хабр!

Здесь мы не будем рассуждать о плюсах и минусах языков.



Мы будем использовать их вместе!





В этом нам поможет замечательная библиотека 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(C) 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(C) 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

Но мне кажется это не фундаментальная проблема и автор её сможет легко исправить.

Очень приятная документация по проекту находится здесь и ещё примеры здесь