Search code examples
pythonclassinheritancegraphsubclassing

Python: how to assign a name of a list variable to a class


Basically, I have a list of lists that looks like this:

lst = [['B', 'A'], ['C', 'B'], ['D', 'B'], ['E','C D'] ......]

Where t[0] supposed to be a subclass of t[1] (from which it inherits).

I need to write a script that creates empty classes with names matching lst and then checks whenever a t[i-1] is a subclass of t[i]:

if issubclass(B, A)== True:
   print('Yes')
   

So far, I understand how to begin and how to finish the script but I'm completely unaware how to assign a name from a list so that it would be possible to use issubclass. Maybe there are other ways of creating classes and tracing their inheritance?

I've just started learning classes and OOP today, so it's more than possible that I missed something crucial while dealing with this problem.


Solution

  • The usual way we define classes is with the class keyword, naturally.

    class B(A):
        some_variable = 100
    

    Effectively, we're constructing a new type in the Python runtime. And, in fact, types have their own constructor in Python; it's called type and we can call it directly. The above declaration is roughly equivalent to

    B = type('B', ('A',), { 'some_variable': 100 })
    

    And now the type names are strings. There's just one more piece of the puzzle we'll need. We want to take and assign it to the name B, using a string so we can do so with names that aren't known in advance. Assuming you want to do this at module-scope, we can use globals, which returns a dictionary of the current module's top-level variables, which we can freely modify to add more variables. So we can do

    globals()['B'] = type('B', ('A',), { 'some_variable': 100 })
    

    Now, let's put those pieces together and write a script that uses the lst list you suggested.

    lst = [['B', 'A'], ['C', 'B'], ['D', 'B'], ['E','C D']]
    
    # Iterate over the list.
    for class_name, superclass_names in lst:
        # Here, we're going to lookup all of the superclass names in the
        # current global scope. It's a little more complicated than that,
        # since we also want to *create* any names that don't exist (like
        # 'A' in your example) when we find them.
        superclasses = []
        for superclass_name in superclass_names.split(' '):
            # If the name doesn't exist, create it and assume its
            # supertype is object, the root of Python's type hierarchy.
            if superclass_name not in globals():
                globals()[superclass_name] = type(superclass_name, (object,), {})
            # Get the class, whether it's the one we just made or one that
            # already exists.
            superclasses.append(globals()[superclass_name])
        # Now we construct the new class. The first argument to type() is
        # the class name, the second is all of the superclasses (it must
        # be a tuple, not a list, according to the documentation, so we
        # convert it), and finally the contents. Since you just want the
        # classes themselves, I'll assume the contents are meant to be
        # empty. You can easily change that as needed.
        globals()[class_name] = type(class_name, tuple(superclasses), {})
    
    # Now let's see that everything is defined correctly. __mro__ is a
    # complicated concept, but the basic idea is that it should give us E,
    # followed by all of its subclasses in a reasonable order (for some
    # definition of reasonable).
    print(E.__mro__)
    

    Try it online!