Search code examples
pythonpython-2.7typeerrorsetattr

Why does an object with redefined __getattr__() throws TypeError?


Here is the code

class MyTest: 
    def __init__(self):
         pass

    def __getattr__(self, attr):
        pass
t = MyTest()
print 'my test object: %r' %t

So print triggers a TypeError: 'NoneType' object is not callable while i only want to see if object exists. Granted this code isn't very useful. But i've had a stub class like that in a big code base so i did

if module and module.class and module.class.propery:
   # do something with that property
 ...

and got a Type Error: 'NoneType' object is not callable but the line doesn't call anything! I guess python is calling some functions implicitly behind the scenes.

Curiously this doesn't happen if the class inherits from Object

What's going on?


Solution

  • In an old-style class, __getattr__ is used for a greater variety of attribute access, including magic methods. The % operator is trying to call t.__repr__() in order to fill in the %r placeholder, but t.__repr__ is evaluated by t.__getattr__('__repr__'), which returns None.

    In the if case, a different magic method is invoked, but the same problem occurs.

    >>> class Foo:
    ...   def __getattr__(self, attr):
    ...     print(attr)
    ...
    >>> f = Foo():
    >>> if f:
    ...   pass
    __nonzero__
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'NoneType' object is not callable
    

    Use a new-style class, __getattr__ is only invoked if an attribute cannot be found via the normal method (checking the __dict__ attribute of the instance or of any of the class's in the instance's MRO).

    >>> class Foo(object):
    ...     def __init__(self):
    ...         self.x = 3
    ...     def __getattr__(self, attr):
    ...         print(attr)
    ...
    >>> f = Foo()
    >>> if f:
    ...   pass
    ...
    >>> f.x
    3
    >>> f.y
    y
    

    In the if f case, f itself doesn't implement __nonzero__ or __len__, and neither does its parent object, but in that case, no attribute is used; the fact that f is, in fact, an object is used. In f.x, x is found in the instance's attribute dict, so its value is returned directly. Only y, which isn't otherwise defined by f, Foo, or object, invokes a call to __getattr__.