def get_permitted_functions(default_funcs, whitelist, blacklist, always_allowed):
    """
    Constructs a set of functions whose usage is permitted.

    Arguments:
        default_funcs: an iterable whose elements are are function names
            Examples: {'func1':..., 'func2':..., ...} or ['func1', 'func2']
        blacklist ([str]): function names to remove from default_funcs
        whitelist ([str]): function names to keep from default_funcs
        always_allowed: an iterable whose elements are function names that
            are always allowed.

    Note: whitelist and blacklist cannot both be non-empty

    Usage
    =====
    Whitelist some functions:
    >>> default_funcs = {'sin': None, 'cos': None, 'tan': None}
    >>> always_allowed = {'f1': None, 'f2': None}
    >>> get_permitted_functions(
    ...     default_funcs,
    ...     ['sin', 'cos'],
    ...     [],
    ...     always_allowed
    ... ) == set(['sin', 'cos', 'f1', 'f2'])
    True

    If whitelist=[None], all defaults are disallowed:
    >>> default_funcs = {'sin': None, 'cos': None, 'tan': None}
    >>> always_allowed = {'f1': None, 'f2': None}
    >>> get_permitted_functions(
    ...     default_funcs,
    ...     [None],
    ...     [],
    ...     always_allowed
    ... ) == set(['f1', 'f2'])
    True

    Blacklist some functions:
    >>> default_funcs = {'sin': None, 'cos': None, 'tan': None}
    >>> always_allowed = {'f1': None, 'f2': None}
    >>> get_permitted_functions(
    ...     default_funcs,
    ...     [],
    ...     ['sin', 'cos'],
    ...     always_allowed
    ... ) == set(['tan', 'f1', 'f2'])
    True

    Blacklist and whitelist cannot be simultaneously used:
    >>> default_funcs = {'sin': None, 'cos': None, 'tan': None}
    >>> always_allowed = {'f1': None, 'f2': None}
    >>> try:
    ...     get_permitted_functions(
    ...         default_funcs,
    ...         ['sin'],
    ...         ['cos'],
    ...         always_allowed)
    ... except ValueError as error:
    ...     print(error)
    whitelist and blacklist cannot both be non-empty
    """
    # should never trigger except in doctest above,
    # Grader's config validation should raise an error first
    if whitelist and blacklist:
        raise ValueError('whitelist and blacklist cannot both be non-empty')
    if whitelist == []:
        permitted_functions = set(always_allowed).union(
            set(default_funcs)
            ).difference(set(blacklist))
    elif whitelist == [None]:
        permitted_functions = set(always_allowed)
    else:
        permitted_functions = set(always_allowed).union(whitelist)
    return permitted_functions