Как вызвать метод в дочернем классе из родительского экземпляра? - PullRequest
0 голосов
/ 09 марта 2020

Как мне создать экземпляр Stats и вызвать метод в GameStats? Как ниже:

class Stats():

    """Object to instansiate a league and season pair 
    Args:
        league(str): league_id
        season(str): season_id
    """
    fb = Football()
    dir = Directory()
    def __init__(self, league='EN_PR', season='2019/2020'):
        self.pool = multiprocessing.cpu_count()
        self.league = league
        self.season = season

    def load_season_fixture(self):
        """Loads the fixtures for a league-season,
        calls api_scraper.py methods
        """
        self.fb.load_leagues()
        self.fb.leagues[self.league].load_seasons()
        return self.fb.leagues[self.league].seasons[self.season].load_played_fixtures()

    def load_season_players(self):
        """Loads the players for a league-season,
        calls api_scraper.py methods
        """
        player_id = []
        self.fb.load_leagues()
        self.fb.leagues[self.league].load_seasons()
        teams = self.fb.leagues[self.league].seasons[self.season].load_teams()
        for team in tqdm(teams.values()):
            players = self.fb.leagues[self.league].seasons['2019/2020'].teams[team['shortName']].load_players()
        for player in players.keys():
            player_id.append(player)
        return player_id

    def load_season_teams(self):
        """Loads the teams for a league-season,
        calls api_scraper.py methods
        """
        player_id = []
        self.fb.load_leagues()
        self.fb.leagues[self.league].load_seasons()
        return self.fb.leagues[self.league].seasons[self.season].load_teams()

class GameStats(Stats):
    def __init__(self, *args, **kwargs):
        super().__init__()
        self.fixture_id = [fix['id'] for fix in self.load_season_fixture().values()]

    def fixture_stats_singel(self, fixture):
        """Gets stats for a fixture"""
        ds = load_match_data(f'https://footballapi.pulselive.com/football/stats/match/{fixture}')
        return ds

    def fixture_stats(self):
        """Gets stats for all fixtures in a league-season using multithreading
        saves output in a json file.

        """
        stats = {}
        with Pool(self.pool) as p:
            fixture_stats = list(tqdm(p.imap(self.fixture_stats_singel, self.fixture_id, chunksize=1), total=len(self.fixture_id)))
        i = 0
        for fixture in fixture_stats:
            game_id = fixture['entity']['id'] #Get's the gameIDs for each game
            index = game_id #Set's gameIDs as index for dictionairy
            stats[index] = {'info': fixture['entity']}

        print(f'Saved as {filename}.json in {path}')

class PlayerStats(Stats):
    def __init__(self, *args, **kwargs):
        super().__init__()
        self.player_id = self.load_season_players()

    def player_stats_singel(self, player):
        #NEED TO HAVE SEASON ID
        season_id = self.fb.leagues[self.league].seasons[self.season]['id']
        ds = load_match_data(
            f'https://footballapi.pulselive.com/football/stats/player/{player}?compSeasons={season_id}')
        return ds

    def player_stats(self):
        stats = {}
        with Pool(self.pool) as p:
            player_stats = list(tqdm(p.imap(self.player_stats_singel, self.player_id, chunksize=1), total=len(self.player_id)))
        all_players = player_stats
        i = 0
        for player in all_players:
            game_id = int(player['entity']['id']) #Get's the gameIDs for each game
            index = game_id #Set's gameIDs as index for dictionairy
            stats[index] = {'info': player['entity']}

        print(f'Saved as {filename}.json in {path}')

class TeamStandings(Stats):
    def __init__(self, *args, **kwargs):
        super().__init__()
        self.team_id = [fix['id'] for fix in self.load_season_teams().values()]

    def team_standings_singel(self, team_id):
        #NEED TO HAVE SEASON ID
        season_id = self.fb.leagues[self.league].seasons[self.season]['id']
        ds = load_match_data(
            f'https://footballapi.pulselive.com/football/compseasons/{season_id}/standings/team/{team_id}')
        return ds

    def team_standings(self):
        stats = {}
        with Pool(self.pool) as p:
            team_standings = list(tqdm(p.imap(self.team_standings_singel, self.team_id, chunksize=1), total=len(self.team_id)))
        i = 0
        team_standing = team_standings
        for team in team_standing:
            team_id = int(team['team']['club']['id']) #Get's the gameIDs for each game
            index = team_id #Set's gameIDs as index for dictionairy
            if 'compSeason' in team:
                stats[index] = {'season': team['compSeason']}

        print(f'Saved as {filename}.json in {path}')

#What I'm doing right now
d = TeamStandings()
d.team_standings()
e = PlayerStats()
e.player_stats()
f = GameStats()
f.fixture_stats()

#What I want to do
d.Stats()
d.team_standings()
d.player_stats()
d.fixture_stats()

Ответы [ 3 ]

1 голос
/ 09 марта 2020

Это плохой дизайн. Поэтому мой ответ - просто , вы не должны этого делать .

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

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

Существуют приемлемые конструкции, если вы хотите построить объект базового класса и позже использовать методы из подкласса:

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

    d = Stats()
    # additional Stats operation on d...
    (GameStats(d)).fixture_stats()
    
  • используйте __getattr__, который будет загружать методы из дополнительных миксинов (ручной эквивалент вышеуказанный дизайн)

    class Base:
        def set_mixin(self, mixin):
            self.mixin = mixin
        def __getattr__(self, attr):
            return functools.partial(getattr(self.mixin, attr), self)
    
    class M1:
        """Mixin for class Base: only contains methods that apply on Base objects"""
        def __init__(self, *args, **kwargs):
            raise TypeError("Cannot create M1 objects")
        def foo(self, x):
            print(x, 'on', self)
    
    b = Base()
    b.set_mixin(M1)
    b.foo('x')
    

    напечатает

    x on <__main__.Base object at 0x...>
    

    Но ИМХО это сомнительный дизайн и должен использоваться только для особых случаев использования.

1 голос
/ 09 марта 2020

«Простое лучше, чем сложное» и «Практичность превосходит чистоту» - PEP 20

#What I want to do
d.Stats()
d.team_standings()
d.player_stats()
d.fixture_stats()

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

class SeasonStats:
    ...
    def __init__(self, league='EN_PR', season='2019/2020'):
        ...

        ### Included from '__init__'s of previous subclasses ###
        self.fixture_ids = [fix['id'] for fix in self.load_season_fixture().values()]
        self.team_ids = [team['id'] for team in self.load_season_teams().values()]
        self.player_ids = self.load_season_players()

    def load_season_fixture(self):
        ...
    def load_season_players(self):
        ...
    def load_season_teams(self):
        ...
    def fixture_stats_singel(self, fixture):
        ...
    def fixture_stats(self):
        ...
    def player_stats_singel(self, player):
        ...
    def player_stats(self):
        ...
    def team_standings_singel(self, team_id):
        ...
    def team_standings(self):
        ...

Обратите внимание, что я слегка изменил имена переменных, включенные в предыдущие подклассы __init__ s

И тогда, действительно, просто используя его как:

season_19_20 = SeasonStats(season='2019/2020')
season_19_20.team_standings()
season_19_20.player_stats()
season_19_20.fixture_stats()

Некоторые рекомендации по именованию переменных

d, e, f

Я бы посоветовал не использовать a, b, c, et c ... Память дешевая, а экраны достаточно большие: не бойтесь использовать значимые имена, такие как season.

Списки множественного числа

Говоря о списке идентификаторов, вы, естественно, уже используете множественные идентификаторы. Называя вашу переменную как таковую, вы можете легко различать guish между списками вещей и отдельными вещами. Это также делает очень естественным использование, например, в for -loops:

team_ids = [team['id'] for team in ...]
for team_id in team_ids:
    do_something_with(team_id)
1 голос
/ 09 марта 2020

С учетом контекста из комментариев:

Итак ... вы не хотите иметь дочерний объект, вы просто хотите иметь больше методов в исходном классе? Но не помещая эти методы в исходный класс?

@ h4ze, это звучит правильно! Мне нужна фрагментация

Если вы явно укажете имя класса, вы можете передать что-нибудь как self.

d = Stats()
GameStats.fixture_stats(d)

Это называется утиной печатью - нас не волнует фактическая тип объекта, нас интересует, что он делает: «если он плавает как утка и крякает как утка, это утка».

Это позволяет вам пропустить любой объект в любом месте ... Но это также означает, что объект должен «плавать» и «крякать», как изначально ожидаемый объект, верно?

Мы знаем, что все GameStats объекты являются Stats объектами (потому что наследуются), но не наоборот. - Это означает, что ваши методы в GameStats могут использовать только то, что есть у Stats.


Но только то, что вы можете сделать что-то, не означает, что вы должны . Вы не можете использовать потенциал своего дочернего класса - вы просто используете класс в качестве хранилища методов для другого класса!


Лучше было бы сделать это наоборот - использовать мульти-наследование. Каждый родительский класс будет иметь то, что необходимо для выполнения его действий (и, конечно, вы установите все эти части в вашем дочернем init) и эти действия - таким образом, минимизируете необходимость перезаписи методов в child.

Подумайте об этом как и интерфейсы в других языках, но с уже реализованными методами (обычно вы должны реализовать интерфейс - вызывая больше вещей в классе).


Или просто сделать нормальные функции , документ (в строках документации), что они должны взять данный объект и использовать его. Вы можете хранить эти функции в разных модулях, предоставляя им разные пространства имен - обеспечивая желаемую «фрагментацию».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...