12

I've seen a few "solutions" to this, but the solution every time seems to be "Don't use nested classes, define the classes outside and then use them normally". I don't like that answer, because it ignores the primary reason I chose nested classes, which is, to have a pool of constants (associated with the base class) accessible to all sub-class instances which are created.

Here is example code:

class ParentClass:

    constant_pool = []
    children = []

    def __init__(self, stream):
        self.constant_pool = ConstantPool(stream)
        child_count = stream.read_ui16()
        for i in range(0, child_count):
            children.append(ChildClass(stream))

    class ChildClass:

        name = None

        def __init__(self, stream):
            idx = stream.read_ui16()
            self.name = constant_pool[idx]

All classes are passed a single param, which is a custom bitstream class. My intention is to have a solution that does not require me to read the idx value for ChildClass while still in the ParentClass. All child-class stream reading should be done in the child class.

This example is over simplified. The constant pool is not the only variable i need available to all subclasses. The idx variable is not the only thing read from the stream reader.

Is this even possible in python? Is there no way to access the parent's information?

Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
Gerald Thibault
  • 1,043
  • 2
  • 11
  • 17
  • 3
    Calling them `ParentClass` and `ChildClass` does not make them parent and child classes. An inner class bears no inheritance relationship to its enclosing class. The only way to do what you want is to have `ParentClass` introspect itself and give a reference to itself to each of its children. – kindall Jun 17 '11 at 21:23
  • 2
    "Calling them ParentClass and ChildClass does not make them parent and child classes." is a bit patronizing, and assumes I didn't name them that way solely for the benefit of people reading the code and trying to understand what I was doing. Was that line even necessary? Did you truly not understand my motivation for naming them that way? – Gerald Thibault Jun 17 '11 at 21:40
  • 1
    I think what he was trying to say is that nested classes do not share a parent/child relationship, regardless of the intent of the programmer. The names you chose presupposes that kind of relationship is feasible, and that's what he was looking to correct. – ironchefpython Jun 17 '11 at 22:09
  • I concur with ironchefpython. Nesting ChildClass within ParentClass does not change the behavior of ChildClass. It just means that your code has to create instances by calling ParentClass.ChildClass(stream) instead of just ChildClass(stream). – wberry Jun 17 '11 at 22:25
  • Well I'm still somewhat new to python,but would a making the constant_pool a global variable work? – darkdoughnut Jun 17 '11 at 20:56

6 Answers6

12

Despite my "bit patronizing" comment (fair play to call it that!), there are actually ways to achieve what you want: a different avenue of inheritance. A couple:

  1. Write a decorator that introspects a class just after it's declared, finds inner classes, and copies attributes from the outer class into them.

  2. Do the same thing with a metaclass.

Here's the decorator approach, since it's the most straightforward:

def matryoshka(cls):

    # get types of classes
    class classtypes:
        pass
    classtypes = (type, type(classtypes))

    # get names of all public names in outer class
    directory = [n for n in dir(cls) if not n.startswith("_")]

    # get names of all non-callable attributes of outer class
    attributes = [n for n in directory if not callable(getattr(cls, n))]

    # get names of all inner classes
    innerclasses = [n for n in directory if isinstance(getattr(cls, n), classtypes)]

    # copy attributes from outer to inner classes (don't overwrite)
    for c in innerclasses:
        c = getattr(cls, c)
        for a in attributes:
            if not hasattr(c, a):
                setattr(c, a, getattr(cls, a))

    return cls

Here is a simple example of its use:

@matryoshka
class outer(object):

    answer = 42

    class inner(object):

        def __call__(self):
            print self.answer

outer.inner()()   # 42

However, I can't help but think some of the ideas suggested in other answers would serve you better.

kindall
  • 178,883
  • 35
  • 278
  • 309
  • Your decorator suggestion is exactly what I was looking for. I ended up with the following, which accomplishes what I want in the situation I am working in [i cannot figure out how the code formatting in the comments works, i am giving up]: `def add_parent_links(cls): cls_init = cls.__init__ def __init__(self, s): for k in dir(self): if str(type(getattr(self,k))).find('classobj') > -1: getattr(self,k).parent = self cls_init(self, s) cls.__init__ = __init__ return cls` – Gerald Thibault Jun 18 '11 at 04:40
  • 1
    For a variation - namelly geting at the base class frmo 'class scope' - see http://stackoverflow.com/questions/28734919/python-nested-classes-scoping-define-class-attribute-in-derived-class-using-ba - unanswered – Mr_and_Mrs_D Apr 24 '15 at 18:45
7

You don't need two classes here. Here's your example code written in a more concise fashion.

class ChildClass:
    def __init__(self, stream):
        idx = stream.read_ui16()
        self.name = self.constant_pool[idx]

def makeChildren(stream):
    ChildClass.constant_pool = ConstantPool(stream)
    return [ChildClass(stream) for i in range(stream.read_ui16())]

Welcome to Python. Classes are mutable at runtime. Enjoy.

ironchefpython
  • 3,409
  • 1
  • 19
  • 32
2

You can access the parent class through its name:

class ChildClass:

    name = None

    def __init__(self, stream):
        idx = stream.read_ui16()
        self.name = ParentClass.constant_pool[idx]

Then again, I'm not sure I understand what you are trying to achieve.

Remy Blank
  • 4,236
  • 2
  • 23
  • 24
1

Another alternative design to consider:

When you find yourself trying to use classes as namespaces, it might make more sense to put the inner classes into a module of their own and make what were the attributes of the outer class global variables. In other words, if you never intend to instantiate your ParentClass, then it's just serving as a glorified module.

Global variables get a bad rap in most programming languages, but they are not truly global in Python, and are nicely encapsulated to the module.

kindall
  • 178,883
  • 35
  • 278
  • 309
0

Well, the following works (further simplified from your example). Note that you don't have to "declare" member variables at class level like C++/C#/Java etc, just set them on self within __init__:

class ParentClass:
    def __init__(self):
        self.constant_pool = ["test"]
        self.ChildClass.constant_pool = self.constant_pool
        self.children = [self.ChildClass()]

    class ChildClass:
        def __init__(self):
            self.name = self.constant_pool[0]
            print "child name is", self.name

p = ParentClass() # Prints "child name is test"

Note that you could still do the same sort of thing without the child classes being nested.

Martin Stone
  • 12,682
  • 2
  • 39
  • 53
  • 1
    The actual class I am using has 20 sub objects, and 5 'shared' resources, so this solution would result in 100 lines of assignment as i apply each shared resource to each object. This is exactly the type of hackery I was trying to avoid by implementing the parent/child relationship. – Gerald Thibault Jun 17 '11 at 21:18
0

Your question uses the word subclass, so I'm keying from that to interpret your question. As with the others who have answered, I am not certain I understand what you are looking for.

class ParentClass(object):
  constant_pool = [c1, c2, c3]
  def __init__(self):
    # anything not included in your question

class ChildClass(ParentClass):
  def __init__(self, stream):
    ParentClass.__init__(self)
    self.name = ParentClass.constant_pool[stream.read_ui16()]

stream = get_new_stream()
children = []
for count in range(stream.read_ui16()):
  children.append(ChildClass(stream))

This code uses inheritance to derive ChildClass from ParentClass (and all methods, etc). The constant_pool is an attribute of ParentClass itself, though it is OK to treat as an attribute of any instance of ParentClass or ChildClass (saying self.constant_pool within ChildClass.__init__ would be equivalent to the above but, in my view, misleading).

Nesting the class definitions is not necessary. Nesting the definition of ChildClass within ParentClass just means that ChildClass is an attribute of ParentClass, nothing more. It does not make instances of ChildClass inherit anything from ParentClass.

wberry
  • 18,519
  • 8
  • 53
  • 85