Python Forum
dynamically creating a subclass - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: General Coding Help (https://python-forum.io/forum-8.html)
+--- Thread: dynamically creating a subclass (/thread-6333.html)



dynamically creating a subclass - sidereal - Nov-16-2017

I have a situation where I know what superclass I am creating (using the superclass sort of like a java abstract class), and will be receiving an string argument to control which type of subclass I return. Here is a simplified snippet that accomplishes what I'm trying to do:

class Person:
    def __init__(self, pet_types):
        self.pets = [Pet().factory(t) for t in pet_types]
      
        
    def get_pets(self):
        print([pet.name for pet in self.pets])
        
    
class Pet:
    def factory(self, t):
        klass = next((cls for cls in self.__class__.__subclasses__() if cls.__name__ == t), self.__class__)
        return klass()
        
    def __init__(self):
        self.name = 'unknown'
        
        
class Cat(Pet):
    def __init__(self):
        self.name = 'Garfield'
    
class Dog(Pet):
    def __init__(self):
        self.name = 'Fido'

Person(['Cat', 'Dog', 'Bird']).get_pets()
It works, but I'm wondering if there is a more pythonic (or just, programming practices in general) way of doing this. Or if this is a "code smell". For context, I'm receiving a json file which defines object types.


RE: dynamically creating a subclass - nilamo - Jan-04-2018

Do they HAVE to be strings? I think the more pythonic way would be to just pass classes that should be instanciated:
Person([Cat, Dog, Bird]).get_pets()



RE: dynamically creating a subclass - Gribouillis - Jan-04-2018

If you really need to convert strings, you could use a metaclass to collect the subclasses

class PetMeta(type):
    pet_classes = {}

    def __new__(cls, clsname, superclasses, attributedict):
        if clsname in cls.pet_classes:
            raise ValueError(
                "Redefinition of Pet subclass '{}'".format(clsname))
        tp = type.__new__(cls, clsname, superclasses, attributedict)
        cls.pet_classes[clsname] = tp
        return tp

def pet_class(name):
    return PetMeta.pet_classes[name]

class Person:
    def __init__(self, pet_types):
        self.pets = [pet_class(t)() for t in pet_types]

class Pet(metaclass=PetMeta): pass
class Dog(Pet): pass
class Cat(Pet): pass
class Bird(Pet): pass

john = Person(['Dog', 'Cat'])
print(john.pets)
The drawback of __subclasses__() is that it only knows direct subclasses.