Search code examples
pythonpython-3.xgetattr

Odd getattr() behaviour with class property in the default


Here I expected that all 4 IDs will be the same, however it seems that when I pass class property to the default attribute of the getattr, it simply skips the attribute lookup and always return the default:

Python 3.6.5 (default, May 11 2018, 04:00:52) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from datetime import datetime
   ...: 
   ...: class my_cls(object):
   ...:     @property
   ...:     def _id(self):
   ...:         self._cid = datetime.now().isoformat()
   ...:         return self._cid
   ...: 
   ...:     def get_id(self):
   ...:         return getattr(self, '_cid', self._id)
   ...: 
   ...: cls = my_cls()
   ...: print('Init ID:', cls._id)
   ...: print('Still OK:', getattr(cls, '_cid', False))
   ...: print('WRONG:', getattr(cls, '_cid', cls._id))
   ...: print('WRONG:', cls.get_id())
   ...: 
   ...: 
Init ID: 2018-06-17T12:45:20.409601
Still OK: 2018-06-17T12:45:20.409601
WRONG: 2018-06-17T12:45:20.409804
WRONG: 2018-06-17T12:45:20.409849

Is that expected and/or documented behaviour? Why is getattr doing what it's doing?


Solution

  • It's not getattr, it's your code accessing cls._id before calling getattr.

    In Python, before a function is called, all its arguments are evaluated. In particular, in getattr(cls, '_cid', cls._id) subexpression cls._id is evaluated. In order to do so, _id property is accessed, which updates _cid. getattr is called afterwards and returns the updated value.