Search code examples
pythonclassinner-classesoperator-precedence

Inner classes evaluation order in python


Say I do this in python 3.6:

class A:
    class B:
        pass
    class C:
        x = B()

This fails complaining that B is not defined when instantiating it in C. However, this:

class A:
    x = 1
    y = x

    def f(self):
        return self

    z = f

works fine.

And of course, this:

class A:
    pass

class B:
    x = A()

also works.

Why do inner classes definitions not follow the same logical rules as everything else?


Solution

  • The problem here is not the definition order, which works like everything else, it is the special nature of class-body scope. Essentially, variables in the class-body scope can only be accessed using the class-namespace, e.g. MyClass.my_class_variable. The special aspect of it is that it does not create an enclosing scope, which is why you cannot access my_class_variable and are forced to use the class-namespace inside a method definition, for example. Read more about that in this answer. So again, B is in fact defined. Note, the following works:

    In [4]: class A:
       ...:     class B:
       ...:         pass
       ...:     x = B.__name__
       ...:
       ...:
    
    In [5]: A.x
    Out[5]: 'B'
    

    Now, you might hope the following would work:

    In [6]: class A:
       ...:     class B:
       ...:         pass
       ...:     class C:
       ...:         x = A.B()
       ...:
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-1-a145c80eee84> in <module>()
    ----> 1 class A:
          2     class B:
          3         pass
          4     class C:
          5         x = A.B()
    
    <ipython-input-1-a145c80eee84> in A()
          2     class B:
          3         pass
    ----> 4     class C:
          5         x = A.B()
          6
    
    <ipython-input-1-a145c80eee84> in C()
          3         pass
          4     class C:
    ----> 5         x = A.B()
          6
    

    However, it does not, because A hasn't actually been defined yet! So, the solution is something like this:

    In [7]: class A:
       ...:     class B:
       ...:         pass
       ...:     class C:
       ...:         pass
       ...:
       ...:
    
    In [8]: A.C.x = A.B
    

    Edited

    So, this example should be illuminating:

    In [14]: x = 'global'
    
    In [15]: class Foo:
        ...:     x = 'foooooo!'
        ...:     def bar(self):
        ...:         return x
        ...:
    
    In [16]: Foo().bar()
    Out[16]: 'global'
    
    In [17]: Foo.x
    Out[17]: 'foooooo!'