2

First off, let me say that yes I have researched this extensively for a few days now with no luck. I have looked at numerous examples and similar situations such as this one, but so far nothing has been able to resolve me issue.

My problem is I have a Python project that has a primary class, with two nested classes (yea yea I know), one of those classes is a subclass of the first. I can not figure out why I keep getting NameError: global name 'InnerSubClass' is not defined.

I understand scoping (both classes in question are in the same scope) but nothing I try seems to resolve the issue (I want to keep the two classes nested at a minimum) despite this problem working for other people.

Here is a simple example of what I am trying to do:

class SomeClass(object):
        def __init__(self):

            """lots of other working stuff"""


class MainClass(object):
    def __init__(self):
        self.stuff = []
        self.moreStuffs = []

    class InnerClass(object):
        def __init__(self, thing, otherThing):
            self.thing = thing
            self.otherThing = otherThing
            self.otherStuff = []

    class InnerSubClass(InnerClass):
        def __init__(self, thing, otherThing, newThing):
            super(InnerSubClass).__init__(thing, otherThing)
            self.newThing = newThing

        """other code that worked before the addition of 'InnerSubClass'"""

    def doSomething(self):
        innerclass = self.InnerSubClass('thisthing', 'thatthing', 'thingthing')
        print("just more thing words %s" % innerclass.newThing)


myThing = MainClass()
myThing.doSomething()

I have tried changing super(InnerSubClass).__init__(thing, otherThing) to super(InnerClass.InnerSubClass).__init__(thing, otherThing) and even super(MainClass.InnerClass.InnerSubClass).__init__(thing, otherThing) with no success. I made "InnerSubClass" inherit straight from object InnerSubClass(object): etc, and it still doesn't work.

Granted I am far from a seasoned python developer and come from mostly other compiled OO languages, and can't seem to wrap my head around why this isn't working. If I get rid of the "InnerSubClass", everything works just fine.

It doesn't seem like python offers "private" classes and functions like other languages, which is fine but I would like to utilize the nesting to at least keep objects "lumped" together. In this case, nothing should be instantiating "InnerClass" or "InnerSubClass" except functions in "MainClass".

Please provide helpful advice and explain why it doesn't work as expected with background information on how this should be done properly. If this was as simple as it seems, it would have been figured out by now.

edit: for clarification, this is only for v2

DoS
  • 1,454
  • 13
  • 18

3 Answers3

5

There is no "class scope" in lookup order

When creating a new class, the code in the body is executed and the resulting names are passed to type for creation. Python lookups go from inner to outer, but you don't have a "class level", only the names you define to become attributes/methods of your new class. In fact, if you want to access class variables inside a method, you use MyClass.attr instead of simple attr.

The inheritance works because InnerSubClass(InnerClass) occurs inside the class creation. To access InnerClass after MainClass has been created, do the same as you would for class attributes: MainClass.InnerClass

Just to include an example:

class Outer:
    out = 1
    class Inner:
        inside = 2
        try:
            print(out)  # this is confusing
        except NameError:
            print("can't find out")
        def f(self):
            try:
                print(inside)  # this is clear
            except NameError:
                print("can't find inside")
        try:
            print(Inner.inside)  # this is less clear
        except NameError:
            print("can't find Inner.inside")
Outer.Inner().f()
# can't find anything

Edit:

The above is a general view, to apply it directly to your situation, look at your inner classes the way you look at regular class attributes. You'd access these as MyClass.attr, where MyClass is defined globally. If you replace attr with InnerSubClass, you get the class (attribute lookup doesn't care about inheritance, but about where the attributes are).

A stripped-down example with nested inheriting classes:

class MainClass(object):
    class Inner(object):
        pass
    class InnerSub(Inner):
        def __init__(self):
            print(super(MainClass.InnerSub))  # note you use MainClass, known globally
    def f(self):
        return self.InnerSub()
MainClass().f()  # prints "<super ...>" and returns a MainCLass.InnerSub object
user24343
  • 892
  • 10
  • 19
  • i think i get what you're getting at, but you're talking about nesting not inheritance. i have no issue nesting the classes, its subclassing thats causing the issue. using your example, one should be able to instasiate "Inner" and have it inherit "out" from "Outer" with a value of "1". – DoS Feb 16 '19 at 13:58
  • Unless you excluded a not-working example, your problem is the nesting. I'll add an example to match your classes. – user24343 Feb 16 '19 at 14:24
  • @DoS Make sure to downvote this answer, too, since the poster didn't explicitly pass an object with his class declaration, so it won't work on Python 2.x. – Mark Moretto Feb 16 '19 at 14:49
  • Class scope is a scope. It's not a valid closure scope, but it's a scope. – user2357112 Feb 16 '19 at 19:05
  • @user2357112 arghhh, totally right, I'm sorry. I hope the edit makes it clear enough, otherwise feel free to highlight better. – user24343 Feb 16 '19 at 19:36
  • copy and pasting your lower example i get the error "TypeError: super() argument 1 must be type, not classobj". so not exactly sure what thats all about. – DoS Feb 16 '19 at 21:09
  • @DoS inherit your classes from ``object`` for Python 2. I'll move it inside the code from the very last line. – user24343 Feb 16 '19 at 21:15
3

Here, they do it like this

super(MainClass.InnerSubClass, self).__init__(thing, otherThing)

So that you can test it, here is the full working example

class SomeClass(object):
        def __init__(self):

            """lots of other working stuff"""


class MainClass(object):
    def __init__(self):
        self.stuff = []
        self.moreStuffs = []

    class InnerClass(object):
        def __init__(self, thing, otherThing):
            self.thing = thing
            self.otherThing = otherThing
            self.otherStuff = []

    class InnerSubClass(InnerClass):
        def __init__(self, thing, otherThing, newThing):
            super(MainClass.InnerSubClass, self).__init__(thing, otherThing)
            self.newThing = newThing

        """other code that worked before the addition of 'InnerSubClass'"""

    def doSomething(self):
        innerclass = self.InnerSubClass('thisthing', 'thatthing', 'thingthing')
        print("just more thing words %s" % innerclass.newThing)

        print("and I also inherit from InnerClass %s" % innerclass.otherThing)


myThing = MainClass()
myThing.doSomething()

The output is

just more thing words thingthing
and I also inherit from InnerClass thatthing
p13rr0m
  • 1,107
  • 9
  • 21
  • as you can see in my original posting i have already tried that, and several other variants looking for a solution. thanks for the suggestions. – DoS Feb 16 '19 at 13:48
  • sorry but I don't think you have used that, at least you haven't mentioned it `MainClass.InnerSubClass`, and it works for me without an error – p13rr0m Feb 16 '19 at 14:59
  • @DoS: This is not one of the variants you said you tried in your post, and you probably haven't tried it, because it's the correct variant. – user2357112 Feb 16 '19 at 19:14
  • sorry for the late comment. i did actually try this, and i get "TypeError: super() argument 1 must be type, not str". using the same code on a mac, i get "TypeError: must be type, not str" – DoS Feb 16 '19 at 20:47
  • @DoS: That error is because you've done something wrong you haven't shown us, most likely reusing a name or making a typo. We can't tell, because you haven't shown us. – user2357112 Feb 16 '19 at 22:12
  • so help me out here, way way down, inside the MainClass i have a function that inside it instantiates an "InnerClass" object and a "InnerSubClass" object. if i comment out that line you suggest everything works, except the "InnerSubClass" object doesn't inherit anything form "InnerClass" (defeating the purpose off all this). when referencing an internal object in python, is there a difference from using "s1 = self.InnerClass(...etc...)" & "s2 = self.InnerSubClass(...etc..)" verses "s1 = MainClass.InnerClass(...etc...)" and so on? i'm wondering if internally my namespace references are off? – DoS Feb 16 '19 at 23:19
0

If you have reasons for not using MainClass.InnerSubClass, you can also use type(self) or self.__class__ (OK, but which one) inside __init__ to get the containing class. This works well lots of layers deep (which shouldn't happen anyway), and requires the argument passed to super to be the type of the instance (which it should be anyway) but breaks if you subclass, as seen here. The concept might be clearer to you than scoping rules:

class MainClass:
    class Inner:
        pass
    class InnerSub(Inner):
        def __init__(self):
            print(super(self.__class__))
            print(super(type(self)))
MainClass().InnerSub()
user24343
  • 892
  • 10
  • 19
  • **NO**. This is a common mistake people make. The first argument to `super` needs to be the class in which the method is defined, not `type(self)` or `self.__class__`. `type(self)` and `self.__class__` are the wrong class in subclasses, leading to [wrong method calls and infinite recursion](https://ideone.com/Ld8PAX). – user2357112 Feb 16 '19 at 19:08
  • If it were as simple as `type(self)`, `super` never would have required the class argument, even in Python 2. – user2357112 Feb 16 '19 at 19:09
  • "breaks if you subclass without overriding the method" - [it breaks even if you override the method](https://ideone.com/nBbNNM), as long as the override still delegates to the original, which it should for `__init__`. Using `super` this way is a really dangerous move, and it propagates dangerous practices to people who will likely not realize the danger. – user2357112 Feb 16 '19 at 19:53