skip to Main Content

I’m writing an API parsing Twitter bot and am very new to OOP. I have some existing Python code that relies on global variables and figured I could take this opportunity to learn.

I have the following Team class that gets updated when the API is parsed and is like to be able to call a totally unrelated (external) method when a class attribute changes.

class Team(object):
  def __init__(self, team_name, tri_code, goals, shots, goalie_pulled):
    self.team_name = team_name
    self.tri_code = tri_code
    self.goals = goals
    self.shots = shots
    self.goalie_pulled = goalie_pulled

When goalie_pulled is changed for an existing instance of Team I’d like the following method to be called (pseudo code):

def goalie_pulled_tweet(team):
  tweet = "{} has pulled their goalie with {} remaining!".format(team.team_name, game.period_remain)
  send_tweet(tweet)

Two things –

  1. How do I call goalie_pulled_tweet from within my Team class once I detect that goalie_pulled attribute has changed?
  2. Can I access an instance of my Game object from anywhere or does it need to be passed to that variable as well?

2

Answers


  1. You should take a look at the property class. Basically, it lets you encapsulate behaviour and private members without the consumer even noticing it.

    In your example, you may have a goalie_pulled property:

    class Team(object):
        def __init__(self, team_name, tri_code, goals, shots, goalie_pulled):
            # Notice the identation here. This is very important.
            self.team_name = team_name
            self.tri_code = tri_code
            self.goals = goals
            self.shots = shots
    
            # Prefix your field with an underscore, this is Python standard way for defining private members
            self._goalie_pulled = goalie_pulled
    
        @property
        def goalie_pulled(self):
            return self._goalie_pulled
    
        @goalie_pulled.setter
        def goalie_pulled(self, new_value):
            self._goalie_pulled = new_value
            goalie_pulled_tweet(self) #self is the current Team instance
    

    From the consumer’s point of view:

    team = create_team_instance()
    
    # goalie_pulled_tweet is called
    team.goalie_pulled = 'some_value'
    

    I’d recommend you to use properties whenever you can (and must), as they are a nice way of abstraction.

    Login or Signup to reply.
  2. From a design standpoint, it would make more sense to have a pull_goalie method.

    Classes are a tool to create more meaningful abstractions. Pulling a goalie is an action. If you think of Team as representing a real-life team, it makes more sense to say “The team pulled their goalie!” rather than “The team set their pulled-goalie attribute to X player!”

    class Team(object):
        ...
    
        def pull_goalie(self, player):
            self.pulled_goalie = player
            tweet = '<your format string>'.format(
                self.pulled_goalie,
                # Yes, your Team *could* store a reference to the current game.
                # It's hard to tell if that makes sense in your program without more context.
                self.game.period_remaining  
            )
    

    I was going to recommend a property, but I think that would solve the immediate problem without considering broader design clarity.

    NOTE: There is no such thing as a “private” attribute in Python. There is a convention that attributes beginning with a single underscore (self._pulled_goalie) is treated as private, but that’s just so that people using your code know that they can’t depend on that value always doing what they think it will. (i.e., it’s not part of the public contract of your code, and you can change it without warning.)

    EDIT: To create a register_team method on a Game object, you might do something like this:

    class Game(object):
        def __init__(<stuff>):
            ...
            self.teams = {}
    
        ...
    
        def register_team(self, team):
            if len(self.teams) > 1:
                raise ValueError(
                    "Too many teams! Cannot register {} for {}"
                    .format(team, game)
                )
    
            self.teams[team] = team
            team.game = self
    
        def unregister_team(self, team):
            try:
                del self.teams[team]
            except KeyError:
                pass
    
            team.game = None
    

    Note that by using a dictionary, register_team and unregiser_team can be called multiple times without ill effect.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search