Multiplication Grid function
make_grid() isn't quite the right name for this function, as it is printing a grid rather than returning a grid.  I think you would be better off returning a long string to be printed.

In your list comprehension, _ is a bad choice for the iteration variable name, since _ indicates a throwaway value, by convention.  If you then call str(_), you should pick a different name.

Besides the excessively wide spacing as you have observed, using tabs is problematic because the output would be better with the entries right-aligned.  You also have a superfluous tab at the end of each line (except the header row).

Suggested solution

Use '{0:&gt;{width}}'.format(…) to print the entries right-aligned with a fixed width.

def multiplication_grid(max):
    max += 1    # Inclusive upper bound for ranges
    grid = [['X'] + [j for j in range(max)]] + \
             [[i] + [i * j for j in range(max)] for i in range(max)]

    # Use fixed-width right alignment for every entry in the 2D grid
    width = 2 + len(str(max * max))
    fmt = lambda n: '{0:&gt;{width}}'.format(n, width=width)
    return '\n\n'.join(''.join(fmt(entry) for entry in row) for row in grid)


[['X'] + [j for j in range(max)]] creates the header row.

[[i] + [i * j for j in range(max)] for i in range(max)] is a nested list comprehension to create the rest of the rows, where i is the row number and j is the column number.

lambda is a way to define very simple functions that do nothing more than returning an expression.  The lambda could also have been written as:

    def fmt(n):
        return '{0:&gt;{width}}'.format(n, width=width)


The last line applies fmt() to every entry in the two-dimensional grid and joins everything into a single string.