conditional class inheritance in python

Question:

I am trying to dynamically create classes in Python and am relatively new to classes and class inheritance. Basically I want my final object to have different types of history depending on different needs. I have a solution but I feel there must be a better way. I dreamed up something like this.

class A:
    def __init__(self):
        self.history={}
    def do_something():
        pass

class B:
    def __init__(self):
        self.history=[]
    def do_something_else():
        pass

class C(A,B):
    def __init__(self, a=False, b=False):
        if a:
            A.__init__(self)
        elif b:
            B.__init__(self)

use1 = C(a=True)
use2 = C(b=True)
Asked By: jamesRH

||

Answers:

I’m assuming that for some reason you can’t change A and B, and you need the functionality of both.

Maybe what you need are two different classes:

class CAB(A, B): 
    '''uses A's __init__'''

class CBA(B, A):
    '''uses B's __init__'''

use1 = CAB()
use2 = CBA()

The goal is to dynamically create a class.

I don’t really recommend dynamically creating a class. You can use a function to do this, and you can easily do things like pickle the instances because they’re available in the global namespace of the module:

def make_C(a=False, b=False):
    if a:
        return CAB()
    elif b:
        return CBA()

But if you insist on “dynamically creating the class”

def make_C(a=False, b=False):
    if a:
        return type('C', (A, B), {})()
    elif b:
        return type('C', (B, A), {})()

And usage either way is:

use1 = make_C(a=True)
use2 = make_C(b=True)

You probably don’t really need that, and this is probably an XY problem, but those happen regularly when you are learning a language. You should be aware that you typically don’t need to build huge class hierarchies with Python like you do with some other languages. Python employs “duck typing” — if a class has the method you want to use, just call it!

Also, by the time __init__ is called, the instance already exists. You can’t (easily) change it out for a different instance at that time (though, really, anything is possible).

if you really want to be able to instantiate a class and receive what are essentially instances of completely different objects depending on what you passed to the constructor, the simple, straightforward thing to do is use a function that returns instances of different classes.

However, for completeness, you should know that classes can define a __new__ method, which gets called before __init__. This method can return an instance of the class, or an instance of a completely different class, or whatever the heck it wants. So, for example, you can do this:

class A(object):
    def __init__(self):
        self.history={}
    def do_something(self):
        print("Class A doing something", self.history)

class B(object):
    def __init__(self):
        self.history=[]
    def do_something_else(self):
        print("Class B doing something", self.history)

class C(object):
    def __new__(cls, a=False, b=False):
        if a:
            return A()
        elif b:
            return B()

use1 = C(a=True)
use2 = C(b=True)
use3 = C()

use1.do_something()
use2.do_something_else()

print (use3 is None)

This works with either Python 2 or 3. With 3 it returns:

Class A doing something {}
Class B doing something []
True
Answered By: Patrick Maupin

I was thinking about the very same thing and came up with a helper method for returning a class inheriting from the type provided as an argument.

The helper function defines and returns the class, which is inheriting from the type provided as an argument.

The solution presented itself when I was working on a named value class. I wanted a value, that could have its own name, but that could behave as a regular variable. The idea could be implemented mostly for debugging processes, I think. Here is the code:

def getValueClass(thetype):
    """Helper function for getting the `Value` class

    Getting the named value class, based on `thetype`.
    """

    # if thetype not in (int, float, complex):  # if needed
        # raise TypeError("The type is not numeric.")

    class Value(thetype):

        __text_signature__ = "(value, name: str = "")"
        __doc__ = f"A named value of type `{thetype.__name__}`"

        def __init__(self, value, name: str = ""):
            """Value(value, name) -- a named value"""

            self._name = name

        def __new__(cls, value, name: str = ""):

            instance = super().__new__(cls, value)

            return instance

        def __repr__(self):

            return f"{super().__repr__()}"

        def __str__(self):

            return f"{self._name} = {super().__str__()}"

    return Value

Some examples:

IValue = getValueClass(int)
FValue = getValueClass(float)
CValue = getValueClass(complex)

iv = IValue(3, "iv")
print(f"{iv!r}")
print(iv)
print()

fv = FValue(4.5, "fv")
print(f"{fv!r}")
print(fv)
print()

cv = CValue(7 + 11j, "cv")
print(f"{cv!r}")
print(cv)
print()

print(f"{iv + fv + cv = }")

The output:

3
iv = 3

4.5
fv = 4.5

(7+11j)
cv = (7+11j)

iv + fv + cv = (14.5+11j)

When working in IDLE, the variables seem to behave as built-in types, except when printing:

>>> vi = IValue(4, "vi")
>>> vi
4
>>> print(vi)
vi = 4
>>> vf = FValue(3.5, 'vf')
>>> vf
3.5
>>> vf + vi
7.5
>>>
Answered By: khaz