In [12]:
class TicTacToe(object):
    template = '''{}|{}|{}
-+-+-
{}|{}|{}
-+-+-
{}|{}|{}'''

    def __init__(self):
        """
        Initialise le jeu du morpion.
        """
        self.grid = [
            [None, None, None],
            [None, None, None],
            [None, None, None],
        ]

    def play(self, symbol, x, y):
        """
        Place le symbol du joueur à l'emplacement x, y de la grille de jeu.
        
        Arguments :
        - symbol : caractère à placer, idéalement 'X' ou 'O'
        - x : ligne sur laquelle placer le caractère
        - y : colonne sur laquelle placer le caractère
        
        Throws :
        - AssertionError : si l'emplacement désigné est déjà pris
        - IndexError : si l'emplacement est en dehors de la grille
        """
        assert self.grid[x][y] is None, "Can't play where the other player played"
        self.grid[x][y] = symbol

    @property
    def inline_grid(self):
        
        inline = []
        for row in self.grid:
            for cell in row:
                inline.append(cell)
        return inline
    
    def is_finished(self):
        """
        Dis si le jeux est fini : retourne True si toutes les cellules sont remplies, et False dans l'autre cas
        """
        return None not in self.inline_grid

    def _check_triplet(self, triplet):
        if triplet[0] == triplet[1] == triplet[2]:
            return triplet[0]
        return None

    @property
    def columns(self):
        return list(zip(*self.grid))

    @property
    def diags(self):
        diags = [[], []]
        for i in range(3):
            diags[0].append(self.grid[i][i])
            diags[1].append(self.grid[i][-i])
        return diags

    def find_winner(self):
        """
        Cherche si un des joueurs a gagné. Retourne son symbole si c'est le cas, et retourne None sinon.
        """
        for triplet in self.grid + self.columns + self.diags:
            winner = self._check_triplet(triplet)
            if winner:
                return winner
        return None

    def display(self):
        """
        Affiche la grille de jeu
        """
        args = [cell or ' ' for cell in self.inline_grid]
        print(self.template.format(*args))

    def main(self):
        """
        Exécute le jeu en entier : à chaque tour :
        - affiche le plateau
        - affiche le symbole du joueur qui doit jouer
        - propose de saisir le numéro de la colonne et de la ligne où mettre le symbole
        
        Affiche les messages d'erreur si il y a besoin.
        
        Quand un joueur gagne, affiche le nom du joueur qui a gagné et quitte.
        
        Quand le plateau est plein, affiche que personne n'a gagné et quitte.
        """
        player = 'X'
        while not (tictactoe.find_winner() or tictactoe.is_finished()):
            self.display()
            print('Joueur ' + player)
            y = input('Colonne : ')
            x = input('Ligne : ')
            try:
                self.play(player, int(x) - 1, int(y) - 1)
            except AssertionError:
                print('Cette case est déjà prise')
                continue
            except (ValueError, IndexError):
                print('Vous devez saisir une colonne et une ligne entre 1 et 3')
                continue
            if player == 'X':
                player = 'O'
            else:
                player = 'X'
        winner = self.find_winner()
        if winner:
            print(winner + ' a gagné')
        else:
            print('Personne n\'a gagné')
  
tictactoe = TicTacToe()
tictactoe.main()