Confusing error message when using @property and getattr

I reproduce here a confusing error I experienced recently with Python 3.

Below is defined a simple class, called MyClass, equipped with an accessor att(self) of the attribute att and a method val(self), that simply returns the real part of att using its accessor. I also defined getattr to look for nonexistent attributes of MyClass in self._att.

Example 1: val is a method
import warnings

class MyClass(object):
def __init__(self):
self._att = 1

def __getattr__(self, item):
warnings.warn('look for {} in self._att'.format(item))
return getattr(self._att, item)

def att(self):
return self._att

def val(self):
return self.att.real

obj = MyClass()
print(obj.val())


Unfortunately, I wrote this script late at night and forgot the parentheses of the accessor att(self) in the definition of val(self), leading to this intelligible error.

Output
Traceback (most recent call last):
File "prop_get.py", line 20, in <module>
print(obj.val())
File "prop_get.py", line 16, in val
return self.att.real
AttributeError: 'function' object has no attribute 'real'


Later, I decided to turn the method val(self) into a property with @property, which makes sense because the method in question does not alter my object.

Example 2: val is a property
import warnings

class MyClass(object):
def __init__(self):
self._att = 1

def __getattr__(self, item):
warnings.warn('look for {} in self._att'.format(item))
return getattr(self._att, item)

def att(self):
return self._att

@property
def val(self):
return self.att.real

obj = MyClass()
print(obj.val)


While I expected to get exactly the same alert, it appeared that Python raised a very confusing and pointless error.

Output
prop_get.py:9: UserWarning: look for val in self._att
warnings.warn('look for {} in self._att'.format(item))
Traceback (most recent call last):
File "prop_get.py", line 21, in <module>
print(obj.val)
File "prop_get.py", line 10, in __getattr__
return getattr(self._att, item)
AttributeError: 'int' object has no attribute 'val'


Since Python did not found real as an attribute of the method att(self) and because of the property decorator, it moved up a rung and looked for the property val in self, falling into getattr and then producing the error we can read.

The confusing point is that the error is not actually on the attribute val being nonexistent but on missing parentheses when calling the accessor att(self).

As we can see on this last example, this is definitely a problem occurring when using simultaneously both @property and getattr.

Example 3: no getattr
import warnings

class MyClass(object):
def __init__(self):
self._att = 1

def att(self):
return self._att

def val(self):
return self.att.real

obj = MyClass()
print(obj.val())

Output
Traceback (most recent call last):
File "prop_get.py", line 16, in <module>
print(obj.val())
File "prop_get.py", line 12, in val
return self.att.real
AttributeError: 'function' object has no attribute 'real'


Obviously, the solution is to return self.att().real in val(self).