Python: динамически создавать подкласс в теле класса - PullRequest
0 голосов
/ 04 мая 2018

В следующем фрагменте я пытаюсь определить фабричную функцию, которая будет возвращать объекты различных классов, полученных из Hero на основе аргументов.

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)

С намерением позже использовать его так:

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

Проблема в том, что я не могу инициализировать подклассы, потому что Hero еще не определено:

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

Очевидно, что если бы я заменил статический вызов метода прямым вызовом type(), мне все равно потребовалось бы определить Hero. Мой вопрос заключается в том, как лучше всего реализовать такую ​​фабрику. Приоритет состоит в том, чтобы метод summon() сохранял ту же сигнатуру и возвращал экземпляры классов, производных от Hero.

P.S. ни один из приведенных выше кодов не был успешно выполнен, поэтому он может содержать другие ошибки.

Ответы [ 4 ]

0 голосов
/ 04 мая 2018

Сейчас я прибегну к использованию метода summon(), который создает подкласс и добавляет его к Races, если его еще нет. Спасибо уравнениям за это предложение.

Races = OrderedDict()

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

@staticmethod
def summon(race: str, *args, **kwargs):
    try:
        hero = Hero.Races[race](*args, **kwargs)
    except KeyError:
        hero = Hero.def_race(race)(*args, **kwargs)
        Hero.Races[race] = hero.__class__
    return hero
0 голосов
/ 04 мая 2018

Вы можете использовать classmethods и определить вашу Races переменную как метод, который кэширует свой результат после первого вызова в переменной класса. Это будет выглядеть так:

@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)
0 голосов
/ 04 мая 2018

Можете ли вы попробовать:

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

альтернативно:

hero = Hero('Knight', 'Ronald', 90, 150, 150, 0, 20)
hero.summon()
0 голосов
/ 04 мая 2018

После определения класса выполните Hero.knight = Hero.summon(...) и т. Д.

...