2

IronPython (2.7.3)은 논리적 AND 및 OR 연산의 단락 회로 평가를 위해 ExpressionType.IsFalse 및 ExpressionType.IsTrue를 사용하여 TryUnaryOperation을 확인하지 않는 것처럼 보입니다.TryUnaryOperation을 재정의하는 IronPython 및 DynamicObject

다음은 DynamicObject에서 상속 한 클래스를 사용하는 예입니다. C#에서는 완벽하게 작동하지만 IronPython 표현식에서 사용되는 경우 잘못된 결과를 생성합니다. 그 행동이 예상 되었습니까? 아니면 버그입니까? IronPython을 C#과 같은 방식으로 작동 시키려면 어떻게해야합니까?

클래스 :

public class Dyn : DynamicObject 
{ 
    private readonly string text; 

    public Dyn(string text) 
    { 
     this.text = text; 
    } 

    public override string ToString() 
    { 
     return this.text; 
    } 

    public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) 
    { 
     result = new Dyn(this + " " + binder.Operation + " " + arg); 
     return true; 
    } 

    public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result) 
    { 
     switch (binder.Operation) 
     { 
      case ExpressionType.IsFalse: 
      case ExpressionType.IsTrue: 
       result = false; 
       return true; 
     } 

     return base.TryUnaryOperation(binder, out result); 
    } 
} 

사용법 :

dynamic a = new Dyn("a"); 
dynamic b = new Dyn("b"); 
dynamic c = new Dyn("c"); 

var correct = a && b || c; 

var engine = Python.CreateEngine(); 
var scope = engine.CreateScope(); 
scope.SetVariable("a", a); 
scope.SetVariable("b", b); 
scope.SetVariable("c", c); 
var incorrect = engine.Execute("a and b or c", scope); 

Console.WriteLine("Correct: " + correct); 
Console.WriteLine("Incorrect: " + incorrect); 

인쇄 :

Correct: a And b Or c 
Incorrect: b 

답변

1

당신이 얻을 수없는 욕망,하지만 약간의 트릭이있는 정확한 동작. 정신이 먼저

을 확인

는, 오버라이드 (override) 메소드가 실제로 전화를 우리가 DynamicObject의 올바른 구현이되고 있음을 관찰 할 수 있습니다. 내가 수정 한 사용자의 TryUnaryOperation :

dynamic a = new Dyn("a"); 

var engine = Python.CreateEngine(); 
var scope = engine.CreateScope(); 
scope.SetVariable("a", a); 
var result = engine.Execute("not a", scope); 
Console.WriteLine(result); 

인쇄를 예상대로 :

public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result) 
{ 
    Console.WriteLine("TryUnaryOperation was called with operation: {0}", binder.Operation); 

    return base.TryUnaryOperation(binder, out result); 
} 

Dyn 객체를 생성하고이 같은 범위로를 통과 한 후

TryUnaryOperation was called with: Not 

동기 부여

에이 우리가 관찰 할 수있는 것 중 아무 것도 호출되지 않는다는 것을 알 수 있습니다. TryInvoke, TryInvokeMember, TryConvert. 그들은 더 많은 사업자에 비해 제어 흐름 도구처럼 더

이를 고려하면 무시처럼 될 것이라고 무시

: 서핑 후 나는 때문에 단락 사업자 andor는, 오버라이드 (override) 할 수 없음을 발견 에 StackOverflow에 대한 질문 Any way to override the and operator in Python?

닫기 솔루션

그러나이 논리 연산자 &|을 재정의하는 방법이 있습니다.당신의 Dyn의 소스 코드는 다음 코드를 호출 한 후

public class Dyn : DynamicObject 
{ 
    private readonly string text; 


    public Dyn(string text) 
    { 
     this.text = text; 
    } 

    public override string ToString() 
    { 
     return this.text; 
    } 

    public object __and__(Dyn other) 
    { 
     return new Dyn(this + " and " + other); 
    } 

    public object __or__(Dyn other) 
    { 
     return new Dyn(this + " or " + other); 
    } 
} 

아래에 주어진 그것을 성공적으로 인쇄 a and b or c

dynamic a = new Dyn("a"); 
dynamic b = new Dyn("b"); 
dynamic c = new Dyn("c"); 

var engine = Python.CreateEngine(); 
var scope = engine.CreateScope(); 

scope.SetVariable("a", a); 
scope.SetVariable("b", b); 
scope.SetVariable("c", c); 

var correct = engine.Execute("a & b | c", scope); 
Console.WriteLine(correct); 

참고 :이 TryGetMember 오버라이드 (override)하더라도 - 그것은 여전히 ​​a & b에 호출되지 않습니다 표현. a.Name 표현식 또는 심지어 a.Name()과 함께 호출 될 것으로 예상하는 것이 완전히 안전합니다. 당신은 다음 코드

public override bool TryGetMember(GetMemberBinder binder, out object result) 
{ 
    result = "test"; 
    return true; 
} 

그것을 확인하고 a.Name 또는 a.Name()처럼 호출 할 수 있습니다. 나중에 str is callable not message 에러가 발생합니다.

희망이 조금 도움이되었습니다.

+0

문제 :

((value(Parse.Program+<>c__DisplayClass0).a AndAlso value(Parse.Program+<>c__DisplayClass0).b) OrElse value(Parse.Program+<>c__DisplayClass0).c) ((a AndAlso b) OrElse c) 

그리고 여기에 (불완전한) 변환 절차입니다 :

static void Main(string[] args) { var a = true; var b = true; var c = true; Expression<Func<bool>> csAst =() => a && b || c; var csexpr = csAst.Body; Console.WriteLine(csexpr.ToString()); ScriptEngine engine = Python.CreateEngine(); ScriptScope scope = engine.CreateScope(); scope.SetVariable("a", a); scope.SetVariable("b", b); scope.SetVariable("c", c); string code = "a and b or c"; var pyexpr = GetLinqExpressionFromPyExpression(code, scope); Console.WriteLine(pyexpr.ToString()); } 

출력은 비교 연산자 (==, <, >, ...)보다 적게 바인딩하므로 "a == b | b == c"와 같이 뭔가 부 자연스럽게 보이는 괄호 만 사용합니다. "not"라고 쓰면 이상하지만, &와 |를 사용해야합니다. 인스 티 언트 및/또는 – Rauhotz

+0

그 이유는 내가 단서 회로 연산자를 오버로드 할 수 없기 때문에, 당신이 만날 의도가있는 정확한 동작을 달성 할 수 없다는 것입니다. –

1

나는 구문 트리를 얻기 위해 연산자 오버로딩을 사용하는 것이 최선의 방법은 아니라고 생각합니다. 아마도 구문 트리를 탐색하여 필요한 정보를 추출하는 것이 좋습니다. 슬프게도 C# 람다 식의 AST는 IronPython AST와 호환되지 않습니다. 그래서 IronPython AST를 Linq AST로 변환하는 변환 프로 시저를 설정했습니다. | &와 함께

static System.Linq.Expressions.Expression GetLinqExpressionFromPyExpression(string pyExpression, ScriptScope scope) 
    { 
     ScriptEngine engine = scope.Engine; 
     ScriptSource source = 
      engine.CreateScriptSourceFromString(pyExpression, SourceCodeKind.Expression); 
     SourceUnit sourceUnit = HostingHelpers.GetSourceUnit(source); 
     LanguageContext context = HostingHelpers.GetLanguageContext(engine); 
     Parser parser = Parser.CreateParser(
      new CompilerContext(sourceUnit, context.GetCompilerOptions(), ThrowingErrorSink.Default), 
      (PythonOptions)context.Options); 
     PythonAst ast = parser.ParseFile(true); 
     SuiteStatement suite = (SuiteStatement)ast.Body; 
     ExpressionStatement statement = (ExpressionStatement)suite.Statements[0]; 
     IronPython.Compiler.Ast.Expression expression = statement.Expression; 

     return Convert(expression, scope); 
    } 

    static readonly Dictionary<PythonOperator, ExpressionType> linqOpFromPyOp = new Dictionary<PythonOperator, ExpressionType>{ 
     { PythonOperator.Not, System.Linq.Expressions.ExpressionType.Not }, 
     { PythonOperator.Pos, System.Linq.Expressions.ExpressionType.UnaryPlus }, 
     { PythonOperator.Invert, System.Linq.Expressions.ExpressionType.OnesComplement }, 
     { PythonOperator.Negate, System.Linq.Expressions.ExpressionType.NegateChecked }, 
     { PythonOperator.Add, System.Linq.Expressions.ExpressionType.AddChecked }, 
     { PythonOperator.Subtract, System.Linq.Expressions.ExpressionType.SubtractChecked }, 
     { PythonOperator.Multiply, System.Linq.Expressions.ExpressionType.MultiplyChecked }, 
     { PythonOperator.Divide, System.Linq.Expressions.ExpressionType.Divide }, 
     { PythonOperator.TrueDivide, System.Linq.Expressions.ExpressionType.Divide }, 
     { PythonOperator.Mod, System.Linq.Expressions.ExpressionType.Modulo }, 
     { PythonOperator.BitwiseAnd, System.Linq.Expressions.ExpressionType.And }, 
     { PythonOperator.BitwiseOr, System.Linq.Expressions.ExpressionType.Or }, 
     { PythonOperator.ExclusiveOr, System.Linq.Expressions.ExpressionType.ExclusiveOr }, 
     { PythonOperator.LeftShift, System.Linq.Expressions.ExpressionType.LeftShift }, 
     { PythonOperator.RightShift, System.Linq.Expressions.ExpressionType.RightShift }, 
     { PythonOperator.Power, System.Linq.Expressions.ExpressionType.Power }, 
     //{ PythonOperator.FloorDivide, System.Linq.Expressions.ExpressionType.Divide }, // TODO 
     { PythonOperator.LessThan, System.Linq.Expressions.ExpressionType.LessThan }, 
     { PythonOperator.LessThanOrEqual, System.Linq.Expressions.ExpressionType.LessThanOrEqual }, 
     { PythonOperator.GreaterThan, System.Linq.Expressions.ExpressionType.GreaterThan }, 
     { PythonOperator.GreaterThanOrEqual, System.Linq.Expressions.ExpressionType.GreaterThanOrEqual }, 
     { PythonOperator.Equal, System.Linq.Expressions.ExpressionType.Equal }, 
     { PythonOperator.NotEqual, System.Linq.Expressions.ExpressionType.NotEqual }, 
     //{ PythonOperator.In, System.Linq.Expressions.ExpressionType. }, // TODO 
     //{ PythonOperator.NotIn, System.Linq.Expressions.ExpressionType. }, // TODO 
     //{ PythonOperator.IsNot, System.Linq.Expressions.ExpressionType.TypeIs }, // TODO 
     { PythonOperator.Is, System.Linq.Expressions.ExpressionType.TypeIs }, 
    }; 

    static System.Linq.Expressions.Expression Convert(IronPython.Compiler.Ast.Expression node, ScriptScope scope) 
    { 
     switch (node.NodeName) 
     { 
      case "AndExpression": 
       { 
        var _node = (IronPython.Compiler.Ast.AndExpression)node; 
        return System.Linq.Expressions.BinaryExpression.AndAlso(
         Convert(_node.Left, scope), 
         Convert(_node.Right, scope)); 
       } 
      case "BinaryExpression": 
       { 
        var _node = (IronPython.Compiler.Ast.BinaryExpression)node; 
        // TODO: do conversion if left and right have different types 
        return System.Linq.Expressions.BinaryExpression.MakeBinary(
         linqOpFromPyOp[_node.Operator], 
         Convert(_node.Left, scope), 
         Convert(_node.Right, scope)); 
       } 
      case "OrExpression": 
       { 
        var _node = (IronPython.Compiler.Ast.OrExpression)node; 
        return System.Linq.Expressions.BinaryExpression.OrElse(
         Convert(_node.Left, scope), 
         Convert(_node.Right, scope)); 
       } 
      case "NameExpression": 
       { 
        var _node = (IronPython.Compiler.Ast.NameExpression)node; 
        return System.Linq.Expressions.Expression.Parameter(
         scope.GetVariable(_node.Name).GetType(), 
         _node.Name); 
       } 
      // TODO: Add further Python Expression types 
      default: 
       throw new ArgumentTypeException("unhandled NodeType '" + node.NodeName + "'"); 
     } 
    } 

    internal class ThrowingErrorSink : ErrorSink 
    { 
     public static new readonly ThrowingErrorSink/*!*/ Default = new ThrowingErrorSink(); 

     private ThrowingErrorSink() { } 

     public override void Add(SourceUnit sourceUnit, string message, SourceSpan span, int errorCode, Severity severity) 
     { 
      if (severity == Severity.Warning) 
      { 
       PythonOps.SyntaxWarning(message, sourceUnit, span, errorCode); 
      } 
      else 
      { 
       throw PythonOps.SyntaxError(message, sourceUnit, span, errorCode); 
      } 
     } 
    } 
+0

표현식은 파이썬 스크립트에 깊게 묻힐 수 있으므로 정적 AST 변환은 no입니다. 선택권. – Rauhotz