Python | Why is accessing instance attribute slower than local?

You've run into scoping issue which is pretty detailed-ly explained here

Although scopes are determined statically, they are used dynamically. At any time during execution, there are at least three nested scopes whose namespaces are directly accessible:

  • the innermost scope, which is searched first, contains the local names
  • the scopes of any enclosing functions, which are searched starting with the nearest enclosing scope, contains non-local, but also non-global names
  • the next-to-last scope contains the current module’s global names
  • the outermost scope (searched last) is the namespace containing built-in names

So accessing local variables is 1 lookup less than instance variable and stacked against so many repetitions it works slower.

This question is also a possible duplicate of this one


Because local variables are simply accessed using a single byte code step LOAD_FAST, on the other hand self.x will require first looking up for self using LOAD_FAST and then access x on it, that too is complicated as Python has to first check whether it is data descriptor or just simple instance attribute and based on that then its value is fetched.

Usually it is good idea to cache such heavily repeated calls when dealing with methods in CPython, because otherwise every time a new bound object is created. I've hardly seen a case where normal attribute was cached to get some performance benefits. Other implementations like PyPy and Pyston have their own way of speeding up attribute lookups. From data model page:

Note that the transformation from function object to (unbound or bound) method object happens each time the attribute is retrieved from the class or instance. In some cases, a fruitful optimization is to assign the attribute to a local variable and call that local variable.

One example for this would be list.append(Also see: https://hg.python.org/cpython/file/f7fd2776e80d/Lib/heapq.py#l372_), for example if you're populating a list with huge number of items and cannot use a list-comprehension for some reason then caching list.append provides slight speedup:

>>> %%timeit
lst = []                  
for _ in xrange(10**6):
    lst.append(_)
... 
10 loops, best of 3: 47 ms per loop
>>> %%timeit
lst = [];append=lst.append
for _ in xrange(10**6):
    append(_)
... 
10 loops, best of 3: 31.3 ms per loop

Python 3.7

Python 3.7 will have two new byte codes to speed up method loading and calling.

Added two new opcodes: LOAD_METHOD and CALL_METHOD to avoid instantiation of bound method objects for method calls, which results in method calls being faster up to 20%. (Contributed by Yury Selivanov and INADA Naoki in bpo-26110.)


Every time python looks up a variable, you pay a little (LOAD_FAST op code). Every time you look up an attribute on an existing object, you pay a little more (LOAD_ATTR op code). e.g.

>>> def f1(self):
...   x = self.x
...   x
... 
>>> def f2(self):
...   self.x
...   self.x
... 
>>> dis.dis(f1)
  2           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (x)
              6 STORE_FAST               1 (x)

  3           9 LOAD_FAST                1 (x)
             12 POP_TOP             
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE        
>>> dis.dis(f2)
  2           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (x)
              6 POP_TOP             

  3           7 LOAD_FAST                0 (self)
             10 LOAD_ATTR                0 (x)
             13 POP_TOP             
             14 LOAD_CONST               0 (None)
             17 RETURN_VALUE        
>>> 

Even if you don't know how to read python disassembled bytecode, you can see that there is more stuff being done for f2 than for f1.

Also, note that not all op codes are the same. LOAD_FAST is basically a array lookup in the local scope (so it is FAST as the name implies). LOAD_ATTR is (on the other hand) a bit slower as it translates to a function call (__getattribute__) that (usually) does a dictionary lookup.


As far as what is "best practice", do what reads the easiest. I think that it's pretty customary to use self unless you demonstrate that there is a noticeable performance gain by avoiding it, but I don't think that is a hard rule.