[vtk-developers] Problem with Python "hasattr"

David Gobbi david.gobbi at gmail.com
Sun Aug 9 11:41:25 EDT 2015


This is related to a bug I found in vtk/numpy_support/dataset_adapter.py.

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".

https://docs.python.org/2/library/functions.html#hasattr

This leads to some unexpected behavior in this method from
dataset_adaptor.py:

    def __getattr__(self, name):
        "Forwards unknown attribute requests to VTK array."
        if not hasattr(self, "VTKObject") or not self.VTKObject:
            raise AttributeError("class has no attribute %s" % name)
        return getattr(self.VTKObject, name)

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.

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).

In Python 3, hasattr() does not catch the infinite recursion, and the
RuntimeError becomes visible to the user.

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).

If anyone is curious, try running the following program on Python 2 and
Python 3 to see the difference in behavior:

=======
 class test_hasattr:
    def __init__(self):
        self.bar = "hi"
    def __getattr__(self, attr):
        print("calling hasattr")
        if hasattr(self, "baz"):
            return "hasattr"
        raise AttributeError

if __name__ == "__main__":
    c = test_hasattr()
    try:
        c.wombat
    except AttributeError:
        pass
    print("passed!")
=======

 - David
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://public.kitware.com/pipermail/vtk-developers/attachments/20150809/0d206d36/attachment.html>


More information about the vtk-developers mailing list