FindObject returns a null pointer TObject?

Hi,

I just noticed that the current HEAD version of ROOT (which tells me 5.23/05) seems to return a null pointer to a TObject in case FindObject cannot find the requested object.

Python 2.5.1 (r251:54863, Feb  6 2009, 19:02:12) 
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import ROOT
>>> a = ROOT.TGraph()
a>>> a.FindObject("a")
<ROOT.TObject object at 0x0>
>>> a.FindObject("a") is None
False

Root version 5.22/00 gives me instead:

Python 2.5.1 (r251:54863, Feb  6 2009, 19:02:12) 
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import ROOT
>>> a = ROOT.TGraph()
>>> a.FindObject("a")
>>> a.FindObject("a") is None
True

The above is on an Intel MacBook running OS X 10.5.7.

I donā€™t know at what time this change occurred but itā€™s a bit nasty since all checks for `is Noneā€™ now fail their purpose.

What/where did this change? And was this on purpose or could this be reverted?

Thanks for the help.

Cheers,
Jeroen

2 Likes

Jeroen,

sorry, this is on purpose. :slight_smile: The reason is that NULL results being typed pointers allows proper subsequent overloading if the result of the function is used in another C++ function (w/o checking for None or NULL).

But Iā€™d say that the right check was never to do ā€˜is Noneā€™, but ā€˜== Noneā€™, or simply the use of ā€˜notā€™. The two latter checks will give the right result for all versions. Any specific reason why you need to use ā€˜is Noneā€™?

Cheers,
Wim

Thanks for explaining the idea behind it Wim. When you put it like that it makes sense. But about the ā€˜is Noneā€™ but: I thought this was good practice. Reading the Python style guide (PEP 8, python.org/dev/peps/pep-0008/) it says:

- Comparisons to singletons like None should always be done with
  'is' or 'is not', never the equality operators.

  Also, beware of writing "if x" when you really mean "if x is not None"
  -- e.g. when testing whether a variable or argument that defaults to
  None was set to some other value.  The other value might have a type
  (such as a container) that could be false in a boolean context!

But, Iā€™m still learning Python (by Google and example) so if you want to shed some more light on this, Iā€™m all ears.

I assume in the case of the null pointer there is something that guarantees that this is considered False, right? (Iā€™m curious how this works internally, actually.)

Cheers,
Jeroen

Jeroen,

given the context, I say that there are two reasons for using ā€˜is Noneā€™ as is presented in that PEP: efficiency, and the ā€˜when you really mean ā€œif x is not Noneā€ā€™. In the ROOT comparison code, you donā€™t really mean ā€˜is Noneā€™. :slight_smile: After all, the return is a TObject*, which is never the singleton None, even if it is 0.

What I have to deal with is inconsistencies between C++ and python, and then get something that has a natural feel to it, while minimizing surprises. This approach is completely open to discussion. Below is my recollection of history and decisions. Feel free to add ideas.

The specific problem for None, is that in python it is both the not-object (i.e. C++ NULL) as well as nothing (i.e. C++ void). In staying strict with C++, I originally preferred to distinguish between (long)0 and None, when (void*)0 was the C++ result. However, that turned out to be awkward for users, who did not want to notice the difference (isnā€™t always possible, though). But so, it all became None.

Then, there is the following. If the result of a C++ function is a pointer, and it is compared against something, pointer-comparison is used in C++. However, using pointer comparison in python is awkward, because everything is a pointer. Hence, object comparison is always used, even when comparing a TObject** to a TObject*. That then, however, leads to differences in which comparison was selected, depending on the type of the object returned, because 1) PyROOT does automatic down-casting of pointers to the most derived known type, 2) python will select the comparison operator of the most object-like object, and 3) users can provide any kind of operator==() behavior they like.

Compare this, old root (and note that TFile::Get() returns a TObject*, just like FindObject() does; the comparison problem is with the right-hand TLorentzVector if None is returned):

[code]>>> from ROOT import *

f = TFile(ā€˜aap.rootā€™, ā€˜recreateā€™ )
a = TLorentzVector( 1, 2, 3, 4 )
a.Write()
156
del a
f.Get(ā€˜TLorentzVectorā€™) == TLorentzVector( 1, 2, 3, 4 )
1
f.Get(ā€˜TSpellErrorLorentzVectorā€™) == TLorentzVector( 1, 2, 3, 4 )
Traceback (most recent call last):
File ā€œā€, line 1, in
TypeError: Bool_t TLorentzVector::operator==(const TLorentzVector& q) =>
could not convert argument 1
f.Get(ā€˜TLorentzVectorā€™) == None
Traceback (most recent call last):
File ā€œā€, line 1, in
TypeError: Bool_t TLorentzVector::operator==(const TLorentzVector& q) =>
could not convert argument 1
f.Get(ā€˜TSpellErrorLorentzVectorā€™) == None
True
[/code]versus new root:>>> from ROOT import * f = TFile('aap.root', 'recreate' ) a = TLorentzVector( 1, 2, 3, 4 ) a.Write() 156 del a f.Get('TLorentzVector') == TLorentzVector( 1, 2, 3, 4 ) 1 f.Get('TSpellErrorLorentzVector') == TLorentzVector( 1, 2, 3, 4 ) False f.Get('TLorentzVector') == None False f.Get('TSpellErrorLorentzVector') == None True Iow., with a typed pointer return, the usage of comparison can remain consistent, even with a user-provided operator==(), and even if that operator is overloaded on multiple types.

Internally, the objects use the python C-API ā€œrich compareā€ interface, so all that has to be done is special-case None and map operator==() and friends to richcompare as appropriate.

Cheers,
Wim

2 Likes

Wow, okay. I agree the new approach looks much more robust and thought-out. I will have to rethink a bit my understanding of what exactly I need to ask to see if an object is valid or not.

Thanks for explaining!

Cheers,
Jeroen