Back to Basics: Tic Tac Toe - follow-up
Ways to improve:


simplifying list construction:

*list(x if x != '0' else ' ' for x in board)) --> *[x if x != '0' else ' ' for x in board]
check_diagonal function. The function performs a logical check if the passed row is fully selected/marked with the same mark/code and returns boolean result respectively.Therefore, it's better to give it a more meaningful and unified name, say row_crossed (reflecting boolean purpose):

def row_crossed(row):
    return '0' not in row and len(set(row)) == 1


Besides, renaming it will serve beneficially for the next improvements (see below)
2 consecutive conditions:

if check_diagonal(a.diagonal()):
    return True

# check rl diagonal
if check_diagonal(np.fliplr(a).diagonal()):
    return True


return the same boolean result. That's a sign for Consolidate conditional expression technique.Thus, the flow becomes:

# check lr diagonal
if row_crossed(a.diagonal()) or row_crossed(np.fliplr(a).diagonal()):
    return True

the for loop (in check_for_win function) which iterates through pair of  matrices (initial and transposed one) duplicates the same logical check 

 ...
 if '0' not in row and len(set(row)) == 1:


as row_crossed (formerly check_diagonal) function does. Thus, replacing duplicated condition with function call.Eventually, the restructured check_for_win function would look as below:

def check_for_win(board):
    def row_crossed(row):
        return '0' not in row and len(set(row)) == 1

    a = np.array(board).reshape(3, 3)

    # check lr diagonal
    if row_crossed(a.diagonal()) or row_crossed(np.fliplr(a).diagonal()):
        return True

    # check rows &amp; cols
    for matr in (a, a.transpose()):
        for row in matr:
            if row_crossed(row):
                return True

    return False



Actually, the above Consolidation of conditionals can be even more encompassing with applying np.concatenate and builtin any functions, though may seem less readable but quite viable for those who likes concise/compact code:

def check_for_win(board):
    def row_crossed(row):
        return '0' not in row and len(set(row)) == 1

    a = np.array(board).reshape(3, 3)

    if row_crossed(a.diagonal()) \
            or row_crossed(np.fliplr(a).diagonal()) \
            or any(row_crossed(row) for row in np.concatenate([a, a.transpose()])):
        return True

    return False




Minor "refinements":


for matr in (a, a.transpose()) looks a bit more "connected" than for matr in (a, np.transpose(a)):
optionally, to reduce typing misses of codes = ('c1', 'c2', 'c3', 'b1', 'b2', 'b3', 'a1', 'a2', 'a3') - a good alternative is:

codes = tuple(map(''.join, itertools.product('cba', '123')))