You are viewing roinet

C# + IronPython: вызов методов

IronPython – реализация python на платформе .Net обычно имеено тем, что это именно .Net реализация на основе DLR. Это дает возможность простого сочетания производительности и строгости статических языков .Net (c#, например) и гибкости python. В текущем проекте потребовалась как раз такая связка для обеспечения производительности и гибкости одного из компонентов сервисной архитектуры.

В решаемой задаче необходимо было в коде C# использовать объекты python, находящиеся в динамически подгружаемых скриптах. Для решения задачи использованы .Net 3.5 (C# 3.0), DLR 0.91, IronPython 2.6 RC1. На прямую работать с интерпретатором python и dlr классами не очень удобно, т.к. надо постоянно тащить за собой такие объекты как ScriptEngine, ScriptScope. Так как способы динамической типизации появятся только в версии C# 4.0, то на данный момент приходится пользоваться небольшими и простыми обертками.

Пусть имеется скрипт:

class PythonCalc ( object ) :

	def add ( self, val1, val2 ) :
		return val1 + val2

	def minus ( self, val1, val2 ) :
		return val1 - val2
</p>

Для того чтобы начать использовать экземпляр класса PythonCalc необходимо создать экземпляр интерпретатора IronPython:

// вычитываем конфигурацию из app.config
var scriptLang = ScriptRuntimeSetup.ReadConfiguration();
var scriptRuntime = new ScriptRuntime(scriptLang);
var scriptEngine = scriptRuntime.GetEngine("IronPython");

Для успешного исполнения данного кода app.config должен иметь примерно следующий вид:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="microsoft.scripting"
type="Microsoft.Scripting.Hosting.Configuration.Section, Microsoft.Scripting"
requirePermission="false" />
  </configSections>
  <microsoft.scripting>
    <languages>
        <language names="IronPython;Python;py" extensions=".py"
displayName="IronPython 2.6"
type="IronPython.Runtime.PythonContext, IronPython" />
    </languages>
  </microsoft.scripting>
</configuration>
</p>

Далее создаем класс обертку. Из-за особенностей динамической типизации методы add и minus могут принимать в качестве фактических параметров любые объекты. В шарпе можно добится того же, однако придется либо постоянно указывать тип сигнатуры методов, либо пользоваться приведением. Как один из вариантов решения проблемы дополнить реализацию ограничением идентичности типа первого и второго операнда:

public class PythonCalc<T>
{
    private readonly object pythonCalc;

    public PythonCalc(ScriptEngine scriptEngine)
    {
        var operations = scriptEngine.CreateOperations();
        var script = Encoding.GetEncoding(1251).GetString(
            Assembly.GetCallingAssembly().GetManifestResourceStream("TestDLRApplication.script.py").ReadToEnd());
        var scriptSource = scriptEngine.CreateScriptSourceFromString(script, SourceCodeKind.Statements);
        var scriptCode = scriptSource.Compile();
        scriptCode.Execute();
        var scriptScope = scriptCode.DefaultScope;

        pythonCalc = scriptScope.GetVariable<Func<object>>("PythonCalc")();
        Add = operations.GetMember<Func<T, T, T>>(pythonCalc, "add");
        Minus = operations.GetMember<Func<T, T, T>>(pythonCalc, "minus");
    }

    public Func<T, T, T> Add { get; private set; }
    public Func<T, T, T> Minus { get; private set; }
}
</p>

Можно немного расширить гибкость данной обертки по средством замены делегатов на методы с обобщенной типизацией:


public class PythonCalc1
{
    private object pythonCalc;
    private Func<object, object, object> add;
    private Func<object, object, object> minus;

    public PythonCalc1(ScriptEngine scriptEngine)
    {
        var operations = scriptEngine.CreateOperations();
        var script = Encoding.GetEncoding(1251).GetString(
            Assembly.GetCallingAssembly().GetManifestResourceStream("TestDLRApplication.script.py").ReadToEnd());
        var scriptSource = scriptEngine.CreateScriptSourceFromString(script, SourceCodeKind.Statements);
        var scriptCode = scriptSource.Compile();
        scriptCode.Execute();
        var scriptScope = scriptCode.DefaultScope;

        pythonCalc = scriptScope.GetVariable<Func<object>>("PythonCalc")();
        add = operations.GetMember<Func<object, object, object>>(pythonCalc, "add");
        minus = operations.GetMember<Func<object, object, object>>(pythonCalc, "minus");
    }

    public TResult Add<TResult>(TResult val1, TResult val2)
    {
        return (TResult) add(val1, val2);
    }

    public TResult Minus<TResult>(TResult val1, TResult val2)
    {
        return (TResult) minus(val1, val2);
    }

}

Пример использования первого и второго варианта очевиден:

var pythonCalc = new PythonCalc<int>(scriptEngine);
var result = pythonCalc.Add(12, 3) + pythonCalc.Minus(30, 5);

var pythonCalc1 = new PythonCalc1(scriptEngine);
var result = pythonCalc1.Add(12, 3) + pythonCalc1.Minus(30, 5);

Таким образом, мы получаем относительно простые обертки, позволяющие нам прозрачно использовать DLR объекты в статическом коде.

Весь python основан на двух концепциях, из которых следуют практически все остальные нюансы – все есть объект и некоторые объекты могут обладать callable свойством, т.е. их можно вызвать. Таким образом все классы, экземпляры классов, свойства, методы и т.п. – является объектом. Кроме того если углублятся в структуру классов python, то можно заметить, что экземпляр класса – фактически словарь, в котором хранятся имена членов класса и ссылки на них. Это позволяет использовать операцию GetMember для любых переменных, классов, свойств и методов, рассматривая их как члены класса. Так в этом примере:

add = operations.GetMember<Func<object, object, object>>(pythonCalc, "add");

видно, что мы методом GetMember берем указатель на член экземпляра DLR класса PythonCalc, который обладает callable свойством, приводя его к определенной сигнатуре делегата.

Кроме того, каждый класс python является объектом, наследующимся от метакласса type. Этот стандартный метакласс реализует метод __call__, делающий каждый класс callable сущностью – конструктор. Поэтому появляется возможность применить метод GetMember для классов и привести к сингатуре делегата.

В ObjectOperations имеются методы CreateInstance и InvokeMember для аналогичных действий, однако они не всегда работают. В ironpython 2.0-2.6 имеется проблема в связи с вызовом методов и функций более чем с двумя параметрами из CLR (c# и т.д.). Если уж говорить по честному, то это проблема не IronPython, а DLR. Проблема заключается в том, что CreateInstance в любом случае, а InvokeMember при вызове метода класса более чем с двумя параметрами – генерирует исключение NotImplementedException. Например:


class Broker ( object ) :
    def validation( self, info, provider_manager ):
        pass

    def check( self, payment, payment_manager, provider_manager ):
        pass

operations = scriptEngine.CreateOperations();
var scriptSource = scriptEngine.CreateScriptSourceFromString(script,
SourceCodeKind.Statements);
scriptCode = scriptSource.Compile();
scriptCode.Execute();
scriptScope = scriptCode.DefaultScope;
broker = scriptScope.GetVariable<Func<object>>("Broker")();
operations.InvokeMember(broker, "validation", paymentInfo, providerManager);
operations.InvokeMember(broker, "check", payment, paymentManager, providerManager);

Вызов метода validation проходит успешно, а вот check вызывает ошибку NotImplementedException


System.NotImplementedException: Method or operation not implemented.
   в Microsoft.Scripting.Runtime.DynamicOperations.InvokeMember(Object obj, String memberName, Boolean ignoreCase, Object[] parameters)
   в Microsoft.Scripting.Hosting.ObjectOperations.InvokeMember(Object obj, String memberName, Object[] parameters)


В списке рассылки Discussion of IronPython ответил Dino Viehland (разработчик команды DLR) следующее:

We haven't implemented support for more than 3 parameters :)In 2.6 I just fixed > 3 params for the Invoke case (not InvokeMember)and I can fix > 3 for InvokeMember/CreateInstance as well before2.6 ships.The preferred way to do this though is to do a GetMember whereT is a delegate type with > 3 paraemters. Then you can just invokethe delegate.

Таким образом можно обойти указанную проблему не только для вызова методов объекта, но и для создания экземпляров класса.

В результате, создается впечаление, что в архитектуре DLR далеко не все так гладко - как будто присутствует две ветки реализации (InvokeMember и GetMember). Возможно это будет как пофиксено в конце концов.

Запись опубликована Roinet.Net.Вы можете оставить комментарии здесь или тут

Comments

(Anonymous)

рыбалка

спасибо большое было очень интересно читать

December 2009

S M T W T F S
  12345
6789101112
13141516171819
20212223242526
2728293031  
Powered by LiveJournal.com