Skip to content

Commit 70e67f1

Browse files
committed
[Python] Deprecate TObject __eq__ pythonization
The TObject `__eq__` pythonization calls `TObject::Equals()`, which by default does a pointer comparison. That can be very confusing for Python users.
1 parent d51f7e9 commit 70e67f1

File tree

4 files changed

+37
-10
lines changed

4 files changed

+37
-10
lines changed

README/ReleaseNotes/v638/index.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ If you want to keep using `TList*` return values, you can write a small adapter
144144

145145
ROOT dropped support for Python 3.8, meaning ROOT now requires at least Python 3.9.
146146

147+
### Deprecation of the `TObject` equality pythonization
148+
149+
`TObject.__eq__` is deprecated and will be removed in ROOT 6.40.
150+
151+
It forwards to `TObject::Equals()`, which uses pointer comparison if not overridden in derived classes.
152+
This may be confusing, because people expect value comparisons.
153+
Use Pythons `is` for pointer comparison, or request an implementation of `operator==` on the C++ side if you need value-based equality checks for a given class.
154+
147155
### Deprecate the attribute pythonization of `TDirectory` in favor of item-getting syntax
148156

149157
Since ROOT 6.32, the recommended way to get objects from a `TFile` or any `TDirectory` in general is via `__getitem__`:

bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def _TFileOpen(klass, *args):
9090
# klass: TFile class
9191
# *args: arguments passed to the constructor
9292
f = klass._OriginalOpen(*args)
93-
if f == ROOT.bind_object(0, klass):
93+
if not f:
9494
# args[0] can be either a string or a TFileOpenHandle
9595
raise OSError('Failed to open file {}'.format(str(args[0])))
9696
return f

bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tobject.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
# Searching
1515

16+
1617
def _contains(self, o):
1718
# Relies on TObject::FindObject
1819
# Parameters:
@@ -22,26 +23,47 @@ def _contains(self, o):
2223
# - True if self contains o
2324
return bool(self.FindObject(o))
2425

26+
2527
# Comparison operators
2628

29+
30+
def _eq(self, o):
31+
import warnings
32+
33+
warnings.warn(
34+
"\nTObject.__eq__ is deprecated and will be removed in ROOT 6.40."
35+
"\n\nIt forwards to TObject::Equals(), which uses pointer comparison if"
36+
" not overridden in derived classes."
37+
"\nThis may be confusing, because people expect value comparisons."
38+
"\nUse Pythons `is` for pointer comparison, or request/implement"
39+
" `operator==` on the C++ side if you need value-based equality checks.",
40+
FutureWarning,
41+
stacklevel=2,
42+
)
43+
return self._cpp_eq(o)
44+
45+
2746
def _lt(self, o):
2847
if isinstance(o, cppyy.gbl.TObject):
2948
return self.Compare(o) == -1
3049
else:
3150
return NotImplemented
3251

52+
3353
def _le(self, o):
3454
if isinstance(o, cppyy.gbl.TObject):
3555
return self.Compare(o) <= 0
3656
else:
3757
return NotImplemented
3858

59+
3960
def _gt(self, o):
4061
if isinstance(o, cppyy.gbl.TObject):
4162
return self.Compare(o) == 1
4263
else:
4364
return NotImplemented
4465

66+
4567
def _ge(self, o):
4668
if isinstance(o, cppyy.gbl.TObject):
4769
return self.Compare(o) >= 0
@@ -57,11 +79,14 @@ def pythonize_tobject():
5779

5880
# Inject comparison operators
5981
AddTObjectEqNePyz(klass)
82+
klass._cpp_eq = klass.__eq__
83+
klass.__eq__ = _eq
6084
klass.__lt__ = _lt
6185
klass.__le__ = _le
6286
klass.__gt__ = _gt
6387
klass.__ge__ = _ge
6488

89+
6590
# Instant pythonization (executed at `import ROOT` time), no need of a
6691
# decorator. This is a core class that is instantiated before cppyy's
6792
# pythonization machinery is in place.

main/python/cmdLineUtils.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,9 @@ def isDirectoryKey(key):
195195
"""
196196
Return True if the object, corresponding to the key, inherits from TDirectory
197197
"""
198-
import cppyy
199-
200198
classname = key.GetClassName()
201199
cl = ROOT.gROOT.GetClass(classname)
202-
if cl == cppyy.nullptr:
200+
if not cl:
203201
logging.warning("Unknown class to ROOT: " + classname)
204202
return False
205203
return cl.InheritsFrom(ROOT.TDirectory.Class())
@@ -209,11 +207,9 @@ def isTreeKey(key):
209207
"""
210208
Return True if the object, corresponding to the key, inherits from TTree
211209
"""
212-
import cppyy
213-
214210
classname = key.GetClassName()
215211
cl = ROOT.gROOT.GetClass(classname)
216-
if cl == cppyy.nullptr:
212+
if not cl:
217213
logging.warning("Unknown class to ROOT: " + classname)
218214
return False
219215
return cl.InheritsFrom(ROOT.TTree.Class())
@@ -223,11 +219,9 @@ def isTHnSparseKey(key):
223219
"""
224220
Return True if the object, corresponding to the key, inherits from THnSparse
225221
"""
226-
import cppyy
227-
228222
classname = key.GetClassName()
229223
cl = ROOT.gROOT.GetClass(classname)
230-
if cl == cppyy.nullptr:
224+
if not cl:
231225
logging.warning("Unknown class to ROOT: " + classname)
232226
return False
233227
return cl.InheritsFrom(ROOT.THnSparse.Class())

0 commit comments

Comments
 (0)