122

I'm trying to understand scope in nested classes in Python. Here is my example code:

class OuterClass:
    outer_var = 1
    class InnerClass:
        inner_var = outer_var

The creation of class does not complete and I get the error:

<type 'exceptions.NameError'>: name 'outer_var' is not defined

Trying inner_var = Outerclass.outer_var doesn't work. I get:

<type 'exceptions.NameError'>: name 'OuterClass' is not defined

I am trying to access the static outer_var from InnerClass.

Is there a way to do this?

martineau
  • 119,623
  • 25
  • 170
  • 301

7 Answers7

107
class Outer(object):
    outer_var = 1

    class Inner(object):
        @property
        def inner_var(self):
            return Outer.outer_var

This isn't quite the same as similar things work in other languages, and uses global lookup instead of scoping the access to outer_var. (If you change what object the name Outer is bound to, then this code will use that object the next time it is executed.)

If you instead want all Inner objects to have a reference to an Outer because outer_var is really an instance attribute:

class Outer(object):
    def __init__(self):
        self.outer_var = 1

    def get_inner(self):
        return self.Inner(self)
        # "self.Inner" is because Inner is a class attribute of this class
        # "Outer.Inner" would also work, or move Inner to global scope
        # and then just use "Inner"

    class Inner(object):
        def __init__(self, outer):
            self.outer = outer

        @property
        def inner_var(self):
            return self.outer.outer_var

Note that nesting classes is somewhat uncommon in Python, and doesn't automatically imply any sort of special relationship between the classes. You're better off not nesting. (You can still set a class attribute on Outer to Inner, if you want.)

martineau
  • 119,623
  • 25
  • 170
  • 301
  • 2
    It might be helpful to add with which version(s) of python your answer will work. – AJ. Nov 19 '09 at 19:00
  • 1
    I wrote this with 2.6/2.x in mind, but, looking at it, I see nothing that wouldn't work the same in 3.x. –  Nov 19 '09 at 19:07
  • I don't quite understand what you mean in this part, "(If you change what object the name Outer is bound to, then this code will use that object the next time it is executed.)" Can you please help me understand? – batbrat Mar 28 '14 at 09:55
  • 2
    @batbrat it means that the reference to `Outer` is looked up anew every time you do `Inner.inner_var`. So if you rebind the name `Outer` to a new object, `Inner.inner_var` will start returning that new object. – Felipe Nov 20 '14 at 13:44
45

I think you can simply do:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

The problem you encountered is due to this:

A block is a piece of Python program text that is executed as a unit. The following are blocks: a module, a function body, and a class definition.
(...)
A scope defines the visibility of a name within a block.
(...)
The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes generator expressions since they are implemented using a function scope. This means that the following will fail:

   class A:  

       a = 42  

       b = list(a + i for i in range(10))

http://docs.python.org/reference/executionmodel.html#naming-and-binding

The above means:
a function body is a code block and a method is a function, then names defined out of the function body present in a class definition do not extend to the function body.

Paraphrasing this for your case:
a class definition is a code block, then names defined out of the inner class definition present in an outer class definition do not extend to the inner class definition.

eyquem
  • 26,771
  • 7
  • 38
  • 46
  • 2
    Amazing. Your example fails, claiming "global name 'a' is not defined". Yet substituting a list comprehension `[a + i for i in range(10)]` successfully binds A.b to the expected list [42..51]. – George Jan 08 '12 at 20:50
  • 6
    @George Note that the example with **class A** isn't mine, it is from the Python official doc whose I gave link. This example fails and that failure is what is wanted to be shown in this example. In fact ``list(a + i for i in range(10))`` is ``list((a + i for i in range(10)))`` that is to say ``list(a_generator)``. They say **a generator** is implemented with a similar scope than the scope of functions. – eyquem Jan 08 '12 at 23:01
  • @George For me, that means that functions act differently according if they are in a module or in a class. In the first case, a function goes outside to find the object binded to a free identifier. In the second case, a function, that is to say a method, doesn't go outside its body. Functions in a module and methods in a class are in reality two kinds of objects. Methods are not just functions in class. That's my idea. – eyquem Jan 08 '12 at 23:01
  • 5
    @George: FWIW, neither the `list(...)` call nor comprehension work in Python 3. The [documentation](http://docs.python.org/3/reference/executionmodel.html?highlight=naming%20binding#naming-and-binding) for Py3 is also slightly different reflecting this. It now says "**The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes _comprehensions_ and generator expressions since they are implemented using a function scope.**" (emphasis mine). – martineau Jan 29 '13 at 16:00
  • I am curious about why list(A.a + i for i in range(10)) also doesn't work, where I fixed a by A.a. I think A may be a global name. – gaoxinge Feb 12 '17 at 12:43
  • @gaoxinge Because the execution of the definition of class A is a dynamic process taking place in two steps: first the construction of the class OBJECT according to the definition, then the BINDING of the just created class object to the name A. (Note that I use the word definition as meaning 'the part of the script that defines the class' (as in the Python doc by the way), not the execution itself.) – eyquem Mar 14 '17 at 20:43
  • @gaoxinge During the creating step of the class object, the name A does not yet exist in the global space: that can be put in evidence by writing ``print(globals())`` just before ``line b = list(a + i for i in range(10))`` – eyquem Mar 14 '17 at 20:44
  • @Martineau Your remark, interesting. See at the end of paragraph (https://docs.python.org/release/3.0/whatsnew/3.0.html#changed-syntax) in _What's new in Python 3.0_ : « list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a list() constructor» – eyquem Mar 14 '17 at 21:19
  • eyquem: Unclear to me what point you are trying to make. Please be more explicit. – martineau Mar 14 '17 at 21:40
20

You might be better off if you just don't use nested classes. If you must nest, try this:

x = 1
class OuterClass:
    outer_var = x
    class InnerClass:
        inner_var = x

Or declare both classes before nesting them:

class OuterClass:
    outer_var = 1

class InnerClass:
    inner_var = OuterClass.outer_var

OuterClass.InnerClass = InnerClass

(After this you can del InnerClass if you need to.)

Jason Orendorff
  • 42,793
  • 6
  • 62
  • 96
3

Easiest solution:

class OuterClass:
    outer_var = 1
    class InnerClass:
        def __init__(self):
            self.inner_var = OuterClass.outer_var

It requires you to be explicit, but doesn't take much effort.

Cristian Garcia
  • 9,630
  • 6
  • 54
  • 75
1

In Python mutable objects are passed as reference, so you can pass a reference of the outer class to the inner class.

class OuterClass:
    def __init__(self):
        self.outer_var = 1
        self.inner_class = OuterClass.InnerClass(self)
        print('Inner variable in OuterClass = %d' % self.inner_class.inner_var)

    class InnerClass:
        def __init__(self, outer_class):
            self.outer_class = outer_class
            self.inner_var = 2
            print('Outer variable in InnerClass = %d' % self.outer_class.outer_var)
T. Alpers
  • 46
  • 2
  • 2
    Please note that you have a reference cycle here, and in some scenario instance of this class won't be freed. One example, with cPython, should you have defined `__del__` method, the garbage collector won't be able to handle the reference cycle, and the objects will go into `gc.garbage`. The code above, as-is, isn't problematic though. The way to deal with it is using a **weak reference**. You can read documentation on [weakref (2.7)](https://docs.python.org/2/library/weakref.html) or [weakref (3.5)](https://docs.python.org/3.5/library/weakref.html) – Guy Sep 01 '16 at 05:37
1

All explanations can be found in Python Documentation The Python Tutorial

For your first error <type 'exceptions.NameError'>: name 'outer_var' is not defined. The explanation is:

There is no shorthand for referencing data attributes (or other methods!) from within methods. I find that this actually increases the readability of methods: there is no chance of confusing local variables and instance variables when glancing through a method.

quoted from The Python Tutorial 9.4

For your second error <type 'exceptions.NameError'>: name 'OuterClass' is not defined

When a class definition is left normally (via the end), a class object is created.

quoted from The Python Tutorial 9.3.1

So when you try inner_var = Outerclass.outer_var, the Quterclass hasn't been created yet, that's why name 'OuterClass' is not defined

A more detailed but tedious explanation for your first error:

Although classes have access to enclosing functions’ scopes, though, they do not act as enclosing scopes to code nested within the class: Python searches enclosing functions for referenced names, but never any enclosing classes. That is, a class is a local scope and has access to enclosing local scopes, but it does not serve as an enclosing local scope to further nested code.

quoted from Learning.Python(5th).Mark.Lutz

Andy Guo
  • 127
  • 2
  • 7
0
class c_outer:
    def __init__(self, name:str='default_name'):
        self._name = name
        self._instance_lst = list()
        self._x = self.c_inner()

    def get_name(self):
        return(self._name)

    def add_inner_instance(self,name:str='default'):
        self._instance_lst.append(self.c_inner(name))

    def get_instance_name(self,index:int):
        return(self._instance_lst[index].get_name())


    class c_inner:
        def __init__(self, name:str='default_name'):
            self._name = name
        def get_name(self):
            return(self._name)


outer = c_outer("name_outer")

outer.add_inner_instance("test1")
outer.add_inner_instance("test2")
outer.add_inner_instance("test3")
inner_1 = outer.c_inner("name_inner1")
inner_2 = outer.c_inner("name_inner2")
inner_3 = outer.c_inner("name_inner3")

print(outer.get_instance_name(index=0))
print(outer.get_instance_name(1))
print(outer._instance_lst[2]._name
print(outer.get_name())
print(inner_1.get_name())
print(inner_2.get_name())

test1 test2 test3 name_outer name_inner1 name_inner2 name_inner3

ron
  • 61
  • 4