7

I'm wondering what the best way of accessing a parent variable from a nested subclass is, currently I'm using a decorator.

Is that the only/best way???

I don't want to have to directly access the parent variable (eg. ComponentModel.origin (see below)) as that would require more code in the "config" file, so I'm wondering whether I could assign parent variable in a class which the subclass in question inherits from?

Trivial example of my current solution:

# defined in a big library somewhere:
class LibrarySerialiser(object):
    pass

# defined in my module:
class ModelBase:
    pass

class SerialiserBase(LibrarySerialiser):
    def __init__(self, *args, **kwargs):
        # could i some how get hold of origin here without the decorator?
        print self.origin
        super(SerialiserBase, self).__init__(*args, **kwargs)

def setsubclasses(cls):
    cls.Serialiser.origin = cls.origin
    return cls

# written by "the user" for the particular application as the
# configuration of the module above:

@setsubclasses
class ComponentModel(ModelBase):
    origin = 'supermarket'

    class Serialiser(SerialiserBase):
        pass


ser = ComponentModel.Serialiser()

This is obviously a trival example that misses all the real logic hence lots of the classes appear void but are really necessary.

1 Answer 1

7

FYI, the accepted terminology used when nesting classes as you've done is inner/outer, not parent/child or super/subclass. The parent/child or super/sub relationship refers to inheritance. This makes your decorator's name, setsubclasses, confusing, since there are no subclasses involved!

The unusual thing you're doing here is using the class as a namespace without instantiating it. Normally you would instantiate your ComponentModel and at that time, it is trivial to give your Serialiser inner class a copy of an attribute from its outer class. E.g.:

class ModelBase(object):
    def __init__(self):
        self.Serialiser.origin = self.origin

# ... then

cm  = ComponentModel()
ser = cm.Serialiser()

Better yet, have the outer class instantiate the inner class and pass it a reference to the outer class; then it can grab any attributes it wants itself, whenever it needs them:

class ModelBase(object):
    def __init__(self, *args, **kwargs):
        serialiser = self.Serialiser(self, *args, **kwargs)

class SerialiserBase(LibrarySerialiser):
    def __init__(self, outer, *args, **kwargs):
        self.outer = outer
        print self.outer.origin
        super(SerialiserBase, self).__init__(*args, **kwargs)

# ...

cm  = ComponentModel()
ser = cm.serialiser

However, if you insist on being able to get this attribute without instantiating the outer class, you can use a metaclass to set the attribute:

class PropagateOuter(type):
    def __init__(cls, name, bases, dct):
        type.__init__(cls, name, bases, dct)
        if "Serialiser" in dct:
            cls.Serialiser.outer = cls

class ModelBase(object):
    __metaclass__ = PropagateOuter

# Python 3 version of the above
# class ModelBase(metaclass=PropagateOuter):
#     pass

class SerialiserBase(LibrarySerialiser):
    def __init__(self, *args, **kwargs):
        print self.outer.origin
        super(SerialiserBase, self).__init__(*args, **kwargs)

class ComponentModel(ModelBase):
    origin = 'supermarket'

    class Serialiser(SerialiserBase):
        pass

ser = ComponentModel.Serialiser()

This isn't doing anything your decorator isn't, but the user gets it automatically through inheritance rather than having to specify it manually. The Zen of Python says "explicit is better than implicit" so tomato, tomato.

You could even write the metaclass so that it introspects the outer class and puts a reference to that class into every inner class regardless of their name.

By the way, one of the pitfalls of the way you're doing this is that all your model classes must subclass SerialiserBase. If a user of your class just wants the default serialiser, they can't just write Serialiser = SerialiserBase in their class definition, they must write class Serialiser(SerialiserBase): pass. This is because there's only one SerialiserBase and it obviously can't contain a reference to multiple outer classes. Of course, you could write your metaclass to deal with this (e.g. by automatically making a subclass of the specified serialiser if it already has an outer attribute).

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot for the response, very helpful. I think your first solution meets all requirements, it's clear (how i didn't think of it i can't imagine), and it's simple in the config file.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.