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.
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.
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:
There are also some advantages of defining a local class:
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With