Skip to content

Implemented dynamic equality and inequality for PyObject instances #1849

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ details about the cause of the failure
able to access members that are part of the implementation class, but not the
interface. Use the new `__implementation__` or `__raw_implementation__` properties to
if you need to "downcast" to the implementation class.
- BREAKING: `==` and `!=` operators on `PyObject` instances now use Python comparison
(previously was equivalent to `object.ReferenceEquals(,)`)
- BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition
to the regular method return value (unless they are passed with `ref` or `out` keyword).
- BREAKING: Drop support for the long-deprecated CLR.* prefix.
Expand Down
22 changes: 22 additions & 0 deletions src/embed_tests/dynamic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,28 @@ public void PassPyObjectInNet()
Assert.IsTrue(sys.testattr1.Equals(sys.testattr2));
}

// regression test for https://github.com/pythonnet/pythonnet/issues/1848
[Test]
public void EnumEquality()
{
using var scope = Py.CreateScope();
scope.Exec(@"
import enum

class MyEnum(enum.IntEnum):
OK = 1
ERROR = 2

def get_status():
return MyEnum.OK
"
);

dynamic MyEnum = scope.Get("MyEnum");
dynamic status = scope.Get("get_status").Invoke();
Assert.IsTrue(status == MyEnum.OK);
}

// regression test for https://github.com/pythonnet/pythonnet/issues/1680
[Test]
public void ForEach()
Expand Down
75 changes: 59 additions & 16 deletions src/runtime/PythonTypes/PyObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1075,12 +1075,9 @@ public virtual bool Equals(PyObject? other)
{
return true;
}
int r = Runtime.PyObject_Compare(this, other);
if (Exceptions.ErrorOccurred())
{
throw PythonException.ThrowLastAsClrException();
}
return r == 0;
int result = Runtime.PyObject_RichCompareBool(obj, other.obj, Runtime.Py_EQ);
if (result < 0) throw PythonException.ThrowLastAsClrException();
return result != 0;
}


Expand Down Expand Up @@ -1304,6 +1301,18 @@ public override bool TryConvert(ConvertBinder binder, out object? result)
return false;
}

private bool TryCompare(PyObject arg, int op, out object @out)
{
int result = Runtime.PyObject_RichCompareBool(this.obj, arg.obj, op);
@out = result != 0;
if (result < 0)
{
Exceptions.Clear();
return false;
}
return true;
}

public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result)
{
using var _ = Py.GIL();
Expand Down Expand Up @@ -1352,32 +1361,29 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg
res = Runtime.PyNumber_InPlaceXor(this.obj, ((PyObject)arg).obj);
break;
case ExpressionType.GreaterThan:
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) > 0;
return true;
return this.TryCompare((PyObject)arg, Runtime.Py_GT, out result);
case ExpressionType.GreaterThanOrEqual:
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) >= 0;
return true;
return this.TryCompare((PyObject)arg, Runtime.Py_GE, out result);
case ExpressionType.LeftShift:
res = Runtime.PyNumber_Lshift(this.obj, ((PyObject)arg).obj);
break;
case ExpressionType.LeftShiftAssign:
res = Runtime.PyNumber_InPlaceLshift(this.obj, ((PyObject)arg).obj);
break;
case ExpressionType.LessThan:
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) < 0;
return true;
return this.TryCompare((PyObject)arg, Runtime.Py_LT, out result);
case ExpressionType.LessThanOrEqual:
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) <= 0;
return true;
return this.TryCompare((PyObject)arg, Runtime.Py_LE, out result);
case ExpressionType.Modulo:
res = Runtime.PyNumber_Remainder(this.obj, ((PyObject)arg).obj);
break;
case ExpressionType.ModuloAssign:
res = Runtime.PyNumber_InPlaceRemainder(this.obj, ((PyObject)arg).obj);
break;
case ExpressionType.NotEqual:
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) != 0;
return true;
return this.TryCompare((PyObject)arg, Runtime.Py_NE, out result);
case ExpressionType.Equal:
return this.TryCompare((PyObject)arg, Runtime.Py_EQ, out result);
case ExpressionType.Or:
res = Runtime.PyNumber_Or(this.obj, ((PyObject)arg).obj);
break;
Expand All @@ -1402,6 +1408,40 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg
return true;
}

public static bool operator ==(PyObject? a, PyObject? b)
{
if (a is null && b is null)
{
return true;
}
if (a is null || b is null)
{
return false;
}

using var _ = Py.GIL();
int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_EQ);
if (result < 0) throw PythonException.ThrowLastAsClrException();
return result != 0;
}

public static bool operator !=(PyObject? a, PyObject? b)
{
if (a is null && b is null)
{
return false;
}
if (a is null || b is null)
{
return true;
}

using var _ = Py.GIL();
int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_NE);
if (result < 0) throw PythonException.ThrowLastAsClrException();
return result != 0;
}

// Workaround for https://bugzilla.xamarin.com/show_bug.cgi?id=41509
// See https://github.com/pythonnet/pythonnet/pull/219
internal static object? CheckNone(PyObject pyObj)
Expand Down Expand Up @@ -1436,14 +1476,17 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out object?
case ExpressionType.Not:
r = Runtime.PyObject_Not(this.obj);
result = r == 1;
if (r == -1) Exceptions.Clear();
return r != -1;
case ExpressionType.IsFalse:
r = Runtime.PyObject_IsTrue(this.obj);
result = r == 0;
if (r == -1) Exceptions.Clear();
return r != -1;
case ExpressionType.IsTrue:
r = Runtime.PyObject_IsTrue(this.obj);
result = r == 1;
if (r == -1) Exceptions.Clear();
return r != -1;
case ExpressionType.Decrement:
case ExpressionType.Increment:
Expand Down
25 changes: 0 additions & 25 deletions src/runtime/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -962,31 +962,6 @@ internal static IntPtr PyObject_CallObject(IntPtr pointer, IntPtr args)

internal static int PyObject_RichCompareBool(BorrowedReference value1, BorrowedReference value2, int opid) => Delegates.PyObject_RichCompareBool(value1, value2, opid);

internal static int PyObject_Compare(BorrowedReference value1, BorrowedReference value2)
{
int res;
res = PyObject_RichCompareBool(value1, value2, Py_LT);
if (-1 == res)
return -1;
else if (1 == res)
return -1;

res = PyObject_RichCompareBool(value1, value2, Py_EQ);
if (-1 == res)
return -1;
else if (1 == res)
return 0;

res = PyObject_RichCompareBool(value1, value2, Py_GT);
if (-1 == res)
return -1;
else if (1 == res)
return 1;

Exceptions.SetError(Exceptions.SystemError, "Error comparing objects");
return -1;
}


internal static int PyObject_IsInstance(BorrowedReference ob, BorrowedReference type) => Delegates.PyObject_IsInstance(ob, type);

Expand Down