<div dir="ltr"><div>This is related to a bug I found in vtk/numpy_support/dataset_adapter.py.</div><div><br></div><div>According to the Python docs (both 2 and 3), hasattr() is implemented by calling getattr() and returning False if an exception occurs.  Note that it doesn't say "if an AttributeError exception", it simply says "if an exception".</div><div><br></div><a href="https://docs.python.org/2/library/functions.html#hasattr">https://docs.python.org/2/library/functions.html#hasattr</a><div><br></div><div>This leads to some unexpected behavior in this method from dataset_adaptor.py:</div><div><br></div><div><div>    def __getattr__(self, name):</div><div>        "Forwards unknown attribute requests to VTK array."</div><div>        if not hasattr(self, "VTKObject") or not self.VTKObject:</div><div>            raise AttributeError("class has no attribute %s" % name)</div><div>        return getattr(self.VTKObject, name)</div></div><div><br></div><div>If __getattr__() calls hasattr() on an attribute that isn't there, then infinite recursion results.  In Python, infinite recursion results in a RuntimeError exception.  But hasattr() by definition should catch any exception.  This is where things get hairy, and where Python 2 and Python 3 are apparently different.</div><div><br></div><div>In Python 2, hasattr() silently catches the infinite recursion as a RuntimeError.  Therefore, the above __getattr__() function behaves as expected (but it wastes a ton of CPU cycles while python exhausts its stack).</div><div><br></div><div>In Python 3, hasattr() does not catch the infinite recursion, and the RuntimeError becomes visible to the user.</div><div><br></div><div>To make a long story short, using hasattr() in __getattr__() is very, very bad because the only way it could possibly return "False" is by triggering infinite recursion, and the consequences of the infinite recursion are different in Python 2 (where the RuntimeError is silenced by hasattr) versus Python 3 (where the RuntimeError is exposed).</div><div><br></div><div>If anyone is curious, try running the following program on Python 2 and Python 3 to see the difference in behavior:</div><div><br></div><div>=======</div><div> class test_hasattr:</div><div>    def __init__(self):</div><div>        self.bar = "hi"</div><div>    def __getattr__(self, attr):</div><div>        print("calling hasattr")</div><div>        if hasattr(self, "baz"):</div><div>            return "hasattr"</div><div>        raise AttributeError</div><div><br></div><div>if __name__ == "__main__":</div><div>    c = test_hasattr()</div><div>    try:</div><div>        c.wombat</div><div>    except AttributeError:</div><div>        pass</div><div>    print("passed!")</div><div>=======</div><div><br></div><div> - David</div><div><br></div><div><br></div><div><br><div><br></div><div><br></div></div></div>