Q:

My tic-tac-toe Python game doesn't end when a player wins

Good day. I'm currently reading Michael Dawson's book Python for the Absolute Beginner. I have this code for Chapter 6 and when I ran the code and got the straight "X" in the board, it won't return the winner. The program continues until all the spaces are mark with either "X" or "O". Here is my code:
#!/usr/bin/python3
X = "X"
O = "O"
EMPTY = " "
TIE = "TIE"
NUM_SQUARES = 9

def display_instruct():
    print(
          """
          Welcome to the greatest intellectual challenge of all time: Tic-Tac-Toe.
          This will be a showdown between your human brain and my silicon processor.

          You will make your move known by entering a number, 0 - 8. The number
          will correspond to the board position as illustrated:

                                      0 | 1 | 2
                                     -----------
                                      3 | 4 | 5
                                     -----------
                                      6 | 7 | 8

            Prepare your self, human. The ultimate battle is about to begin. \n
            """)

def ask_yes_no(question):
    response = None
    while response not in ("y", "n"):
        response = input(question).lower()
    return response

def ask_number(question, low, high):
    response = None
    while response not in range(low, high):
        response = int(input(question))
    return response

def pieces():
    go_first = ask_yes_no("Do you require the first move? (y/n): ")
    if go_first == "y":
        print( "\nThen take the first move. You will need it. ")
        human = X
        computer = O
    else:
        print("\nYour bravery will be your undoing... I will go first.")
        computer = X
        human = O
    return computer, human

def new_board():
    board = []
    for square in range(NUM_SQUARES):
        board.append(EMPTY)
    return board

def display_board(board):
    print("\n\t", board[0], "|", board[1], "|", board[2])
    print("\t", "---------")
    print("\t", board[3], "|", board[4], "|", board[5])
    print("\t", "---------")
    print("\t", board[6], "|", board[7], "|", board[8], "\n")

def legal_moves(board):
    moves = []
    for square in range(NUM_SQUARES):
        if board[square] == EMPTY:
            moves.append(square)
    return moves

def winner(board):
    WAYS_TO_WIN = ((0, 1, 2),
                   (3, 4, 5),
                   (6, 7, 8),
                   (0, 3, 6),
                   (1, 4, 7),
                   (2, 5, 8),
                   (0, 4, 8),
                   (2, 4, 6))

    for row in WAYS_TO_WIN:
        if board[row[0]] == board[row[1]] == board[row[2]] != EMPTY:
            winner = board[row[0]]
            return winner

        if EMPTY not in board:
            return TIE

        return None

def human_move(board, human):
    """Get human move."""
    legal = legal_moves(board)
    move = None
    while move not in legal:
        move = ask_number("Where will you move? (0 - 8): ", 0, NUM_SQUARES)
        if move not in legal:
            print("\nThat square is already occupied, foolish human. Choose another.\n")
    print("Fine....")
    return move

def computer_move(board, computer, human):
    """Make computer move."""
    #make a copy to work with since function will be changing list.
    board = board[:]
    BEST_MOVES = (4, 0, 2, 6, 8, 1, 3, 5, 7)
    print("I shall take square number")

    # if computer can win, take that move
    for move in legal_moves(board):
        board[move] = computer
        if winner(board) == computer:
            print(move)
            return move
        board[move] = EMPTY

    # if human can win, block that move
    for move in legal_moves(board):
        board[move] = human
        if winner(board) == human:
            print(move)
            return move
        # done checking this move, undo it
        board[move] =  EMPTY

    # since no one ca win on next move, pick best open square
    for move in BEST_MOVES:
        if move in legal_moves(board):
            print(move)
            return move

def next_turn(turn):
    if turn == X:
        return 0
    else:
        return X

def congrat_winner(the_winner, computer, human):
    if the_winner != TIE:
        print(the_winner, "won!\n")
    else:
        print("It's a tie!\n")
    if the_winner == computer:
        print("As I predicted, human, I am triumphant once more. \n" \
              "Proof that computers are superior to humans in all regards.\n")

    elif the_winner == human:
        print("No, no! It cannot be! Somehow you tricked me, human. \n" \
              "But never again! I, the computer, so swears it\n!")

    elif the_winner == TIE:
              print("You were most lucky, human, and somehow managed to tie me. \n" \
                    "Celebrate today... for this is the best you will ever achieve.\n")

def main():
    display_instruct()
    computer, human = pieces()
    turn = X
    board = new_board()
    display_board(board)

    while not winner(board):
        if turn == human:
            move = human_move(board, human)
            board[move] = human
        else:
            move = computer_move(board, computer, human)
            board[move] = computer

        display_board(board)
        turn = next_turn(turn)

    the_winner = winner(board)
    congrat_winner(the_winner, computer, human)

main()

input("Press enter to exit")

A:

The function that checks for a win or tie is incorrect. Currently you have this:
def winner(board):
    WAYS_TO_WIN = ((0, 1, 2),
                   (3, 4, 5),
                   (6, 7, 8),
                   (0, 3, 6),
                   (1, 4, 7),
                   (2, 5, 8),
                   (0, 4, 8),
                   (2, 4, 6))

    for row in WAYS_TO_WIN:
        if board[row[0]] == board[row[1]] == board[row[2]] != EMPTY:
            winner = board[row[0]]
            return winner

        if EMPTY not in board:
            return TIE

        return None

Notice that return None is inside the loop, so you return None when the first row fails to match. That's unless the board is full, in which case you correctly return TIE, except you could have checked for a full board before trying to match any rows.
We can make the function correct by reindenting return None so that it gets executed after the loop. In addition, it makes sense to move the TIE check before the loop.
def winner(board):
    if EMPTY not in board:
        return TIE

    WAYS_TO_WIN = ((0, 1, 2),
                   (3, 4, 5),
                   (6, 7, 8),
                   (0, 3, 6),
                   (1, 4, 7),
                   (2, 5, 8),
                   (0, 4, 8),
                   (2, 4, 6))

    for row in WAYS_TO_WIN:
        if board[row[0]] == board[row[1]] == board[row[2]] != EMPTY:
            winner = board[row[0]]
            return winner

    return None

Let's review what this function does. We start by checking for a full board. Then we go on to consider the ways to win. If none of them match, we return None.
There's one more problem in your code:
def next_turn(turn):
    if turn == X:
        return 0
    else:
        return X

In order for the later test if turn == human: to work correctly in all cases, the value of turn must be either X or O, not X or 0. This is the quick fix:
def next_turn(turn):
    if turn == X:
        return O
    else:
        return X

The better way to fix this problem is to avoid having a variable named O.

