Tic Tac Toe Program: Are there any ways to make this code more readable?
There are a couple easy gains to be had.

Python Version

Use Python 3. Python 2 is end-of-life as of 2020-01-01.
In Python 3,

# -*- coding: utf-8 -*-


is no longer needed: Python 3 is UTF-8 by default.

Python 3.6 introduced f-strings, where the syntax is:

x = 3
text = f"You have {x} items!"


Much clearer and shorter than the .format() syntax.
The % string formatting syntax should not be used at all anymore.
f-strings are also fast.

PEP8

PEP 8 is a collection of style guidelines. It is highly recommended to make use of them, otherwise other Python developers will never stop reminding you. Use an editor that can format or at least warn you automatically. For this, look into tools like Pylint or black.

PEP8 examples relevant to your example:


global variables are all UPPERCASE
commas generally have trailing spaces: ["X", "O"]


Another note (not part of PEP8) is that I highly prefer double quotes (") over single-quotes (').
This is because empty strings (your code has them) can be confusing with the latter style (''), whereas they are impossible to confuse with double quotes ("").

Function attributes

Functions attributes are

def func(x):
    func.y = 3


That is, objects attached to the function object and accessible via dot notation, among other things. I don't think (and hope) that they are not part of any introductory books. Probably better to avoid them completely, especially when just starting. Thus, your display_board function would become (with PEP8-formatting through black):

def display_board(board):
    """
    Takes the board list as an argument 
    and displays it as a Tic-Tac-Toe board 
    when the game is running

    """

    row3 = "{0:^5s}||{1:^5s}||{2:^5s}".format(board[7], board[8], board[9])
    row2 = "{0:^5s}||{1:^5s}||{2:^5s}".format(board[4], board[5], board[6])
    row1 = "{0:^5s}||{1:^5s}||{2:^5s}".format(board[1], board[2], board[3])
    spaces = "{0:5s}||{1:5s}||{2:5s}".format("", "", "")
    board = [row3, row2, row1]

    for row in board[:2]:
        print(spaces + "\n" + row + "\n" + spaces)
        print("=" * 20)
    print(spaces + "\n" + row1 + "\n" + spaces)


Much better and clearer!

Function attributes can introduce a nasty state.
This is definitely not what a function should do.
For a given input, the return should always be the same.
Functions can have side effects, but the return value should be perfectly predictable, i.e. deterministic.
But using function attributes, this principle can be violated (snipet uses ipython):

In [1]: def func(x):
   ...:     return func.y + x
   ...:

In [2]: func.y = 3

In [3]: func(2)
Out[3]: 5

In [4]: func.y = 5

In [5]: func(2)
Out[5]: 7


It is clear how func(2) should always return the same thing (whatever that may be).
If states are required, that is you need an object that "remembers", use classes and their version of functions, methods.

Bare try/except

Never leave an except statement naked, that is:

try:   
    position = int(input("Choose your next position: (1-9)\n"))
    break
except:
    print('Invalid input. Try again!')


Should be:

try:   
    position = int(input("Choose your next position: (1-9)\n"))
    break
except ValueError:
    print('Invalid input. Try again!')


since you only try to catch the failed conversion to int for invalid input.
In trying out your game, the except also caught KeyboardInterrupt, which is hugely confusing to the user. There has to be a way to exit the game anytime.

Specifying a specific exception (can also be multiple, separated by commas) is much clearer, easier to understand and your program will actually break when something unexpected (unexcepted?) happens. This is what you want to handle such cases properly. It is poor practice if you try to catch a specific exception to handle it, but also catch another, unrelated one and handle that case also, but of course entirely wrong. This is unpredictable behavior that will be hard to debug.