1

In the following snippet I'm trying to define a factory function which would return objects of different classes derived from Hero based on arguments.

class Hero:
    Stats = namedtuple('Stats', ['health', 'defence', 'attack',
                                 'mana', 'experience'])
    RaceMaxStats = OrderedDict([
        ('Knight', Stats(100, 170, 150, 0, inf)),
        ('Barbarian', Stats(120, 150, 180, 0, inf)),
        ('Sorceress', Stats(50, 42, 90, 200, inf)),
        ('Warlock', Stats(70, 50, 100, 180, inf))
    ])

    @staticmethod
    def def_race(race: str):
        return type(race, (Hero,), {'max': Hero.RaceMaxStats[race]})

    Races = OrderedDict([
        (race, Hero.def_race(race)) for race in RaceMaxStats.keys()
    ])

    def __init__(self, lord, health, defence, attack, mana, experience):
        self.race = self.__class__.__name__
        self.lord = lord
        self.stats = Hero.Stats(min(health, self.max.health),
                                min(defence, self.max.defence),
                                min(attack, self.max.attack),
                                min(mana, self.max.mana),
                                min(experience, self.max.experience))

    @staticmethod
    def summon(race, *args, **kwargs):
        return Hero.Races[race](*args, **kwargs)

With the intention of later using it like so:

knight = Hero.summon('Knight', 'Ronald', 90, 150, 150, 0, 20)
warlock = Hero.summon('Warlock', 'Archibald', 50, 50, 100, 150, 50)

The problem is that I cannot initialize the subclasses because Hero is not yet defined:

    (race, Hero.def_race(race)) for race in RaceMaxStats.keys()
NameError: name 'Hero' is not defined

Obviously, if I replaced the static method call with the direct type() call I would still need Hero to be defined. My question is how do I best implement this kind of factory. The priority is for the summon() method to retain the same signature, and to return instances of classes derived from Hero.

P.S. none of the code above has ever been run successfully, so it may contain other mistakes.

3 Answers 3

2

You can use classmethods and define yours Races variable as a method which caches its result after its first call in a class variable. It would look like that:

@classmethod
def def_race(cls, race: str):
    return type(race, (cls,), {'max': cls.RaceMaxStats[race]})

_Races = None

@classmethod
def Races(cls, race):
    if cls._Races is None:
        cls._Races = OrderedDict([
           (race, cls.def_race(race)) for race in cls.RaceMaxStats.keys()
        ])
    return cls._Races[race]

@classmethod
def summon(cls, race, *args, **kwargs):
    return cls.Races(race)(*args, **kwargs)
Sign up to request clarification or add additional context in comments.

2 Comments

The def_race classmethod is okay, but summon requires Races to be defined, which is the class variable I'm having a problem with. It should contain subclasses of Hero, but those cannot be created at that point, since Hero is not yet defined.
You can define races as another classmethod, which sets the class variable if is not already done and return the correct race from the class variable once it is set.
0

After the class definition, do Hero.knight = Hero.summon(...) and so on.

Comments

0

Can you try:

Hero('Knight', 'Ronald', 90, 150, 150, 0, 20).summon()

alternatively:

hero = Hero('Knight', 'Ronald', 90, 150, 150, 0, 20)
hero.summon()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.