Python Conway's Game of life One of my first serious Python programs, realized Conway's Game of Life in Python. Not sure that get_cell_state and update_table functions are good as they might be. Would be glad to see any improvements or critique. Thank you for reviewing ^^.

import time
import random
import copy
import os


black_square_symbol = chr(int('25A0', 16))  # a nice unicode black square symbol


def get_cell_state(table, row, col):
    if row in range(len(table)) and col in range(len(table[0])):
        # little hack to return 0 if row and col are out of rang
        return 1 if table[row][col] else 0
    return 0


def get_neighboring_cells(table, row, col):
    sum = 0
    for row_shift in (-1, 0, 1):
        for col_shift in (-1, 0, 1):
            if row_shift or col_shift:
                #  checking for funcion to not check the state of the cell itself
                sum += get_cell_state(table, row + row_shift, col + col_shift)
    return sum


def generate_table(height, width, rand_seed=time.time()):
    random.seed(rand_seed)  #  giving a seed if user specifies
    table = [[None] * width for _ in range(height)]  # generating the table frame
    for row in range(height):
        for col in range(width):
            table[row][col] = random.choice([True, False])
    return table


def update_table(table, height, width):
    new_table = copy.deepcopy(table)  # deep copy to avoid mutability issues
    for row in range(height):
        for col in range(width):
            neighboring_cells = get_neighboring_cells(table, row, col)
            if neighboring_cells &lt; 2 and table[row][col]:
                # underpopulation
                new_table[row][col] = False
            elif neighboring_cells &gt; 3 and table[row][col]:
                # overpopulation
                new_table[row][col] = False
            elif neighboring_cells == 3 and not table[row][col]:
                # replication
                new_table[row][col] = True
    return new_table


def print_table(table):
    os.system('cls')  # clearing up the screen to print new table
    for row in table:
        for elem in row:
                print(black_square_symbol if elem else ' ', end='')
        print()  # newline


def main():
    os.system('color f0')  # making the background white and text black
    height = int(input('Enter table width: '))
    width = int(input('Enter table height: '))
    rand_seed = input('Enter seed to generate the table(leave blank if dont want to specify')
    if rand_seed:
        table = generate_table(width, height, float(rand_seed))
    else:
        table = generate_table(width, height)
    year = 0
    while True:
        print_table(table)
        print('year:', year)
        year += 1
        table = update_table(table, height, width)
        time.sleep(1)


if __name__ == '__main__':
    main()
Quite nicely structured. some remarks


main() - while you have the guard and also an extra printing function there is a little mess in main(). It has got some game logic (the loop), it has got some UI (input and printing). split it into a run() function with parameters and a interactive main(). parameters to run() should be an initial board and the visualisation callback.
some non-portable OS calls - while acceptable for a test program you should think of a better solution. functions used by an importer should not depent on that. there are also patterns to throw import errors on wrong OS to give correct and immediate information.
seeding - usually shall be done once at program start only. you gave the user the chance to reproduce a board - nicely done. but what about the fallback (which btw. gives a ValueError currently)? What happens if someone imports your module and tries to generate random boards without seed? you will generate identical boards. check why, this is a very important lesson.
deepcopy - you don't need a copy, you want an empty table. this is confusing. implement something like empty_table()