Excellent question. If this were Java, then yes, you would need to declare the variable as a member variable (or "field") of the class.
However, since this is Python, you can actually attach new attributes (member variables) to an object at any point, and in any method. In this case, the bestAction attribute should get assigned in the setBestAction() method.
The error you are getting is because the setBestAction() method is not called before getBestAction() gets called.
To fix this, you can simply add this line of code
curNode.setBestAction(2)
inside of the MiniMaxAIPlayer.getMaxValue()
method.
However, this will always choose to play in column 2, rather than choosing the action appropriately based on which action maximizes the node's value. Still, it will get rid of the error that you are seeing.
(And yes, you could also put something like self.bestAction = -1
into the constructor for GameStateNode, but then you'll get another error that -1 is not a valid column index to play, etc. I thought that leaving the code so that it generates the error would emphasize to students how important it is that the getMaxValue() method must call the setBestAction() method or else the AI won't know where to play.