I have recently written this Minesweeper game in Python:
import random
class Cell(object):
    def __init__(self, is_mine, is_visible=False, is_flagged=False):
        self.is_mine = is_mine
        self.is_visible = is_visible
        self.is_flagged = is_flagged
    def show(self):
        self.is_visible = True
    def flag(self):
        self.is_flagged = not self.is_flagged
    def place_mine(self):
        self.is_mine = True
class Board(tuple):
    def __init__(self, tup):
        super().__init__()
        self.is_playing = True
    def __str__(self):
        board_string = ("Mines: " + str(self.remaining_mines) + "\n  " +
                        "".join([str(i) for i in range(len(self))]))
        for (row_id, row) in enumerate(self):
            board_string += "\n" + str(row_id) + " "
            for (col_id, cell) in enumerate(row):
                if cell.is_visible:
                    if cell.is_mine:
                        board_string += "M"
                    else:
                        board_string += str(self.count_surrounding(row_id,
                                                                   col_id))
                elif cell.is_flagged:
                        board_string += "F"
                else:
                    board_string += "X"
            board_string += " " + str(row_id)
        board_string += "\n  " + "".join([str(i) for i in range(len(self))])
        return board_string
    def show(self, row_id, col_id):
        if not self[row_id][col_id].is_visible:
            self[row_id][col_id].show()
            if (self[row_id][col_id].is_mine and not
                self[row_id][col_id].is_flagged):
                self.is_playing = False
            elif self.count_surrounding(row_id, col_id) == 0:
                [self.show(surr_row, surr_col) for (surr_row, surr_col) in
                 self.get_neighbours(row_id, col_id) if
                 self.is_in_range(surr_row, surr_col)]
    def flag(self, row_id, col_id):
        if not self[row_id][col_id].is_visible:
            self[row_id][col_id].flag()
        else:
            print("Cannot add flag, cell already visible.")
    def place_mine(self, row_id, col_id):
        self[row_id][col_id].place_mine()
    def count_surrounding(self, row_id, col_id):
        count = 0
        for (surr_row, surr_col) in self.get_neighbours(row_id, col_id):
            if (self.is_in_range(surr_row, surr_col) and
                self[surr_row][surr_col].is_mine):
                count += 1
        return count
    def get_neighbours(self, row_id, col_id):
        SURROUNDING = ((-1, -1), (-1,  0), (-1,  1),
                       (0 , -1),           (0 ,  1),
                       (1 , -1), (1 ,  0), (1 ,  1))
        neighbours = []
        for (surr_row, surr_col) in SURROUNDING:
            neighbours.append((row_id + surr_row, col_id + surr_col))
        return neighbours
    def is_in_range(self, row_id, col_id):
        return 0 <= row_id < len(self) and 0 <= col_id < len(self)
    @property
    def remaining_mines(self):
        remaining = 0
        for row in self:
            for cell in row:
                if cell.is_mine:
                    remaining += 1
                if cell.is_flagged:
                    remaining -= 1
        return remaining
    @property
    def is_solved(self):
        for row in self:
            for cell in row:
                if not(cell.is_visible or cell.is_flagged):
                    return False
        return True
def create_board(size, mines):
    board = Board(tuple([tuple([Cell(False) for i in range(size)])
                         for j in range(size)]))
    available_pos = list(range((size-1) * (size-1)))
    for i in range(mines):
        new_pos = random.choice(available_pos)
        available_pos.remove(new_pos)
        (row_id, col_id) = (new_pos % 9, new_pos // 9)
        board.place_mine(row_id, col_id)
    return board
def get_move(board):
    INSTRUCTIONS = ("First, enter the column, followed by the row. To add or "
                    "remove a flag, add \"f\" after the row (for example, 64f "
                    "would place a flag on the 6th column, 4th row). Enter "
                    "your move: ")
    move = input("Enter your move (for help enter \"H\"): ")
    if move == "H":
        move = input(INSTRUCTIONS)
    while not is_valid(move, board):
        move = input("Invalid input. Enter your move (for help enter \"H\"): ")
        if move == "H":
            move = input(INSTRUCTIONS)
    return (int(move[1]), int(move[0]), True if move[-1] == "f" else False)
def is_valid(move_input, board):
    if move_input == "H" or (len(move_input) not in (2, 3) or
                             not move_input[:1].isdigit() or
                             int(move_input[0]) not in range(len(board)) or
                             int(move_input[1]) not in range(len(board))):
        return False
    if len(move_input) == 3 and move_input[2] != "f":
        return False
    return True
def main():
    SIZE = 10
    MINES = 9
    board = create_board(SIZE, MINES)
    print(board)
    while board.is_playing and not board.is_solved:
        (row_id, col_id, is_flag) = get_move(board)
        if not is_flag:
            board.show(row_id, col_id)
        else:
            board.flag(row_id, col_id)
        print(board)
    if board.is_solved:
        print("Well done! You solved the board!")
    else:
        print("Uh oh! You blew up!")
if __name__ == "__main__":
    main()
I am currently aware that when counting remaining_mines, the property function runs each time, whereas it would have been more efficient only adding and subtracting from the mine count when a mine or flag is placed.
When I implemented this, however, it looked quite messy, so I chose readability over performance. Was this the right decision? I also think the for a in b: for c in a: which are repeated throughout the code could be cleaned up. Finally, I wasn't sure whether to use a list comprehension or a normal loop in the final elif of Board.show().
Have you got any answers to my questions, or any general tips on how to improve the performance or readability?

pygamecheck it out (pygame.org), download a couple of projects off there and look at its code. Then try to use pygame to make your minesweeper even better! \$\endgroup\$