Nested classes' scope?
Asked Answered
S

7

123

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?

Sudiesudnor answered 19/11, 2009 at 18:53 Comment(0)
B
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.)

Baroque answered 19/11, 2009 at 18:58 Comment(4)
It might be helpful to add with which version(s) of python your answer will work.Miscall
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.Baroque
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?Greatgranduncle
@Greatgranduncle 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.Saulsauls
N
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.

Nelda answered 3/12, 2011 at 9:26 Comment(9)
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].Ladew
@Ladew 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.Nelda
@Ladew 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.Nelda
@George: FWIW, neither the list(...) call nor comprehension work in Python 3. The documentation 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).Mozzarella
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.Candycecandystriped
@Candycecandystriped 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.)Nelda
@Candycecandystriped 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))Nelda
@Martineau Your remark, interesting. See at the end of paragraph (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»Nelda
eyquem: Unclear to me what point you are trying to make. Please be more explicit.Mozzarella
T
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.)

Turpin answered 19/11, 2009 at 19:19 Comment(0)
D
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.

Dollar answered 27/12, 2013 at 19:51 Comment(2)
NameError: name 'OuterClass' is not defined - -1Brume
The point is to access it from Outer class scope - plus similar error will occur if you call this from a static scope inside Outer. I'd suggest you delete this postBrume
Q
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)
Queri answered 18/7, 2016 at 13:37 Comment(1)
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) or weakref (3.5)Clownery
C
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

Canst answered 22/3, 2017 at 7:8 Comment(0)
B
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

Bolshevism answered 5/10, 2021 at 6:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.