Joseph Boyd
Joseph Boyd

Reputation: 588

Is there a way to reference an object's base __class__ from class scope?

I have a series of classes all, inheriting from one of several possible parents which all share a base class. Each class has a class-scope Dict[str, object] parameters which is based on the base class parameters

from copy import deepcopy


class BaseClass:
    parameters = {
        'example_param1': ParamObject(name='example_param1', editable=True),
    }


class MiddleClass(Baseclass):
    parameters = {
        **deepcopy(Baseclass.parameters),
        'example_param2': ParamObject(name='example_param2', editable=False),
    }


class ChildClass(MiddleClass):
    parameters = {
        **deepcopy(MiddleClass.parameters),
        'example_param3': ParamObject(name='example_param3', editable=True),
    }

This implementation does the job, but I find the **deepcopy(Baseclass.parameters), line unsatisfying. These child classes are going to be edited over time by someone with only a basic understanding of coding so I want to make the code as simple and cut-and-pasteable as possible.

Is there anything I can call at class scope to get the equivilant of super().__class__? I want the user to be able to change the base class from, say, MiddleClass to MiddleClass2 without needing to remember to change the base class in multiple locations.

Upvotes: 3

Views: 611

Answers (4)

João Haas
João Haas

Reputation: 2132

You can use Metaclasses, such as the other answers suggested, but I'm not sure how easier that would be for a new programmer to understand.

You can access the classes a class inherithed from by calling __bases__ on the class. Sadly though, on python you cannot reference the class you're creating on the class body, meaning you can't do:

class A:
   bases = A.__bases__  # Invalid code, A is not defined yet

If you want to do this, while mantaining parameters static, you can do this after the class definition, and it will have the exact behaviour you're looking for, although a bit ugly:

class BaseClass:
    parameters = {
        'example_param1': ParamObject(name='example_param1', editable=True),
    }


class MiddleClass(Baseclass):
    pass

MidleClass.parameters = {
    **deepcopy(MidleClass.__bases__[0].parameters),
    'example_param2': ParamObject(name='example_param2', editable=False),
}

Now, to improve things a bit, if your parameters is really always a 'Dict[str, object]', you don't need to do a deepcopy, since using the ** acts in a functional way. You can also handle multiple parent classes using the __bases__ value, to ensure every value is being captured. So, if I were to do this, I would do something like this:

def base_parameters(cls):
    parameters = {}
    for base in cls.__bases__:
        parameters.update(base.parameters)

    return parameters


class BaseClass:
    parameters = {
        'example_param1': ParamObject(name='example_param1', editable=True),
    }


class MiddleClass(BaseClass):
    pass

MiddleClass.parameters = {
    **base_parameters(MiddleClass),
    'example_param2': ParamObject(name='example_param2', editable=False),
}


class ChildClass(MiddleClass):
    pass

ChildClass.parameters = {
    **base_parameters(ChildClass),
    'example_param3': ParamObject(name='example_param3', editable=True),
}

Upvotes: 0

chepner
chepner

Reputation: 531075

You can use __init_subclass__ to add parameters from parent classes directly to the explicitly defined parameters attribute, at the cost of the insertion order being reversed (should that matter).

class ParameterBase:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        for pc in cls.__bases__:
            d = getattr(pc, 'parameters', {})
            cls.parameters.update(d)

class BaseClass(ParameterBase):
    parameters = {
        'example_param1': ParamObject(name='example_param1', editable=True),
    }


class MiddleClass(BaseClass):
    parameters = {
        'example_param2': ParamObject(name='example_param2', editable=False),
    }


class ChildClass(MiddleClass):
    parameters = {
        'example_param3': ParamObject(name='example_param3', editable=True),
    }

Upvotes: 5

flakes
flakes

Reputation: 23624

You could do this with a metaclass. Check all of the classes in the inheritance tree to see if they have the parameters field. If they do, then merge them together and set the property on the class instance.

class ParamMeta(type):
    def __init__(cls, name, bases, dct):
        class_types = [cls] + list(bases)
        parameters = {}
        for class_type in class_types:
            if hasattr(class_type, "parameters"):
                parameters.update(class_type.parameters)
        cls.parameters = parameters
        super().__init__(name, bases, dct)

Example:

class Foo(metaclass=ParamMeta):
    parameters = {"a": "b"}


class Bar(Foo):
    pass


class Fizz(Bar):
    parameters = {"c": "d"}


print(Foo.parameters)
print(Bar.parameters)
print(Fizz.parameters)

Outputs:

{'a': 'b'}
{'a': 'b'}
{'c': 'd', 'a': 'b'}

Upvotes: 2

ForceBru
ForceBru

Reputation: 44838

If your class inherits from only one class, you can use a metaclass:

class Meta(type):
    def __new__(cls, name, parents, namespace):
        namespace['parameters'] = {
            **deepcopy(parents[0].parameters),
            **namespace['parameters']
        }

        return super().__new__(cls, name, parents, namespace)

class MiddleClass(Baseclass, metaclass=Meta):
    parameters = {
        'example_param2': ParamObject(name='example_param2', editable=False),
    }

This is of course a simplified version that doesn't handle multiple parent classes, for example.

Upvotes: 0

Related Questions