views:

211

answers:

2

Hi everyone,

I want to add class atttributes to a superclass dynamically. Furthermore, I want to create classes that inherit from this superclass dynamically, and the name of those subclasses should depend on user input.

There is a superclass "Unit", to which I can add attributes at runtime. This already works.

def add_attr (cls, name, value):
    setattr(cls, name, value)

class Unit(object):
    pass

class Archer(Unit):
    pass

myArcher = Archer()
add_attr(Unit, 'strength', 5)
print "Strenght ofmyarcher: " + str(myArcher.strength)
Unit.strength = 2
print "Strenght ofmyarcher: " + str(myArcher.strength)

This leads to the desired output:
Strenght ofmyarcher: 5
Strenght ofmyarcher: 2

But now I don't want to predefine the subclass Archer, but I'd rather let the user decide how to call this subclass. I've tried something like this:

class Meta(type, subclassname):
    def __new__(cls, subclassname, bases, dct):
    return type.__new__(cls, subclassname, Unit, dct)

factory = Meta()    
factory.__new__("Soldier")  

but no luck. I guess I haven't really understood what new does here. What I want as a result here is

class Soldier(Unit):
    pass

being created by the factory. And if I call the factory with the argument "Knight", I'd like a class Knight, subclass of Unit, to be created.

Any ideas? Many thanks in advance!
Bye
-Sano

+2  A: 

Have a look at the type() builtin function.

knight_class = type('Knight', (Unit,), {})
  • First parameter: Name of new class
  • Second parameter: Tuple of parent classes
  • Third parameter: dictionary of class attributes.

But in your case, if the subclasses don't implement a different behaviour, maybe giving the Unit class a name attribute is sufficient.

Felix Kling
Thank you, Felix, what you suggested works as well!
Sano98
+4  A: 

To create a class from a name, use the class statement and assign the name. Observe:

def meta(name):
    class cls(Unit):
        pass

    cls.__name__ = name
    return cls

Now I suppose I should explain myself, and so on. When you create a class using the class statement, it is done dynamically-- it is equivalent of calling type().

For example, the following two snippets do the same thing:

class X(object): pass
X = type("X", (object,), {})

The name of a class-- the first argument to type-- is assigned to __name__, and that's basically the end of that (the only time __name__ is itself used is probably in the default __repr__() implementation). To create a class with a dynamic name, you can in fact call type like so, or you can just change the class name afterward. The class syntax exists for a reason, though-- it's convenient, and it's easy to add to and change things later. If you wanted to add methods, for example, it would be

class X(object):
    def foo(self): print "foo"

def foo(self): print "foo"
X = type("X", (object,), {'foo':foo})

and so on. So I would advise using the class statement-- if you had known you could do so from the beginning, you likely would have done so. Dealing with type and so on is a mess.

(You should not, by the way, call type.__new__() by hand, only type())

Devin Jeanpierre
Thank you Devin! Both your and Felix' solution seem to have a side effect I haven't considered: The new class is an object and needs to be hooked to something - stored in a varibale or appended to a list or something. I can't just create the Knight class and then call the constructor with myKnight = Knight(). Rather, I have to do something like this:unitlist.append(meta("Knight"))myKnight = unitlist[0]()Is there a way arround this? To put it bluntly: Save the Knight class at the same "general position" the Unit class is saved when it's definition is reached in the code?
Sano98
@Sano98: I'm really not sure what you mean. You can do `Knight = meta("Knight")` and then do `myKnight = Knight()`. What do you want, exactly?
Devin Jeanpierre
Thanks, that's exactly what I want to do! Simply save it under Knight... And then it's in the global namespace and I can use the class as if it was hardcoded. Great, thank you. Just didn't think I could call Knight() when saving the class as Knight, but sure, why not?
Sano98