11

The example below is from a REST database driver on Python 2.7.

In the __setattr__ method below, if I use the commented out getattr() line, it reduces the object instantiation performance from 600 rps to 230.

Why is getattr() so much slower than self.__dict__.get() in this case?

class Element(object):

    def __init__(self, client):
        self._client = client
        self._data = {}
        self._initialized = True

    def __setattr__(self, key, value):
        #_initialized = getattr(self, "_initialized", False)
        _initialized = self.__dict__.get("_initialized", False)
        if key in self.__dict__ or _initialized is False:
            # set the attribute normally
            object.__setattr__(self, key, value)
        else:
            # set the attribute as a data property
            self._data[key] = value
espeed
  • 4,754
  • 2
  • 39
  • 51
  • 2
    On a side note, if you are having such a performance difference, do cache `self.__dict__` in a local variable - even for only two access to it. (`dict_ = self.__dict__` at the start of `__setattr__`) – jsbueno Mar 20 '12 at 17:45

1 Answers1

13

In short: because getattr(foo,bar) does the same thing as foo.bar, which is not the same thing as just accessing the __dict__ property (for a start, getattr has to select the right __dict__, but there's a whole lot more going on).

An example for illustration:

>>> class A:
...   a = 1
...
>>> class B(A):
...   b = 2
...
>>> dir(B)
['__doc__', '__module__', 'a', 'b']
>>> B.a
1
>>> B.__dict__
{'__module__': '__main__', 'b': 2, '__doc__': None}
>>> B.__dict__['a']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'a'
>>> B.__dict__.get('a')
>>>

Details contained in, or linked to here: http://docs.python.org/reference/datamodel.html (search for "getattr").

Yushin Washio
  • 675
  • 8
  • 12
Marcin
  • 48,559
  • 18
  • 128
  • 201
  • 2
    @bereal: you need to re-read things. The necessary operations for foo.__dict__.get() are a proper subset of getattr(). – bukzor Mar 20 '12 at 17:12
  • 2
    @bereal Bear in mind that python bytecodes do not correspond to primitive operations. You can have a very small amount of bytecode to call a very complex function, while one simple expression can generate a lot more bytecode. Also, what bukzor said is correct and relevant. – Marcin Mar 20 '12 at 17:17
  • @bereal In no way does this disprove that `foo.__dict__` does the same as `getattr(foo, '__dict__')`, because it is possible for calling `getattr(foo,'__dict__')` to be more efficient than dereferencing an arbitrary attribute. – Marcin Mar 20 '12 at 17:35