12

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)
6
  • 3
    What are the semantics here? Do you want "conditional inheritance" or do you want "conditional initialization"? Commented Sep 16, 2015 at 1:47
  • 2
    What happens if use3 = C(a=True, b=True)? Commented Sep 16, 2015 at 1:56
  • 2
    What ever you are trying to do here, either you are explaining it poorly or it is a horrible idea. Say what you are trying to accomplish and there is very likely a better way. Commented Sep 16, 2015 at 2:09
  • 1
    I suspect you'd have a lot better luck reversing your class hierarchy. That is, have C be a base class (with whatever methods and such you always want to be able to use) and A and B should be subclasses that specialize the class for a specific situations (e.g. self.history being a specific type). It doesn't make any sense the way you have it set up now. Commented Sep 16, 2015 at 2:58
  • 1
    @user3790927 adding excess flexibility usually means you've not defined your problem well enough. We write code in order to tame the complexity of the task at hand. Do remember the "You Ain't Gonna Need It" principle; if you forget YAGNI, the tendency is to have a class do everything and then no one will be able to make use of it because you've made it too complicated. Commented Sep 16, 2015 at 4:05

3 Answers 3

18

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
Sign up to request clarification or add additional context in comments.

Comments

3

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)

2 Comments

Thank you. Is it possible I am missing something to multiple inheritance. I feel what I am working on is going to shoot off in so many different directions that I don't want it to become impossible to understand. For instance there is going to be many different types of histories and also many types of results for those histories and I want to assemble those and still leave room for customization. What should I be googling?
@user3790927 I don't know what you should google. I'm assuming that for some reason you can't change A and B, and you need the functionality of both. What you should do is select the answer that best answers your question as you've given it (you'll get plus 2 to your rep for doing so), and think some more about what you want to do and try to better communicate that in your next question, or visit us in the python chat room when you get enough rep to be able to do so. chat.stackoverflow.com/rooms/6/python
0

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
>>>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.