简体   繁体   中英

Call Python Method on Class Attribute Change

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?

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.

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. (ie, 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.

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