2014-11-04 5 views
0

Неверное название правильное, но сложно задавать вопрос о чем-то, когда вы не знаете, как его назвать.изменение базового класса через «данные, определенные оверлеями»

Представьте, что вы есть класс что-то вроде

class Creature(): 
    def __init__(self): 
     self.str = 13 
     self.dex = 10 
     ... 

Это существо будет дано и другие определения, которые влияют на это. В этом случае представьте, что объекту существа дана гонка орков. Орк увеличил бы ее на 2. Возможно, существо может получить несколько из этих модифицирующих свойств шаблонов.

Есть ли способ организовать код, который сделает этот элегантный, pythonic, простой в обслуживании и, возможно, управляемый данными?

ответ

1

Здесь Orc унаследован от Creature и увеличивает его str от 2:

class Creature(): 
    def __init__(self): 
     self.str = 13 
     self.dex = 10 

class Orc(Creature): 
    def __init__(self): 
     Creature.__init__(self) 
     self.str += 2 

Вот один класс, который будет управлять им все:

class Creature(): 
    d_str = {None:13, "Orc":15} 
    d_dex = {None:10, "Orc":10} 

    def __init__(self, race=None): 
     self.race = race 
     self.str = self.d_str[race] 
     self.dex = self.d_dex[race] 
+0

Класс, чтобы управлять ими, все умнее. Полагаю, я мог бы установить self.race и сделать эту работу очень хорошо. Хорошее предложение. Благодарю. – robbie

+0

@robbie Хорошая точка. Я добавил «self.race» к образцу кода. – John1024

+0

Вы действительно не должны называть 'Существо .__ init__' напрямую. Используйте 'super'. (Если это код Python 2.x, это также означает использование 'class Creature (object):', но вы все равно хотите сделать это изменение ...) В противном случае представьте, как бы вы определили «OrcBarbarian», «Орк» и «Варвар». – abarnert

2

Во-первых, вот как вы бы это сделать без с его данными:

class Orc(Creature): 
    def __init__(self): 
     super().__init__() 
     self.str += 2 
     self.race = 'Orc' 

Но что, если вы хотите, чтобы данные управлялись данными? Предположим, что данные просто хранятся в таблице, которая отображает имена расы в dicts, которые сами отображают имена атрибутов для бонусов. Как это:

races = { 
    'Human': {}, 
    'Orc': {'str': +2, 'cha': -1}, 
    'Elf': {'str': -1, 'dex': +1, 'int': +1} 
} 

Это могут быть сохранены в формате JSON файл или базу данных SQLite, или все, что вы хотите; он не должен быть буквами dict, встроенными в ваш исходный код. Я просто делаю так, чтобы простой пример.

Мы могли бы программно создать Human, Orc и Elf подклассы, но, вероятно, нет веской причины. Все, что нам действительно нужно, это фабричная функция, которая создает экземпляр гонки, и после этого все они действуют одинаково. Итак:

def create_creature(race): 
    bonuses = races.get(race, {}) 
    creature = Creature() 
    for attr, bonus in bonuses.items(): 
     setattr(creature, attr, getattr(creature, attr) + bonus) 
    creature.race = race 
    return creature 

Единственная хитрость в том, что setattr линии. Ключ в том, что мы не знаем имя атрибута во время написания кода, мы знаем его только во время выполнения, но мы все же хотим иметь возможность получить или установить значение этого атрибута. Обычно вы не хотите использовать setattr и getattr, но если вам нужно динамически обращаться к атрибутам по имени, это именно то, для чего они предназначены.

Однако стоит отметить, что есть еще одна альтернатива. Насколько ваш код на самом деле полагается на них, являются атрибутами класса Creature? Могли ли они быть членами дикта?

class Creature: 
    def __init__(self): 
     self.stats = {'str': 13, 
         'dex': 10, 
         # ... 
        } 

Если это так, то это некрасиво setattr линии становится намного лучше:

creature.stats[attr] += bonus 

С другой стороны, некоторые другой код в вашей программе может стать уродливым. Может быть, вместо этого:

if player.str + roll(20) > enemy.str + roll(20): 

... Вы должны написать:

if play.stats['str'] + roll(20) > enemy.stats['str'] + roll(20) 

Этот компромисс почти всегда приходит с объектами, управляемыми данными: одна часть вашей программы хочет, чтобы рассматривать их как данные, другая часть хочет относиться к ним как к объектам, и вы должны уравновесить их.

+0

Это был один из способов, которым я был знаком. Рад видеть, что кто-то порекомендовал это, поскольку он проверяет, что я думал. Я ожидал, что какая-то схема наследования классов будет лучше, но ваш пример действительно имеет большой смысл. Кроме того, это позволяет мне применять больше, чем участвовать в гонке за существо для создания или модификации свойств. Спасибо за предложение. – robbie

+0

@robbie: Необходимо изучить, как многократное наследование работает для статического случая в Python, прежде чем создавать динамический случай. Например, представьте, что у вас был «ElfWizard», который является подклассом как «Elf», так и «Wizard». Если вы используете 'super', вместо явного вызова подкласса,' Creature .__ init__' будет вызван ровно один раз, тогда каждый подкласс '__init__' получит свое изменение, чтобы изменить значения, точно однократно - именно то, что вы хотите. – abarnert

Смежные вопросы