Nested Python class needs to access variable in en

2019-01-27 15:51发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

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.



回答4:

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.



回答5:

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.



回答6:

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.