self
will cause a variable to become attributed to an instance of the class, not the class itself. I don't know if you meant that or not, but it's certainly worth thinking about.
Variables in the class-wide scope can be divided into two categories: class and instance variables. Class variables are defined at the beginning of the class definition, outside of any method. If a variable is constant for all instances, or it is only used in class/static methods, it should be a class variable. Often, such variables are true constants, though there are numerous cases where they aren't. Instance variables are generally defined in __init__
, but there are numerous cases where they should be defined elsewhere. That being said, if you don't have a good reason not to, define instance variables in __init__
, as this keeps your code (and class) organized. It is perfectly acceptable to give them placeholder values (such as None
), if you know the variable is essential to the state of the instance but its value is not determined until a certain method is called.
Here's a good example:
class BaseGame:
"""Base class for all game classes."""
_ORIGINAL_BOARD = {(0,0): 1, (2,0): 1, (4,0): 1, (6,0): 1, (8,0): 1,
(1,2): 1, (3,2): 1, (5,2): 1, (7,2): 1, (2,4): 1,
(4,4): 1, (6,4): 1, (3,6): 1, (5,6): 1, (4,8): 0}
_POSSIBLE_MOVES = {(0,0): ((4,0),(2,4)),
(2,0): ((4,0),(2,4)),
(4,0): ((-4,0),(4,0),(2,4),(-2,4)),
(6,0): ((-4,0),(-2,4)),
(8,0): ((-4,0),(-2,4)),
(1,2): ((4,0),(2,4)),
(3,2): ((4,0),(2,4)),
(5,2): ((-4,0),(-2,4)),
(7,2): ((-4,0),(-2,4)),
(2,4): ((4,0),(2,4),(-2,-4),(2,-4)),
(4,4): ((-2,-4,),(2,-4)),
(6,4): ((-4,0),(-2,4),(-2,-4),(2,-4)),
(3,6): ((-2,-4),(2,-4)),
(5,6): ((-2,-4),(2,-4)),
(4,8): ((-2,-4),(2,-4))}
started = False
def __call__(self):
"""Call self as function."""
self.started = True
self.board = __class__._ORIGINAL_BOARD.copy()
self.peg_count = 14
self.moves = []
@staticmethod
def _endpoint(peg, move):
"""Finds the endpoint of a move vector."""
endpoint = tuple(map(add, peg, move))
return endpoint
@staticmethod
def _midpoint(peg, move):
"""Finds the midpoint of a move vector."""
move = tuple(i//2 for i in move)
midpoint = tuple(map(add, peg, move))
return midpoint
def _is_legal(self, peg, move):
"""Determines if a move is legal or not."""
endpoint = self._endpoint(peg, move)
midpoint = self._midpoint(peg, move)
try:
if not self.board[midpoint] or self.board[endpoint]:
return False
else:
return True
except KeyError:
return False
def find_legal_moves(self):
"""Finds all moves that are currently legal.
Returns a dictionary whose keys are the locations of holes with
pegs in them and whose values are movement vectors that the pegs
can legally move along.
"""
pegs = [peg for peg in self.board if self.board[peg]]
legal_moves = {}
for peg in pegs:
peg_moves = []
for move in __class__._POSSIBLE_MOVES[peg]:
if self._is_legal(peg, move):
peg_moves.append(move)
if len(peg_moves):
legal_moves[peg] = peg_moves
return legal_moves
def move(self, peg, move):
"""Makes a move."""
self.board[peg] = 0
self.board[self._midpoint(peg, move)] = 0
self.board[self._endpoint(peg, move)] = 1
self.peg_count -= 1
self.moves.append((peg, move))
def undo(self):
"""Undoes a move."""
peg, move = self.moves.pop()
self.board[peg] = 1
self.board[self._midpoint(peg, move)] = 1
self.board[self._endpoint(peg, move)] = 0
self.peg_count += 1
def restart(self):
"""Restarts the game."""
self.board = __class__._ORIGINAL_BOARD.copy()
self.peg_count = 14
self.moves.clear()
_ORIGINAL_BOARD
and _POSSIBLE_MOVES
are true constants. While started
is not a constant, as its value depends on whether the __call__
method was invoked or not, its default value, False
, IS constant for all instances, so I declared it as a class variable. Notice that in __call__
(don't worry about why I used __call__
instead of __init__
), I redefined it as an instance variable, as __call__
starts the game, and therefore when it is invoked, the instance's state has changed from the class default, "not started", to "started".
Also notice that the other methods besides __call__
regularly change the value of the instance variables, but that they are not initially defined in said methods, as there is no compelling reason for them to be.