ChaimG
ChaimG

Reputation: 7514

How to dynamically create a class in Python with an initializer

Here is some code to start with:

def objectify(name, fields):
    """ Create a new object including the __init__() method. """
    def __init__(self, *argv):
        for name, val in zip(var_names, argv):
            setattr(self, name, val)

    # The following line of code is currently limited to a single dynamic class.
    # We would like to extend it to allow creating multiple classes
    # and each class should remember it's own fields.
    __init__.var_names = fields

    result = type(name, (object,), dict(__init__=__init__))

The challenge here is to find a way to make unique copies of the __init__() method of each class which has a static list of its variable names.

Plan B: We can do this using eval() to run code that a function generates. But eval() is to be avoided wherever possible. The challenge here is to do this without eval().

EDIT: While writing up the question I came up with a solution. (See below.) Maybe this will help someone.

EDIT2: I would use this function to create something like a namedtuple(), except that they are mutable.

Point = objectify('point', ['x', 'y'])
a = Point(1, 2)
b = Point(2, 3)
print a.__dict__
print b.__dict__

Upvotes: 0

Views: 93

Answers (2)

viraptor
viraptor

Reputation: 34175

You don't mention anything about the usage of fields later on. If you only need them in __init__, you don't need to save them at all:

def objectify(name, fields):
    """ Create a new object including the __init__() method. """
    fields = fields[:]
    def __init__(self, *argv):
        for name, val in zip(fields, argv):
            setattr(self, name, val)

    result = type(name, (object,), dict(__init__=__init__))
    return result

Otherwise, you should look at metaclasses - that's exactly the usecase for them.

Updated: making a copy of fields ensures that changing the list in the caller will not affect the stored one. The values can still change... left as an exercise to the reader to verify everything is a str.

Upvotes: 3

ChaimG
ChaimG

Reputation: 7514

Here is one solution:

def objectify(obj_name, fields):
    """ Create a new object including the __init__() method. """

    def __init__(self, *argv):
        """ Generic initializer for dynamically created classes. """
        fields = objectify.fields[self.__class__.__name__]
        for field, val in zip(fields, argv):
            setattr(self, field, val)

    result = type(obj_name, (object,), dict())
    result.__init__ = __init__

    # Save the list of fields in a static dictionary that is retrieved by class name.
    objectify.fields[obj_name] = fields

    return result

objectify.fields = {}           # A static local variable.

Upvotes: 0

Related Questions