2013-01-06 35 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