IronPython (2.7.3) seems to not check the TryUnaryOperation with ExpressionType.IsFalse and ExpressionType.IsTrue for performing short-circuit evaluation of the logical AND and OR operations.
Here's an example that uses a class that inherits from DynamicObject. In C#, it works perfectly, but produces a wrong result if used in an IronPython expression. Is that behavior expected or a bug? How can i get IronPython to behave the same way as C#?
The class:
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);
}
}
The usage:
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);
Prints:
Correct: a And b Or c
Incorrect: b
The exact behavior that you desire can not be achieved, but there are some tricks.
Sanity check
First, lets observe, that overridden methods are actually being called and we have correct implementation of DynamicObject
. I have modified your 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);
}
After creating Dyn
object and passing it into scope like this:
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);
Prints as expected:
TryUnaryOperation was called with: Not
Motivation
After overriding TryInvoke
, TryInvokeMember
, TryConvert
we can observe, that none of them are called. After surfing I found, that short-circuit operators and
or
cannot be overriden, because:
they are more like control flow tools than operators and overriding them would be more like overriding if
Consider this question on StackOverflow Any way to override the and operator in Python?
Close solution
But there exist a way to override logical operators &
and |
. Source code for your Dyn
is given below
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);
}
}
Then after calling next code it successfully prints 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);
Note: even if you override TryGetMember
- it still won't be called in a & b
expressions. It is completely safe to expect it will be called with a.Name
expressions or even a.Name()
. You can verify it with next code
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = "test";
return true;
}
And call it like a.Name
or a.Name()
. Later call would result `str is not callable' message error.
Hope this helped you a bit.