What is the purpose of python's inner classes?

前端 未结 9 1030
心在旅途
心在旅途 2020-11-28 02:12

Python\'s inner/nested classes confuse me. Is there something that can\'t be accomplished without them? If so, what is that thing?

相关标签:
9条回答
  • 2020-11-28 03:06

    Nesting classes within classes:

    • Nested classes bloat the class definition making it harder to see whats going on.

    • Nested classes can create coupling that would make testing more difficult.

    • In Python you can put more than one class in a file/module, unlike Java, so the class still remains close to top level class and could even have the class name prefixed with an "_" to help signify that others shouldn't be using it.

    The place where nested classes can prove useful is within functions

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

    The class captures the values from the function allowing you to dynamically create a class like template metaprogramming in C++

    0 讨论(0)
  • 2020-11-28 03:11

    Is there something that can't be accomplished without them? If so, what is that thing?

    There is something that cannot be easily done without: inheritance of related classes.

    Here is a minimalist example with the related classes A and B:

    class A(object):
        class B(object):
            def __init__(self, parent):
                self.parent = parent
    
        def make_B(self):
            return self.B(self)
    
    
    class AA(A):  # Inheritance
        class B(A.B):  # Inheritance, same class name
            pass
    

    This code leads to a quite reasonable and predictable behaviour:

    >>> type(A().make_B())
    <class '__main__.A.B'>
    >>> type(A().make_B().parent)
    <class '__main__.A'>
    >>> type(AA().make_B())
    <class '__main__.AA.B'>
    >>> type(AA().make_B().parent)
    <class '__main__.AA'>
    

    If B were a top-level class, you could not write self.B() in the method make_B but would simply write B(), and thus lose the dynamic binding to the adequate classes.

    Note that in this construction, you should never refer to class A in the body of class B. This is the motivation for introducing the parent attribute in class B.

    Of course, this dynamic binding can be recreated without inner class at the cost of a tedious and error-prone instrumentation of the classes.

    0 讨论(0)
  • 2020-11-28 03:12

    1. Two functionally equivalent ways

    The two ways shown before are functionally identical. However, there are some subtle differences, and there are situations when you would like to choose one over another.

    Way 1: Nested class definition
    (="Nested class")

    class MyOuter1:
        class Inner:
            def show(self, msg):
                print(msg)
    

    Way 2: With module level Inner class attached to Outer class
    (="Referenced inner class")

    class _InnerClass:
        def show(self, msg):
            print(msg)
    
    class MyOuter2:
        Inner = _InnerClass
    

    Underscore is used to follow PEP8 "internal interfaces (packages, modules, classes, functions, attributes or other names) should -- be prefixed with a single leading underscore."

    2. Similarities

    Below code snippet demonstrates the functional similarities of the "Nested class" vs "Referenced inner class"; They would behave the same way in code checking for the type of an inner class instance. Needless to say, the m.inner.anymethod() would behave similarly with m1 and m2

    m1 = MyOuter1()
    m2 = MyOuter2()
    
    innercls1 = getattr(m1, 'Inner', None)
    innercls2 = getattr(m2, 'Inner', None)
    
    isinstance(innercls1(), MyOuter1.Inner)
    # True
    
    isinstance(innercls2(), MyOuter2.Inner)
    # True
    
    type(innercls1()) == mypackage.outer1.MyOuter1.Inner
    # True (when part of mypackage)
    
    type(innercls2()) == mypackage.outer2.MyOuter2.Inner
    # True (when part of mypackage)
    
    

    3. Differences

    The differences of "Nested class" and "Referenced inner class" are listed below. They are not big, but sometimes you would like to choose one or the other based on these.

    3.1 Code Encapsulation

    With "Nested classes" it is possible to encapsulate code better than with "Referenced inner class". A class in the module namespace is a global variable. The purpose of nested classes is to reduce clutter in the module and put the inner class inside the outer class.

    While no-one* is using from packagename import *, low amount of module level variables can be nice for example when using an IDE with code completion / intellisense.

    *Right?

    3.2 Readability of code

    Django documentation instructs to use inner class Meta for model metadata. It is a bit more clearer* to instruct the framework users to write a class Foo(models.Model) with inner class Meta;

    class Ox(models.Model):
        horn_length = models.IntegerField()
    
        class Meta:
            ordering = ["horn_length"]
            verbose_name_plural = "oxen"
    

    instead of "write a class _Meta, then write a class Foo(models.Model) with Meta = _Meta";

    class _Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"
    
    class Ox(models.Model):
        Meta = _Meta
        horn_length = models.IntegerField()
    
    • With the "Nested class" approach the code can be read a nested bullet point list, but with the "Referenced inner class" method one has to scroll back up to see the definition of _Meta to see its "child items" (attributes).

    • The "Referenced inner class" method can be more readable if your code nesting level grows or the rows are long for some other reason.

    * Of course, a matter of taste

    3.3 Slightly different error messages

    This is not a big deal, but just for completeness: When accessing non-existent attribute for the inner class, we see slighly different exceptions. Continuing the example given in Section 2:

    innercls1.foo()
    # AttributeError: type object 'Inner' has no attribute 'foo'
    
    innercls2.foo()
    # AttributeError: type object '_InnerClass' has no attribute 'foo'
    

    This is because the types of the inner classes are

    type(innercls1())
    #mypackage.outer1.MyOuter1.Inner
    
    type(innercls2())
    #mypackage.outer2._InnerClass
    
    0 讨论(0)
提交回复
热议问题