0

I am trying to make a for loop, which iterates through a range, and makes classes. How could I do this? How could the classes be named? Here is what I have and mean.

for i in range(6):
    class Name(str(i)):     #I want the class name to be Name and then the number as a string. So the classes will be called 'Name1', Name2' etc
        pass

I'm making an RPG type game and I want a range of different monster types. I want to be able to generate classes for each monster type. I want each monster to be a level higher than the last so the health and other stats will be multiplied by the previous monster's stats

myhotchoc
  • 11
  • 1
  • 6
  • You want to name the objects like that, or write code to generate classes for you to copy/paste or something? – Jacobr365 Feb 19 '16 at 22:40
  • Basically, I'm making an RPG type game and I want a range of different monster types. I want to be able to generate classes for each monster type. I want each monster to be a level higher than the last so the health and other stats will be multiplied by the previous monster's stats. – myhotchoc Feb 19 '16 at 22:44
  • The base class of a class has to be a *reference* to a class, not a string containing its name. – chepner Feb 19 '16 at 22:48
  • Also, this loop would simply overwrite the class defined in the previous iteration. – chepner Feb 19 '16 at 22:48
  • @myhotchoc: If the classes are programmatically generated, how are you making them have different behaviors? If they don't have different behaviors, is there some reason you couldn't just use a single class with a `level` attribute? – ShadowRanger Feb 19 '16 at 22:50
  • 1
    `[type(str(i), (), {}) for i in range(6)] ` – dawg Feb 19 '16 at 22:53

6 Answers6

2

To answer the question specifically, you would use the 3 argument form of type to create a metaclass:

>>> classes=[type('Name'+str(i), (), {}) for i in range(6)]
>>> classes
[<class '__main__.Name0'>, <class '__main__.Name1'>, <class '__main__.Name2'>, <class '__main__.Name3'>, <class '__main__.Name4'>, <class '__main__.Name5'>]
>>> classes[0].__name__
'Name0'

The form Bar=type('Bar', (), {}) is analogous to:

class Foo:
    pass

Instantiating an instance would be:

>>> Bar=type('Bar', (), {})
>>> Bar()
<__main__.Bar object at 0x102c90fd0>

vs

>>> class Foo:
...    pass
... 
>>> Foo()
<__main__.Foo instance at 0x102cde5f0>
Community
  • 1
  • 1
dawg
  • 98,345
  • 23
  • 131
  • 206
1

If you want to make brand new classes with names as if defined manually, you're usually stuck with format-ing and eval-ing strings. This is how Python implements collections.namedtuple; it's implemented in Python code using a template string that it fills in programmatically then evals.

Take a look at the namedtuple implementation for an example of how you might do something like this.

You can also make classes programmatically using the three argument type constructor, which lets you explicitly provide a name, bases and class dictionary, so making Name# six times with no special base classes or class attributes or member functions could be made and assigned to globals via:

globals().update({name: type(name, (), {}) for name in  map('Name{}'.format, range(6))})
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
0

First things first: as other answers advised, be sure to think through this design decision. Creating classes in a loop is a possibly a red flag that your design is flawed. Moving on.

You can do this using metaclasses, or the type function. In fact, type is the default metaclass. It is the class of all built-in classes:

>>> print(type(int))
<class 'type'>

...it is the class of the basic object class:

>>> print(type(object))
<class 'type'>

...type is even its own class:

>>> print(type(type))
<class 'type'>

...and unless specified otherwise, all classes you create are themselves type objects:

>>> class MyClass:
        pass
>>> print(type(MyClass))
<class 'type'>

All metaclasses - including type - can be used to create classes. When used this way, type takes 3 arguments:

  1. class name (a string)
  2. a tuple containing the parent classes
  3. a dictionary containing class attributes/members

Probably the simplest way to accomplish your goal is to first create a dictionary to hold your classes:

Name = {(i + 1): None for i in range(6)}

We will populate the dictionary values using the type metaclass:

for num in Name:
    Name[num] = type(('Name' + str(i + 1)), (object,), {})

We can accomplish all of the above with this one-liner:

Name = {(i + 1): type(('Name' + str(i + 1)), (object,), {}) for i in range(6)}

In the example above we are inheriting from object and providing no class members, but this can be adjusted as needed.

If you need more customization in your dynamically created classes, a good option is to use a base class with the starting functionality you require:

class BaseMonster:
    def method1(self):
        # do stuff

Name = {(i + 1): type(('Name' + str(i + 1)), (BaseMonster,), {}) for i in range(6)}

n1 = Name[1]()
n1.method1()

Recall: type is the default metaclass. However, even more custimization can be accomplished by creating your own custom metaclass. You do this by inheriting a new class from type:

class MetaMonster(type):
    def __new__(mclass, number, bases, dct):
        name = 'Name' + str(number + 1)
        return super().__new__(mclass, name, (BaseMonter,) + bases, dct)

And use it like this:

Name = {(i + 1): MetaMonster(i, tuple(), {}) for i in range(6)}
n1 = Name[1]()
n1.method1()

Note that you no longer have to provide the BaseMonster argument, nor do you have to construct the string representing the class name; this is all taken care of in the MetaMonster.__new__ method.

Rick
  • 43,029
  • 15
  • 76
  • 119
0

One you create a class, it's available like any other symbol. So try this:

def make_class(name:str):
    class c:
        _classname = name
        pass

    return c

Now you have an object of type class (not really, but pretend), you can install it wherever you like:

import sys
my_namespace = sys.modules[__name__].__dict__

for i in range(1,3):
    name = "my_class_name_{}".format(i)
    cls = make_class(name)
    my_namespace[name] = cls


obj1 = my_class_name_1()
print(type(obj1), obj1._classname)
obj2 = my_class_name_2()
print(type(obj2), obj2._classname)
aghast
  • 14,785
  • 3
  • 24
  • 56
0

The more pythonic way would be a list of monster objects accessed like monsters[1]. You can do it like this.

monsters = []
for i in range(6):
    monsters.append(monster())

To do it your way you can see the other answers in the thread.

Jacobr365
  • 846
  • 11
  • 24
0

You can generate classes in a loop, but it is the sign of a bad design, and always can be avoided.

This code generates classes and stores them in an array

class Base():
  pass

classes = [Base]

for i in range(6):
  class Name(classes[-1]):
    pass
  classes.append(Name)

print(classes[0] != classes[-1]) # True
print(isinstance(classes[-1](), Base)) # True

It would be much better if all the monsters would be of the same type, and there would be some kind of "metaclass" for monster types. This means that you would have a dynamic structure which is language independent, can be changed even in runtime, and monster types can be loaded from resource files:

class Monster:
  def __init__(self, mtype):
    self.mtype = mtype
    self.health = mtype.max_health
    # ...

class MonsterType:
  def __init__(self, mtype = None):
    self.parent_type = mtype
    self.max_health = 10 if mtype is None else mtype.max_health + 10
    # ...

mtypes = [MonsterType()]
for i in range(10):
    mtypes.append(MonsterType(mtypes[-1]))

monster = Monster(mtypes[3])

# monster.health == 40
Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97