简体   繁体   中英

How to create a collection class which automatically adds new aggregation methods based on inherited class?

I have a class ( Team ) which is a collection of another class ( Player ). In the minimal example I simply have one property for Player , but my real world code has dozens of more complex properties, which I frequently add more to.

Currently every time I add a new property to Player , I need to manually add four new properties to Team , but I know there must be a better way to do this. I would like to have the Team class automatically add those four properties every time I create a new property in Player , vastly reducing the code I need to write, but I'm not sure the best way to go about this.

from statistics import mean, median


class Player:
    def __init__(self, name, age):
        self.name = name
        self._age = age

    @property
    def age(self):
        return self._age


class Team:
    def __init__(self, players):
        self.players = players

    @property
    def all_ages(self):
        return {player.name: player.age for player in self.players}

    @property
    def total_age(self):
        return sum([player.age for player in self.players])

    @property
    def average_age(self):
        return mean([player.age for player in self.players])
        
    @property
    def median_age(self):
        return median([player.age for player in self.players])


p1 = Player('John', 8)        
p2 = Player('Tim', 9)        
p3 = Player('Annie', 11)        
team = Team([p1, p2, p3])

As I said in a comment, there no inheritance involved between your collection class and the class of the object it contains. The means there will always be a certain amount of tedious repetitious coding required to set things — at least if you do it as shown below which makes use of class decorators — but once it's set-up, adding more properties should be relatively easy.

from statistics import mean, median


def make_stat_properties(team_cls, player_cls, name):
    storage_name = '_' + name

    @property
    def prop(self):
        return getattr(self, storage_name)
    @prop.setter
    def prop(self, value):
        setattr(self, storage_name, value)
    setattr(team_cls, name, prop)

    # Statistical methods.
    @property
    def all_func(self):
        return {getattr(player, 'name'): getattr(player, name) for player in self.players}
    func_name = f'all_{name}' if name.endswith('s') else f'all_{name}s'
    setattr(player_cls, func_name, all_func)

    @property
    def total_func(self):
        return sum(getattr(player, name) for player in self.players)
    setattr(player_cls, f'total_{name}', total_func)

    @property
    def average_func(self):
        return mean(getattr(player, name) for player in self.players)
    setattr(player_cls, f'average_{name}', average_func)

    @property
    def median_func(self):
        return median(getattr(player, name) for player in self.players)
    setattr(player_cls, f'median_{name}', median_func)

    return prop


class Team:
    def __init__(self, players):
        self.players = players


def stats_decorate(*names):
    def stats_prop_decorator(cls):
        """ Add stats properties to class. """
        for name in names:
            setattr(cls, name, make_stat_properties(cls, Team, name))
        def __init__(self, name, *args):
            self.name = name
            for name, arg in zip(names, args):
                setattr(self, f'_{name}', arg)
        setattr(cls, '__init__', __init__)
        return cls
    return stats_prop_decorator


@stats_decorate('age', 'goals')  # Add properties and related stat methods to class.
class Player:
    pass


if __name__ == '__main__':

    p1 = Player('John', 8, 1)
    p2 = Player('Tim', 9, 2)
    p3 = Player('Annie', 11, 3)
    team = Team([p1, p2, p3])

    print(f'{team.all_ages=}')
    print(f'{team.total_age=}')
    print(f'{team.average_age=:.2f}')
    print(f'{team.median_age=}')
    print()
    print(f'{team.all_goals=}')
    print(f'{team.total_goals=}')
    print(f'{team.average_goals=:.2f}')
    print(f'{team.median_goals=}')

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM