You can use list comprehension as well as the built-in all() and any() methods to make your is_winner() method more concise and readable:
def is_winner(self, player):
"""Returns True if the player won and False otherwise."""
row1 = [self.board[val] for val in ["TL","TM","TR"]]
row2 = [self.board[val] for val in ["ML","MM","MR"]]
row3 = [self.board[val] for val in ["BL","BM","BR"]]
col1 = [self.board[val] for val in ["TL","ML","BL"]]
col2 = [self.board[val] for val in ["TM","MM","BM"]]
col3 = [self.board[val] for val in ["TR","MR","BR"]]
dia1 = [self.board[val] for val in ["TL","MM","BR"]]
dia2 = [self.board[val] for val in ["TR","MM","BL"]]
wins = [row1, row2, row3, col1, col2, col3, dia1, dia2]
isPlayer = lambda cell: cell == player.type
isWinner = lambda seq: all(map(isPlayer, seq))
if any(map(isWinner, wins)):
''' Returns true if any possible win is a win '''
return True
else:
return False
The code to get all of the possible win combinations could have been made more concise if the board was represented by a 2D array rather than a dict as you have it; but the explicit nature of assigning the values of each row still has its benefits.
Messed around and made a more DRY way of building the wins array, but the nested list comprehension is arguably less readable/understandable. Nonetheless, here it is just for fun:
wins = [[self.board[cell] for cell in seq]
for seq in [
["TL","TM","TR"], # Row 1
["ML","MM","MR"], # Row 2
["BL","BM","BR"], # Row 3
["TL","ML","BL"], # Col 1
["TM","MM","BM"], # Col 2
["TR","MR","BR"], # Col 3
["TL","MM","BR"], # Dia 1
["TR","MM","BL"] # Dia 2
]
]