Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it a bad idea to define a local class inside a function in python?

Tags:

python

scope

One function I was working on not too long ago had a structure like so:

def function():
    class Inner:
        #class stuff
    #function stuff

Inner is only ever used and needed inside of function, and it isn't returned at the end of the function either. Is it a bad idea to define a local class? I've heard many things about how it's bad for performance since python has to recompile the class every single time the function is run, and since performance is what I'm aiming to achieve with this function I'm a little worried about doing this.

like image 321
user3002473 Avatar asked Mar 19 '14 06:03

user3002473


Video Answer


3 Answers

As well as being inefficient, every Inner class is a completely new object, so separate instances will never be of the same class, which somewhat defeats the point of class-ness:

In [4]: def factory():
   ...:     class Inner(object):
   ...:         pass
   ...:     return Inner
   ...:

In [5]: i1 = factory()()

In [6]: i2 = factory()()

In [7]: i1.__class__
Out[7]: __main__.Inner

In [8]: i2.__class__
Out[8]: __main__.Inner

In [9]: i1.__class__ is i2.__class__
Out[9]: False

In [10]: isinstance(i1, i2.__class__)
Out[10]: False

This isn't an issue if each instance is completely independent, but it's something to be aware of.

like image 170
Matthew Trevor Avatar answered Oct 10 '22 12:10

Matthew Trevor


Defining a local class in your case seems useless. I'd do that if I did want to return it. There some disadvantages when defining local classes:

  • Readability: either the class is really trivial or the function inevitably grows quite long and its logic is lost in the class declaration. Also you have an extra level of indentation which might hurt readability if you have some nested loops somewhere
  • Performance: the class will be re-costructed at every function call. This usually wont take a huge amount of time, but it will cost a bit. If the function you are running is fast this cost may be significant.

There are also some advantages of defining a local class:

  • Locality: you are generally pretty sure that the class wont be used outside the function, in ways you didn't expect
  • Performance: looking up a local variable is significantly faster then looking up a global variable. If you create a big number of instances this might improve performance with respect to using a global class. However it's really really easy to counter this advantage via default arguments/local variables.

My suggestion would be to simply define the class globally and, if it should be private, use a name that starts with an underscore, like _MyClass, since this is the convention used to denote private items.


Some timings to give an idea of the changes in performance:

In [1]: class _Out(object):
   ...:     def test(self):
   ...:         for _ in range(10):
   ...:             pass
   ...:         

In [2]: def function_out(n):
   ...:     for _ in range(n):
   ...:         _Out().test()
   ...:         

In [3]: def function_in(n):
   ...:     class Inner(object):
   ...:         def test(self):
   ...:             for _ in range(10):
   ...:                 pass
   ...:     for _ in range(n):
   ...:         Inner().test()
   ...:         

In [4]: def function_mixed(n, cls=_Out):
   ...:     # use of default to access the global class via local variable
   ...:     for _ in range(n):
   ...:         cls().test()
   ...:         

In [5]: %timeit function_out(1000)
1000 loops, best of 3: 602 us per loop

In [6]: %timeit function_in(1000)
1000 loops, best of 3: 621 us per loop

In [7]: %timeit function_mixed(1000)
1000 loops, best of 3: 590 us per loop

In [8]: %timeit function_out(100000)
10 loops, best of 3: 59.9 ms per loop

In [9]: %timeit function_in(100000)
10 loops, best of 3: 60.2 ms per loop

In [10]: %timeit function_mixed(100000)
10 loops, best of 3: 58.4 ms per loop

In [11]: %timeit function_out(10)
100000 loops, best of 3: 6.52 us per loop

In [12]: %timeit function_in(10)
10000 loops, best of 3: 57.8 us per loop

In [13]: %timeit function_mixed(10)
100000 loops, best of 3: 6.33 us per loop

Note how with a big number of iterations function_in and function_out run in about the same time, while with small number of iterations function_in is about 10 times slower.

like image 33
Bakuriu Avatar answered Oct 10 '22 11:10

Bakuriu


Not so much recompiling, but more so re-evaluated. One easy test is to make a module like this:

class Root(object):
    print("root")


def make_inner():
    class Inner(object):
        print("inner")

    return Inner()

print("importing")

Then try to run this

>>> import inner
root
importing
>>> import inner
>>> inner.make_inner()
inner
<inner.Inner object at 0x12efad0>
>>> inner.make_inner()
inner
<inner.Inner object at 0x12efb50>
>>>
>>> reload(inner)
root
importing
<module 'inner' from 'inner.pyc'>

Basically as you can see the class definition is executed every time make_inner was called. This could be a problem if that particular function is called inside a loop, for instance, it's almost like reloading the class definition, but not really.

like image 3
metatoaster Avatar answered Oct 10 '22 13:10

metatoaster