voland
voland

Reputation: 61

Python subclass factory

I have a number of subclasses of a single parent class and want to instantiate them according to strings in serialized data. My approach is to map the instantiating strings to classes and then create corresponding objects using a factory method:

class Superclass:
    @classmethod
    def get_instance(cls, string):
        return CLASSMAP[string]()

    def name(self):
        pass

class Foo(Superclass):
    def name(self):
        return "my name is Foo"

class Bar(Superclass):
    def name(self):
        return "i'm Bar"

class Baz(Superclass):
    def name(self):
        return "i am called Baz"

CLASSMAP = {
    'foo': Foo,
    'bar': Bar,
    'baz': Baz,
}

I don't like having the CLASSMAP as a global dictionary, but I cannot define it as a member of Superclass right in the declaration due to the subclasses not having been defined yet (and they have to be defined after the superclass, of course). I can of course assign the class map into the class afterwards:

Superclass.CLASSMAP = {...}

and have Superclass' get_instance() method refer to the not-yet-existing field, but it seems even clunkier and prone to errors.

Is there a more Pythonic approach to the whole pattern?

Upvotes: 6

Views: 3296

Answers (1)

user2390182
user2390182

Reputation: 73450

If you make your super class a new-style class inheriting from object, you can solve this dynamically, using cls.__subclasses__():

class Superclass(object):
    @classmethod
    def get_instance(cls, string):
        return next(c for c in cls.__subclasses__() if c.__name__.lower() == string)()

A mixed approach combining the advantage of adding classes dynamically while still having O(1) class lookup:

class Superclass(object):
    @classmethod
    def get_instance(cls, string):
        return MAPPING[string]()

class Foo(Superclass):
    lookup = 'foo'

class Bar(Superclass):
    lookup = 'bar'

MAPPING = {c.lookup: c for c in Superclass.__subclasses__()}

Upvotes: 3

Related Questions