What is the purpose of python inner classes?

Python inner / nested classes confuse me. Is there something that cannot be achieved without them? If so, what is this thing?

+73
python oop class language-features
Apr 05 '09 at 21:13
source share
7 answers

Quote from http://www.geekinterview.com/question_details/64739 :

Advantages of the inner class:

  • Logical grouping of classes . If a class is useful only for one other class, then it is logical to embed it in this class and save it together. Nesting such "helper classes" makes their package more orderly.
  • Enhanced Encapsulation . Consider two top-level classes A and B, where B requires access to members of A that would otherwise be declared private. By hiding class B in class A, members can be declared private, and B access to them. In addition, B himself may be hidden from the outside world.
  • More readable, supported code . Nested small classes in top-level classes put the code closer to where it is used.

The main advantage is the organization. Everything that can be done with the help of inner classes can be done without them.

+63
Apr 05 '09 at 21:24
source share
— -

Is there something that cannot be achieved without them?

No. They are absolutely equivalent to defining a class, usually at the top level, and then copying the reference to it into an external class.

I don’t think there is any special reason that nested classes are allowed, besides the fact that it clearly does not make sense to explicitly prohibit them.

If you are looking for a class that exists in the life cycle of an external / owner object, and always refers to an instance of an external class - inner classes, as Java does, then nested Python classes are not a thing. But you can hack something like this:

import weakref, new class innerclass(object): """Descriptor for making inner classes. Adds a property 'owner' to the inner class, pointing to the outer owner instance. """ # Use a weakref dict to memoise previous results so that # instance.Inner() always returns the same inner classobj. # def __init__(self, inner): self.inner= inner self.instances= weakref.WeakKeyDictionary() # Not thread-safe - consider adding a lock. # def __get__(self, instance, _): if instance is None: return self.inner if instance not in self.instances: self.instances[instance]= new.classobj( self.inner.__name__, (self.inner,), {'owner': instance} ) return self.instances[instance] # Using an inner class # class Outer(object): @innerclass class Inner(object): def __repr__(self): return '<%s.%s inner object of %r>' % ( self.owner.__class__.__name__, self.__class__.__name__, self.owner ) >>> o1= Outer() >>> o2= Outer() >>> i1= o1.Inner() >>> i1 <Outer.Inner inner object of <__main__.Outer object at 0x7fb2cd62de90>> >>> isinstance(i1, Outer.Inner) True >>> isinstance(i1, o1.Inner) True >>> isinstance(i1, o2.Inner) False 

(This uses class decorators that are new in Python 2.6 and 3.0. Otherwise, you will have to say “Inner = innerclass (Inner)” after the class is defined.)

+45
Apr 6 '09 at 16:23
source share

There you need to wrap your head to understand this. In most languages, class definitions are compiler directives. That is, the class is created before the program is launched. In python, all statements are executable. This means that this statement:

 class foo(object): pass 

is a statement that is executed at run time, like this one:

 x = y + z 

This means that you can not only create classes in other classes, but also create classes anywhere. Consider this code:

 def foo(): class bar(object): ... z = bar() 

Thus, the idea of ​​an “inner class” is not really a language construct; this is a programmer. Guido has a very good idea of ​​how this happened here . But essentially, the basic idea is that it simplifies the grammar of the language.

+22
Apr 6 '09 at 16:38
source share

Nested classes inside classes:

  • Nested classes inflate a class definition, making it difficult to see what is happening.

  • Nested classes can create a relationship, which makes testing difficult.

  • In Python, you can put more than one class in a file / module, unlike Java, so the class still remains close to the top-level class and may even have a class name prefixed with "_" to help indicate that others should not use it .

The place where nested classes can be useful is inside functions

 def some_func(a, b, c): class SomeClass(a): def some_method(self): return b SomeClass.__doc__ = c return SomeClass 

A class captures values ​​from functions that allow you to dynamically create a class, for example metaprogramming templates in C ++

+11
Apr 6 '09 at 15:43
source share

I understand the arguments against nested classes, but there is a case for their use in some cases. Imagine that I am creating a doubly linked list class, and I need to create a node class to support nodes. I have two options: create a node class inside the DoublyLinkedList class or create a node class outside the DoublyLinkedList class. I prefer the first choice in this case, because the node class only makes sense inside the DoublyLinkedList class. Despite the lack of hiding / encapsulation benefits, there is an advantage to grouping in that you can say that the node class is part of the DoublyLinkedList class.

+5
Apr 29 2018-12-12T00:
source share

I used Python inner classes to intentionally create buggy subclasses in unittest functions (i.e. inside def test_something(): to get closer to 100 percent testing coverage (for example, testing very rarely called log statements by overriding some methods) .

In retrospect, this is similar to Ed's answer https://stackoverflow.com/a/166189/

Such inner classes should go out of scope and be ready to collect garbage as soon as all references to them are removed. For example, take the following inner.py file:

 class A(object): pass def scope(): class Buggy(A): """Do tests or something""" assert isinstance(Buggy(), A) 

I get the following interesting results in OSX Python 2.7.6:

 >>> from inner import A, scope >>> A.__subclasses__() [] >>> scope() >>> A.__subclasses__() [<class 'inner.Buggy'>] >>> del A, scope >>> from inner import A >>> A.__subclasses__() [<class 'inner.Buggy'>] >>> del A >>> import gc >>> gc.collect() 0 >>> gc.collect() # Yes I needed to call the gc twice, seems reproducible 3 >>> from inner import A >>> A.__subclasses__() [] 

Hint. Do not go ahead and do not try to do this with Django models, which seem to preserve other (cached?) Links to my buggy classes.

So, in general, I would not recommend using inner classes for this kind of purpose unless you really are worth the 100% testing coverage and cannot use other methods. Although I find it nice to know that if you use __subclasses__() , it can sometimes get __subclasses__() inner classes. Anyway, if you have followed this far, I think we're pretty deep in Python at the moment, private dunderscores and all.

0
Feb 18 '15 at 10:37
source share

The main use case I use for this is to prevent small modules from spreading and to prevent namespace pollution when separate modules are not needed. If I extend an existing class, but this existing class should refer to another subclass that should always be associated with it. For example, I might have a utils.py module in which there are many helper classes that are not necessarily connected together, but I want to strengthen the connection for some of these helper classes. For example, when I implement https://stackoverflow.com/a/316947/

: utils.py :

 import json, decimal class Helper1(object): pass class Helper2(object): pass # Here is the notorious JSONEncoder extension to serialize Decimals to JSON floats class DecimalJSONEncoder(json.JSONEncoder): class _repr_decimal(float): # Because float.__repr__ cannot be monkey patched def __init__(self, obj): self._obj = obj def __repr__(self): return '{:f}'.format(self._obj) def default(self, obj): # override JSONEncoder.default if isinstance(obj, decimal.Decimal): return self._repr_decimal(obj) # else super(self.__class__, self).default(obj) # could also have inherited from object and used return json.JSONEncoder.default(self, obj) 

Then we can:

 >>> from utils import DecimalJSONEncoder >>> import json, decimal >>> json.dumps({'key1': decimal.Decimal('1.12345678901234'), ... 'key2':'strKey2Value'}, cls=DecimalJSONEncoder) {"key2": "key2_value", "key_1": 1.12345678901234} 

Of course, we could refuse to inherit json.JSONEnocder altogether and simply override default ():

:

 import decimal, json class Helper1(object): pass def json_encoder_decimal(obj): class _repr_decimal(float): ... if isinstance(obj, decimal.Decimal): return _repr_decimal(obj) return json.JSONEncoder(obj) >>> json.dumps({'key1': decimal.Decimal('1.12345678901234')}, default=json_decimal_encoder) '{"key1": 1.12345678901234}' 

But sometimes just for convention, you want utils consist of classes for extensibility.

Here is another use case: I want to use factory for modables in my OuterClass without having to call copy :

 class OuterClass(object): class DTemplate(dict): def __init__(self): self.update({'key1': [1,2,3], 'key2': {'subkey': [4,5,6]}) def __init__(self): self.outerclass_dict = { 'outerkey1': self.DTemplate(), 'outerkey2': self.DTemplate()} obj = OuterClass() obj.outerclass_dict['outerkey1']['key2']['subkey'].append(4) assert obj.outerclass_dict['outerkey2']['key2']['subkey'] == [4,5,6] 

I prefer this template over the @staticmethod decoder, which otherwise you would use for the factory function.

0
Sep 11 '17 at 21:49
source share



All Articles