2

IronPython (2.7.3) 似乎没有使用 ExpressionType.IsFalse 和 ExpressionType.IsTrue 检查 TryUnaryOperation 以执行逻辑 AND 和 OR 操作的短路评估。

这是一个使用继承自 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
4

2 回答 2

1

无法实现您想要的确切行为,但有一些技巧。

完整性检查

首先,让我们观察一下,实际上正在调用重写的方法,并且我们正确实现了DynamicObject. 我修改了你的TryUnaryOperation

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对象并将其传递到范围后,如下所示:

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

按预期打印:

TryUnaryOperation was called with: Not

动机

在覆盖TryInvoke,之后TryInvokeMemberTryConvert我们可以观察到,它们都没有被调用。冲浪后我发现,短路运算符and or不能被覆盖,因为:

它们更像是控制流工具而不是操作符,并且覆盖它们更像是覆盖如果

考虑 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- 它仍然不会在a & b表达式中调用。期望它会用a.Name表达式甚至. 调用是完全安全的a.Name()。您可以使用下一个代码进行验证

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

并称它为a.Nameor a.Name()。稍后调用会导致“str is not callable”消息错误。

希望这对您有所帮助。

于 2013-01-09T11:22:13.750 回答
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);
            }
        }
    }
于 2013-01-11T01:48:20.500 回答