Контроль того, что возвращается методами класса в Python - PullRequest
0 голосов
/ 14 февраля 2019

Моя цель - создать группу (производных) классов, которые возвращают одни и те же структурированные данные / переменные при вызове их основного метода "run".Другими словами, когда вызывается метод run, он должен возвращать «a, b, c, d, e, f, g» и так далее.

Для демонстрации я придумал пример игры, в которой базовый класс равен Enemy, а два возможных подкласса - Kobold и Crow.Основной метод "run" - do_battle, и результат этого сражения должен возвращать player_hp_lost и gold_dropped, а также любую дополнительную информацию, которая хранится в other.

Здесь это в коде

import abc                                                                                                                                                                                   
from typing import Dict                                                                                                                                                                      

class Enemy(abc.ABC):                                                                                                                                                                        
    def __init__(self, health: int, armor: int):                                                                                                                                             
        self.health                                                                                                                                                                          
        self.armor                                                                                                                                                                           

    def generate_battle_results(self,                                                                                                                                                        
                                player_hp_lost: int,                                                                                                                                         
                                loot_dropped: int,                                                                                                                                           
                                other: Dict):                                                                                                                                                
        battle_results = {                                                                                                                                                                   
            "player_hp_lost": player_hp_lost,                                                                                                                                                
            "gold_dropped": loot_dropped,                                                                                                                                                    
            "other": other                                                                                                                                                                   
            }                                                                                                                                                                                

    @abc.abstractmethod                                                                                                                                                                      
    def do_battle(self)                                                                                                                                                                      


class Kobold(Enemy)                                                                                                                                                                            
    def __init__(self, rage_level: int, *args, **kwargs):                                                                                                                                    
        self.rage_level = rage_level                                                                                                                                                         
        super().__init__(*args, **kwargs)                                                                                                                                                    

    def do_battle(self):                                                                                                                                                                     
        # some convoluted logic that results in player hp lost, loot dropped, etc                                                                                                            
        player_hp_lost = 5                                                                                                                                                                   
        gold_dropped = 0                                                                                                                                                                     
        other = {"time taken to finish battle": 10,                                                                                                                                          
                 "fun had": 2                                                                                                                                                                
        }                                                                                                                                                                                    

        return self.generate_battle_results(player_hp_lost=player_hp_lost,                                                                                                                   
                                            gold_dropped=gold_dropped,                                                                                                                       
                                            other=other)                                                                                                                                     

def Crow(Enemy)                                                                                                                                                                              
    def __init__(self, num_feathers: int, *args, **kwargs):                                                                                                                                  
        self.num_feathers = num_feathers                                                                                                                                                     
        super().__init__(*args, **kwargs)                                                                                                                                                    

    def do_battle(self):                                                                                                                                                                     
        # some convoluted logic that results in player hp lost, loot dropped, etc                                                                                                            
        player_hp_lost = 3                                                                                                                                                                   
        gold_dropped = 1                                                                                                                                                                     
        other = {"kaw kaws before death": 10,                                                                                                                                                
                 "estimated annoyance factor":6                                                                                                                                              
        }                                                                                                                                                                                    

        return self.generate_battle_results(player_hp_lost=player_hp_lost,                                                                                                                   
                                            gold_dropped=gold_dropped,                                                                                                                       
                                            other=other)

В приведенном выше примере я хочу, чтобы do_battle всегда возвращал player_hp_lost и gold_dropped.Тем не менее, обратите внимание, что пользователь может создать новый подкласс Enemy и не вызывать self.generate_battle_results и вернуть диктовку по своему выбору, чего я хочу избежать.Есть ли способ сделать это?

Ответы [ 3 ]

0 голосов
/ 14 февраля 2019

Вы можете определить конкретный do_battle, который вызывает generate_battle_results, а затем потребовать, чтобы пользователь реализовал какой-то другой метод, который возвращает значения, которые передаются этому методу.

class Enemy:
    ...
    def do_battle(self):
        return self.generate_battle_results(**self.simulate_combat())
    @abstractmethod
    def simulate_combat(self):
        raise NotImplementedError()

Теперь simulate_combat метод должен возвращать dict, содержащий эти 3 значения, или вызов generate_battle_results будет жаловаться на отсутствующие аргументы.

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

0 голосов
/ 14 февраля 2019

Один из способов сделать это - предложить метод, который подклассы могут переопределять, и обернуть этот метод в другой в родительском классе для проверки на наличие ошибок:

class Enemy(abc.ABC):
    def __init__(self, health: int, armor: int):
        self.health = health
        self.armor = armor

    def do_battle(self):
        data = self.subclass_do_battle()

        # check that all mandatory parts are present
        for k in ('player_hp_lost', 'gold_dropped'):
            # this may raise KeyError
            _ = data[k]

        return data

    @abc.abstractmethod
    def subclass_do_battle(self):
        return {}

class Kobold(Enemy):
    def __init__(self, rage_level: int, *args, **kwargs):
        self.rage_level = rage_level
        super().__init__(*args, **kwargs)

    def subclass_do_battle(self):
        # some convoluted logic that results in player hp lost, loot dropped, etc

        return {
            'player_hp_lost': 5,
            'gold_dropped': 0,
            'time taken to finish battle': 10,
            'fun had': 2,
        }

class Crow(Enemy):
    def __init__(self, num_feathers: int, *args, **kwargs):
        self.num_feathers = num_feathers
        super().__init__(*args, **kwargs)

    def subclass_do_battle(self):
        # some convoluted logic that results in player hp lost, loot dropped, etc

        return {
            'player_hp_lost': 5,
            'gold_dropped': 0,
            'kaw kaws before death': 9,
            'estimated annoyance factor': 6,
        }

Как видите, подклассыдолжен реализовывать только Enemy.subclass_do_battle(), а родительский класс Enemy.do_battle() будет вызывать реализованный метод подкласса и проверять возвращаемые данные, прежде чем возвращать их конечному вызывающему.

И нет способа избежать переопределения метода Enemy.do_battle()Python мы обычно работаем под предлогом того, что мы все взрослые;этот язык не предназначен для того, чтобы вы могли сделать методы приватными, как в других языках (например, Java).

0 голосов
/ 14 февраля 2019

Тем не менее, обратите внимание, что пользователь может создать новый подкласс Enemy и не вызывать self.generate_battle_results и вернуть диктовку по своему выбору, чего я хочу избежать.

Тот, кто собирается использовать ваш код, может сделать гораздо больше.Например, пользователь может создать свой собственный класс, вообще не используя класс Enemy.Я имею в виду, пользователь может делать буквально все, не беспокоясь о том, что вы хотите.Я предполагаю, что это верно для любого языка программирования, особенно если доступен исходный код.

Есть ли способ сделать это?

Я считаю, что это невозможно в Python.

...