1

I'm attempting to write a function that creates a new subclass named with the string it gets passed as an argument. I don't know what tools would be best for this, but I gave it a shot in the code below and only managed to make a subclass named "x", instead of "MySubClass" as intended. How can I write this function correctly?

class MySuperClass:
    def __init__(self,attribute1):
        self.attribute1 = attribute1

def makeNewClass(x):
    class x(MySuperClass):
        def __init__(self,attribute1,attribute2):
            self.attribute2 = attribute2

x = "MySubClass"
makeNewClass(x)
myInstance = MySubClass(1,2)
user3150635
  • 509
  • 2
  • 9
  • 26
  • 3
    Use the `type` built-in function. It's a dynamic form of a class statement. https://docs.python.org/2/library/functions.html#type You can pass in a string name as the first parameter, base classes as the second parameter, and a namespace dict as the 3rd parameter. – Shashank Mar 28 '15 at 19:43
  • 1
    You have to use Python [metaclasses](http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python) – Łukasz Rogalski Mar 28 '15 at 19:43

2 Answers2

1

The safest and easiest way to do this would be to use the type builtin function. This takes an optional second argument (tuple of base classes), and third argument (dict of functions). My recommendation would be the following:

def makeNewClass(x):
    def init(self,attribute1,attribute2):
        # make sure you call the base class constructor here 
        self.attribute2 = attribute2

    # make a new type and return it
    return type(x, (MySuperClass,), {'__init__': init})

x = "MySubClass"
MySubClass = makeNewClass(x)

You will need to populate the third argument's dict with everything you want the new class to have. It's very likely that you are generating classes and will want to push them back into a list, where the names won't actually matter. I don't know your use case though.


Alternatively you could access globals and put the new class into that instead. This is a really strangely dynamic way to generate classes, but is the best way I can think of to get exactly what you seem to want.

def makeNewClass(x):
    def init(self,attribute1,attribute2):
        # make sure you call the base class constructor here 
        self.attribute2 = attribute2

    globals()[x] = type(x, (MySuperClass,), {'__init__': init})
Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
0

Ryan's answer is complete, but I think it's worth noting that there is at least one other nefarious way to do this besides using built-in type and exec/eval or whatever:

class X:
    attr1 = 'some attribute'

    def __init__(self):
        print 'within constructor'

    def another_method(self):
        print 'hey, im another method'

# black magics
X.__name__ = 'Y'
locals()['Y'] = X
del X

# using our class
y = locals()['Y']()
print y.attr1
y.another_method()

Note that I only used strings when creating class Y and when initializing an instance of Y, so this method is fully dynamic.

Shashank
  • 13,713
  • 5
  • 37
  • 63
  • I'm still trying to figure out exactly what this does. I understand everything except the parts with locals(). Doesn't the locals() function return a dictionary of the local variables in a given function? and what notation are you using with locals()['y']()? That looks like a reference to a list item in between two function calls. please explain so I can understand black magics too. – user3150635 Mar 29 '15 at 09:45
  • @user3150635 The ['Y'] part isn't used to reference a list. Lists in Python can only have integer indexes. Dictionaries on the other hand can take integers, strings, and anything else that correctly implements the hashable interface as valid keys. You are right, locals() returns a dictionary, then I take that dictionary and access the value for key 'Y', which we previously set in the earlier line to be the value of our class Y. Finally, I instantiate the class with the final set of parentheses. – Shashank Mar 29 '15 at 15:52