What is the purpose of python's inner classes?

前端 未结 9 1151
心在旅途
心在旅途 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条回答
  •  -上瘾入骨i
    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
    

提交回复
热议问题