2013-01-06 77 views
2

IronPython(2.7.3)似乎不檢查帶有ExpressionType.IsFalse和ExpressionType.IsTrue的TryUnaryOperation,以執行邏輯AND和OR操作的短路評估。覆蓋TryUnaryOperation的IronPython和DynamicObject

下面是一個使用從DynamicObject繼承的類的示例。在C#中,它完美地工作,但如果在IronPython表達式中使用,則會產生錯誤的結果。這是行爲預期還是錯誤?我如何獲得IronPython的行爲方式與C#相同?

Correct: a And b Or c 
Incorrect: b 

回答

1

,你的願望是無法實現的,但也有一些技巧的具體行爲:

類:

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); 

打印。

完整性檢查

首先,讓我們觀察到,該重載方法實際上是被調用,我們有正確實施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不能被覆寫,因爲:

他們更像是控制流的工具比運營商和重寫他們會更喜歡重寫,如果

考慮這個在計算器上有問題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 - 它仍然不會在a & b稱爲表達式。這是完全安全的,預計它將被稱爲a.Name表達式,甚至a.Name()。你可以用下面的代碼

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

驗證它,並把它像a.Namea.Name()。稍後調用將導致'str不可調用'消息錯誤。

希望這對你有所幫助。

+0

的問題,與和|是它們比比較運算符(==,<, >,...)少綁定,所以像「a == b | b == c」這樣的東西只能用於括號,這看起來不自然。寫「不」,也很奇怪,但必須使用&和|和/或。 – Rauhotz

+0

我知道,這就是爲什麼我強調,你打算達到的確切行爲無法實現,因爲短路操作不能超載 –

1

我認爲使用操作符重載來獲取語法樹並不是最好的方法。可能最好遍歷語法樹並從中提取所需的信息。可悲的是,C#lambda表達式的AST與IronPython AST不兼容。所以我建立了一個轉換程序來將IronPython AST轉換爲Linq AST。

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()); 
    } 

輸出是:

((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 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

表達式可以深埋在python腳本中,所以靜態AST轉換不是選項。 – Rauhotz