Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to create subclasses on-the-fly?

I'm creating a game in which I have a somewhat complex method for creating entities.

When a level is loaded, the loading code reads a bunch of YAML files that contain attributes of all the different possible units. Using the YAML file, it creates a so-called EntityResource object. This EntityResource object serves as the authoritative source of information when spawning new units. The goal is twofold:

  1. Deter cheating by implementing a hash check on the output of the YAML file
  2. Aid in debugging by having all unit information come from a single, authoritative source.

These EntityResource objects are then fed into an EntityFactory object to produce units of a specific type.

My question is as follows. Is there a way to create sublcasses of EntityResource dynamically, based on the contents of the YAML file being read in?

Also, I would like each of these YAML-file-derived subclasses to be assigned a singleton metaclass. Any caveats?

like image 298
Louis Thibault Avatar asked Feb 13 '12 23:02

Louis Thibault


2 Answers

I'm not sure if this is what you're looking for, but you can use type to create subclasses dynamically:

SubClass = type('SubClass', (EntityResource,), {})

Edit: To understand how type works, you just need to translate how would you write the class and translate that into a type call. For example, if you want to write something like:

class SubClass(EntityResource):
    A=1
    B=2

then, that would be translated to:

 SubClass = type('SubClass', (EntityResource,), {'A': 1, 'B': 2})

where:

  • The first argument is just the class name
  • The second argument is the list of parent classes
  • The third argument is the dictionary initialize the class object. This includes not only class attributes, but also methods.
like image 109
jcollado Avatar answered Oct 05 '22 17:10

jcollado


It is possible to create subclasses on the fly. This does not mean that you should. In any case, I will provide a mechanism.

The bases attribute in each class tells you the inheritance chain:

class Animal(object):
    pass

class Dog(Animal):
    pass

print Animal.__bases__
print Dog.__bases__
# prints:
#(<type 'object'>,)
#(<class '__main__.Animal'>,)

So, __bases__ is a tuple with the "inheritance bases". You can replace this tuple (you cannot "append to it" or "pop from it" because it is a a tuple, and tuples are immutable). For example, say that you have a "mixin class" which adds functionalities to some animal subclasses but not others:

class Animal(object):
    pass

class Dog(Animal):
    pass

class Cat(Animal):
    pass

class TalkMixin(object):
    def talk(self):
        print("I talk like a {0}".format(self.__class__.__name__))

if __name__ == "__main__":

    dog = Dog()
    cat = Cat()

    try:
        dog.talk() 
        cat.talk()
    except AttributeError:
        print("Great - the program raised AttributeError, as expected")

    # now, add the MixIn to the class "Dog", but not to the class
    # "Cat" - do this on the fly:
    old_dog_bases = Dog.__bases__
    Dog.__bases__ = (Animal, TalkMixin)

    # this should be successful!
    dog.talk()

    try:
        cat.talk()
    except AttributeError:
        print("As expected, cat.talk() raised AttributeError")

    # now do the same (add the mixin dynamically - via inheritance) to
    # the Cat
    old_cat_bases = Cat.__bases__
    Cat.__bases__ = (Animal, TalkMixin)

    # this should be successful!
    cat.talk()

    # Now, remove the mixin (drop from the inheritance) for both cats
    # and dogs:
    Dog.__bases__ = old_dog_bases
    Cat.__bases__ = old_cat_bases

    try:
        dog.talk()
        cat.talk()
    except AttributeError:
        print("as expected, they can no longer talk")

Produces the following output:

Great - the program raised AttributeError, as expected
I talk like a Dog
As expected, cat.talk() raised AttributeError
I talk like a Cat
as expected, they can no longer talk

A lot of people consider MixIn classes evil. And they may be right! You can imagine that if you mess up the bases attribute, you pretty much destroyed your program. So, there it is - you can dynamically change the inheritance of an object, but it does not mean you should (probably abstract classes or concept implementation?)

like image 20
Escualo Avatar answered Oct 05 '22 18:10

Escualo