Python getattr() function in newer ROOT versions

I need to find a replacement for the last line (check if the object is iterable / a dict).
It seems the “if” misbehaves in newer ROOT versions (it prints “???”).

import ROOT
o = ROOT.TH1F("h", "h", 100, 0., 1.)
if getattr(o, '__iter__', False): print("???")

Dear @Wile_E_Coyote ,

The snippet you present is not really misbehaving: the histogram can be iterated over the bins, see the following example.

import ROOT
import numpy as np

# Create a histogram
h = ROOT.TH1D("h", "h", 10, -10, 10)
# Create sample data
data = np.random.normal(0, 2, 10000)
# Fill histogram with data
h.Fill(data)
# Reduce bin content to make the following print statements more evident
h *= 0.01
for bin in h:
    print("*" * int(bin))

So indeed the __iter__ method is well defined. Note that this functionality is currently very rudimentary, we have plans to enhance this interoperability of histograms with common Python patterns via the GitHub - scikit-hep/uhi: Universal Histogram Interface

Cheers,
Vincenzo

1 Like

How can I get the old behavior?

Dear @Wile_E_Coyote ,

I am not sure I understand. If you mean “how can I have a ROOT histogram object that does not define the __iter__ method”, then I believe this is not possible, it’s just how the class functionality works. If the question is “how can I get a falsey value when I’m trying to access the __iter__ method of a ROOT histogram class?”, then I would approach it differently and would have a series of classes that you want to return a falsey value for in that if branch. Something like

if isinstance(o, myclass): # do stuff
if getattr(o, "__iter__"): # do stuff

Can be generalised by having a Python dictionary where the key is the class type itself.

Let me know if I missed something from your question.

Cheers,
Vincenzo

No, the actual code (which recursively processes what it gets as an input) breaks because the new ROOT says histograms (and probably some other objects now) have an “__iter__” (it then immediately dies because it tries to call “items()” on them, to process them recursively).
This problem seems to have been introduced in ROOT 6.32 (older versions behave well).

Dear @Wile_E_Coyote ,

__dict__ is even more common than __iter__. Almost every Python object has a dict (by default).

>>> class MyClass: ...
... 
>>> a = MyClass()
>>> a.__dict__
{}

ROOT objects do not escape this rule. In fact even on older ROOT versions e.g. 6.28 you will see that ROOT Python objects have this method, following Python standards:

[vpadulan@lxplus932 ~]$source /cvmfs/sft.cern.ch/lcg/views/LCG_104/x86_64-el9-gcc13-opt/setup.sh
[vpadulan@lxplus932 ~]$python
Python 3.9.12 (main, Jul 11 2023, 14:40:15) 
[GCC 13.1.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ROOT
>>> ROOT.__version__
'6.28/04'
>>> h = ROOT.TH1D("h", "h", 10, 0, 10)
>>> h.__dict__
{}

I misspelled it (see my previous post).

Dear @Wile_E_Coyote ,

ROOT 6.32 has seen a major update of the underlying machinery of our dynamic python bindings (cppyy). Namely, ROOT now is up-to-date with the latest functionalities of cppyy. This is by and large a huge upgrade of functionality. Personally, I believe the nature of Python objects is so dynamic that I find it a bit fragile to rely on the presence of __iter__ for any particular class, ROOT or not (see also this SO answer). From the little information provided so far, I would still suggest you to apply a separate logic for ROOT histograms, if you need to treat them differently, as I showed in a previous message. If you want you can share more information on your use case and maybe we can find an even nicer solution.

I will also invite @danilo or @jonas to share their thought on this.

Cheers,
Vincenzo

I think I need a robust test to determine whether the object “o” is a genuine “dict” and not some strange ROOT object that pretends to be one.

I could try to use the following:

if hasattr(o, '__iter__') and hasattr(o, 'items'):

But … can you guarantee that you will NOT add the “items()” method to histograms (and possibly other relevant objects, e.g. graphs and so on)?

Dear @Wile_E_Coyote ,

No, there is no such guarantee, as there is no guarantee that any specific class will not get improved/extended in the future, I believe this is true for any software package. Of course, removing methods from a class would be a different story. If you really just need to call the items method on a certain class, you can just call hasattr(o, "items"). Again, if ROOT histograms specifically need special treatment, then they should be treated separately.

Cheers,
Vincenzo