Skip to content

Commit e84731f

Browse files
lostmsufilmor
authored andcommitted
implemented dynamic equality and inequality for PyObject instances
fixed unhandled Python errors during comparison attempts fixes #1848
1 parent 28a78ed commit e84731f

File tree

4 files changed

+83
-41
lines changed

4 files changed

+83
-41
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ details about the cause of the failure
4545
able to access members that are part of the implementation class, but not the
4646
interface. Use the new `__implementation__` or `__raw_implementation__` properties to
4747
if you need to "downcast" to the implementation class.
48+
- BREAKING: `==` and `!=` operators on `PyObject` instances now use Python comparison
49+
(previously was equivalent to `object.ReferenceEquals(,)`)
4850
- BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition
4951
to the regular method return value (unless they are passed with `ref` or `out` keyword).
5052
- BREAKING: Drop support for the long-deprecated CLR.* prefix.

src/embed_tests/dynamic.cs

+22
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,28 @@ public void PassPyObjectInNet()
128128
Assert.IsTrue(sys.testattr1.Equals(sys.testattr2));
129129
}
130130

131+
// regression test for https://github.com/pythonnet/pythonnet/issues/1848
132+
[Test]
133+
public void EnumEquality()
134+
{
135+
using var scope = Py.CreateScope();
136+
scope.Exec(@"
137+
import enum
138+
139+
class MyEnum(enum.IntEnum):
140+
OK = 1
141+
ERROR = 2
142+
143+
def get_status():
144+
return MyEnum.OK
145+
"
146+
);
147+
148+
dynamic MyEnum = scope.Get("MyEnum");
149+
dynamic status = scope.Get("get_status").Invoke();
150+
Assert.IsTrue(status == MyEnum.OK);
151+
}
152+
131153
// regression test for https://github.com/pythonnet/pythonnet/issues/1680
132154
[Test]
133155
public void ForEach()

src/runtime/PythonTypes/PyObject.cs

+59-16
Original file line numberDiff line numberDiff line change
@@ -1075,12 +1075,9 @@ public virtual bool Equals(PyObject? other)
10751075
{
10761076
return true;
10771077
}
1078-
int r = Runtime.PyObject_Compare(this, other);
1079-
if (Exceptions.ErrorOccurred())
1080-
{
1081-
throw PythonException.ThrowLastAsClrException();
1082-
}
1083-
return r == 0;
1078+
int result = Runtime.PyObject_RichCompareBool(obj, other.obj, Runtime.Py_EQ);
1079+
if (result < 0) throw PythonException.ThrowLastAsClrException();
1080+
return result != 0;
10841081
}
10851082

10861083

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

1304+
private bool TryCompare(PyObject arg, int op, out object @out)
1305+
{
1306+
int result = Runtime.PyObject_RichCompareBool(this.obj, arg.obj, op);
1307+
@out = result != 0;
1308+
if (result < 0)
1309+
{
1310+
Exceptions.Clear();
1311+
return false;
1312+
}
1313+
return true;
1314+
}
1315+
13071316
public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result)
13081317
{
13091318
using var _ = Py.GIL();
@@ -1352,32 +1361,29 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg
13521361
res = Runtime.PyNumber_InPlaceXor(this.obj, ((PyObject)arg).obj);
13531362
break;
13541363
case ExpressionType.GreaterThan:
1355-
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) > 0;
1356-
return true;
1364+
return this.TryCompare((PyObject)arg, Runtime.Py_GT, out result);
13571365
case ExpressionType.GreaterThanOrEqual:
1358-
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) >= 0;
1359-
return true;
1366+
return this.TryCompare((PyObject)arg, Runtime.Py_GE, out result);
13601367
case ExpressionType.LeftShift:
13611368
res = Runtime.PyNumber_Lshift(this.obj, ((PyObject)arg).obj);
13621369
break;
13631370
case ExpressionType.LeftShiftAssign:
13641371
res = Runtime.PyNumber_InPlaceLshift(this.obj, ((PyObject)arg).obj);
13651372
break;
13661373
case ExpressionType.LessThan:
1367-
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) < 0;
1368-
return true;
1374+
return this.TryCompare((PyObject)arg, Runtime.Py_LT, out result);
13691375
case ExpressionType.LessThanOrEqual:
1370-
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) <= 0;
1371-
return true;
1376+
return this.TryCompare((PyObject)arg, Runtime.Py_LE, out result);
13721377
case ExpressionType.Modulo:
13731378
res = Runtime.PyNumber_Remainder(this.obj, ((PyObject)arg).obj);
13741379
break;
13751380
case ExpressionType.ModuloAssign:
13761381
res = Runtime.PyNumber_InPlaceRemainder(this.obj, ((PyObject)arg).obj);
13771382
break;
13781383
case ExpressionType.NotEqual:
1379-
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) != 0;
1380-
return true;
1384+
return this.TryCompare((PyObject)arg, Runtime.Py_NE, out result);
1385+
case ExpressionType.Equal:
1386+
return this.TryCompare((PyObject)arg, Runtime.Py_EQ, out result);
13811387
case ExpressionType.Or:
13821388
res = Runtime.PyNumber_Or(this.obj, ((PyObject)arg).obj);
13831389
break;
@@ -1402,6 +1408,40 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg
14021408
return true;
14031409
}
14041410

1411+
public static bool operator ==(PyObject? a, PyObject? b)
1412+
{
1413+
if (a is null && b is null)
1414+
{
1415+
return true;
1416+
}
1417+
if (a is null || b is null)
1418+
{
1419+
return false;
1420+
}
1421+
1422+
using var _ = Py.GIL();
1423+
int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_EQ);
1424+
if (result < 0) throw PythonException.ThrowLastAsClrException();
1425+
return result != 0;
1426+
}
1427+
1428+
public static bool operator !=(PyObject? a, PyObject? b)
1429+
{
1430+
if (a is null && b is null)
1431+
{
1432+
return false;
1433+
}
1434+
if (a is null || b is null)
1435+
{
1436+
return true;
1437+
}
1438+
1439+
using var _ = Py.GIL();
1440+
int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_NE);
1441+
if (result < 0) throw PythonException.ThrowLastAsClrException();
1442+
return result != 0;
1443+
}
1444+
14051445
// Workaround for https://bugzilla.xamarin.com/show_bug.cgi?id=41509
14061446
// See https://github.com/pythonnet/pythonnet/pull/219
14071447
internal static object? CheckNone(PyObject pyObj)
@@ -1436,14 +1476,17 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out object?
14361476
case ExpressionType.Not:
14371477
r = Runtime.PyObject_Not(this.obj);
14381478
result = r == 1;
1479+
if (r == -1) Exceptions.Clear();
14391480
return r != -1;
14401481
case ExpressionType.IsFalse:
14411482
r = Runtime.PyObject_IsTrue(this.obj);
14421483
result = r == 0;
1484+
if (r == -1) Exceptions.Clear();
14431485
return r != -1;
14441486
case ExpressionType.IsTrue:
14451487
r = Runtime.PyObject_IsTrue(this.obj);
14461488
result = r == 1;
1489+
if (r == -1) Exceptions.Clear();
14471490
return r != -1;
14481491
case ExpressionType.Decrement:
14491492
case ExpressionType.Increment:

src/runtime/Runtime.cs

-25
Original file line numberDiff line numberDiff line change
@@ -962,31 +962,6 @@ internal static IntPtr PyObject_CallObject(IntPtr pointer, IntPtr args)
962962

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

965-
internal static int PyObject_Compare(BorrowedReference value1, BorrowedReference value2)
966-
{
967-
int res;
968-
res = PyObject_RichCompareBool(value1, value2, Py_LT);
969-
if (-1 == res)
970-
return -1;
971-
else if (1 == res)
972-
return -1;
973-
974-
res = PyObject_RichCompareBool(value1, value2, Py_EQ);
975-
if (-1 == res)
976-
return -1;
977-
else if (1 == res)
978-
return 0;
979-
980-
res = PyObject_RichCompareBool(value1, value2, Py_GT);
981-
if (-1 == res)
982-
return -1;
983-
else if (1 == res)
984-
return 1;
985-
986-
Exceptions.SetError(Exceptions.SystemError, "Error comparing objects");
987-
return -1;
988-
}
989-
990965

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

0 commit comments

Comments
 (0)