- Notifications
You must be signed in to change notification settings - Fork748
Support comparison operators#1347
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Support comparison operators#1347
Uh oh!
There was an error while loading.Please reload this page.
Conversation
6196140
to3c4ea26
Comparesrc/runtime/classbase.cs Outdated
@@ -21,6 +21,7 @@ internal class ClassBase : ManagedType | |||
[NonSerialized] | |||
internal List<string> dotNetMembers; | |||
internal Indexer indexer; | |||
internal Hashtable richcompare; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Why not have it asDictionary<int, MethodObject>
, mapping directly from{Py_EQ, ...}
to corresponding operator implementation?
src/runtime/classbase.cs Outdated
// otherwise fallback to checking if an IComparable interface is handled. | ||
if (PyToCilOpMap.ContainsKey(op)) { | ||
string CilOp = PyToCilOpMap[op]; | ||
if (cls.richcompare.Contains(CilOp)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Generally, you'd docls.richcompare.TryGetValue(op, out var methodObject)
instead of having separateContainsKey
check.
src/runtime/classbase.cs Outdated
var methodObject = (MethodObject)cls.richcompare[CilOp]; | ||
IntPtr args = other; | ||
var free = false; | ||
if (!Runtime.PyTuple_Check(other)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I don't think the code inside thisif
should ever be skipped. How woulda > (1,2)
work ifa
was a .NET object with>
operator? Because(1,2)
is a tuple, they'd be passed toInvoke
as two args instead of 1.
Consequently,free
will alwasy betrue
and not needed.
Probably a good idea to add a test. I believe you'd need anoperator >(SomeClass a, PyObject b)
defined and to checkb
indeed receives the tuple(1,2)
.
christabellaJan 8, 2021 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Indeed ifb
were a tuple thisif
would be skipped. Once removing theif
, the operator method does receiveb
asPyObject
, but I get anAccessViolationException : Attempted to read or write protected memory.
so I thinkb
is wrongly parsed? I suppose a tuple when converted into a PyObject should probably not look like this:
christabellaJan 8, 2021 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I think that error message might have been a flaky bug, because now the variable states are the same but the error is:
Message: Python.Runtime.PythonException : TypeError : '>=' not supported between instances of 'int' and 'tuple'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This is because to work withPyObject
instances in C# callbacks you must hold GIL. E.g. the method body must be insideusing (Py.GIL()) { ...
block. It applies to debugging too.
christabellaJan 11, 2021 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Thanks, I didn't know about that. I put theoperator >(Obj a, PyObject b)
body in aPy.GIL
block and can now see thatb
has a value of{(1, 2)}
I added 2/6 of the tuple comparison tests but it's done in a bit of a strange way. In the test I'm assuming that the PyObject is a tuple; is that okay?
(Below is resolved)
However, the same error message persists, and when I try to debugpythonnet/src/runtime/methodbinder.cs
Line 847 ind6c0081
result=binding.info.Invoke(binding.inst,BindingFlags.Default,null,binding.args,null); Maybe I'm missing something simple again, should the GIL block should also be wrapping that statement? But that would make the change bigger than I thought. Are tuples commonly used enough for comparison operators, or can I put the tuple test in a different PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I don't think you need to add a test with a tuple for each comparison operator. One is enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The failure on the screenshot is only happening because you have debugger attached. It tries to callToString
on somePyObject
to show in Watches window, but it does not work withoutPy.GIL
src/runtime/operatormethod.cs Outdated
["op_Equality"] = new SlotDefinition("__eq__", TypeOffset.tp_richcompare), | ||
["op_Inequality"] = new SlotDefinition("__ne__", TypeOffset.tp_richcompare), | ||
["op_LessThanOrEqual"] = new SlotDefinition("__le__", TypeOffset.tp_richcompare), | ||
["op_GreaterThanOrEqual"] = new SlotDefinition("__ge__", TypeOffset.tp_richcompare), | ||
["op_LessThan"] = new SlotDefinition("__lt__", TypeOffset.tp_richcompare), | ||
["op_GreaterThan"] = new SlotDefinition("__gt__", TypeOffset.tp_richcompare), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I think this is error-prone.op_LessThan
does not really implementtp_richcompare
. It would be better to have a separateComparisonOpMap
,string -> string
, and fixIsOperatorMethod
correspondingly.
It would also remove the need to changeFixupSlots
below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Makes sense, thanks! Should thatComparisonOpMap
also be merged with the other map forop_LessThan
->Py_LT
?
christabellaJan 8, 2021 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I added thisOperatorMethod.ComparisonOpMap
in the latest commit, without merging it with theClassBase.CilToPyOpMap
, although they both have the same keys so perhaps they should be together inOperatorMethod
.
src/runtime/operatormethod.cs Outdated
@@ -86,7 +99,7 @@ public static void FixupSlots(IntPtr pyType, Type clrType) | |||
Debug.Assert(_opType != null); | |||
foreach (var method in clrType.GetMethods(flags)) | |||
{ | |||
if (!IsOperatorMethod(method)) | |||
if (!IsOperatorMethod(method) || IsComparisonOp(method)) // We don't want to override ClassBase.tp_richcompare. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Direct comment to the reason why we don't want to override, otherwise it does not explain anything and simply says what code in this method does (which is generally not very useful).
Something like "comparison operators are handled byClassBase.tp_richcompare
"
src/embed_tests/TestOperator.cs Outdated
@@ -25,6 +25,17 @@ public class OperableObject | |||
{ | |||
public int Num { get; set; } | |||
public override int GetHashCode() | |||
{ | |||
return 159832395 + Num.GetHashCode(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
unchecked(159832395 + Num.GetHashCode())
Uh oh!
There was an error while loading.Please reload this page.
src/runtime/classbase.cs Outdated
if (true) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Please, remove unnecessaryif
andfree
boolean (alwaystrue
)
src/runtime/operatormethod.cs Outdated
if (!IsOperatorMethod(method)) | ||
// We don't want to override slots for either non-operators or | ||
// comparison operators, which are handled by ClassBase.tp_richcompare. | ||
if (!IsOperatorMethod(method) || IsComparisonOp(method)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
NIT:!OpMethodMap .ContainsKey(method)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Of course, thanks!
a9e28ba
to7a2b5e2
Compare
Uh oh!
There was an error while loading.Please reload this page.
What does this implement/fix? Explain your changes.
Continuation ofhttps://github.com/pythonnet/pythonnet/pull/1324/files but specifically for comparison operators e.g. >, >=, <, == etc. based on a discussion with@tminka --- in a nutshell, we want to check each C# class for any comparison
operator
methods when callingClassBase.tp_richcompare
, before proceeding with the usual logic (see#294) which handles C# classes that implement anIComparable
interface.Would appreciate a review from@lostmsu who helped with the previous PR that this builds on. Thanks!
Does this close any currently open issues?
Closes#1312
More concrete examples can also be found in other Infer.NET tutorials e.g. having to use
op_GreaterThan
https://github.com/dotnet/infer/blob/67b4f80d97018460bcb817f76ec874d0f33f1651/test/TestPython/test_tutorials.py#L31Any other comments?
Some remaining tasks could be:
operator ==()
, the class should also define .Equals and .GetHashCode()https://stackoverflow.com/questions/10790370/whats-wrong-with-defining-operator-but-not-defining-equals-or-gethashcodeChecklist
Check all those that are applicable and complete.
AUTHORS
CHANGELOG
The tests pass in net472 but not on netcoreapp3.1