Get element reference from input element.

    The element can be a builtin/globals/scope object or is resolved from the
    current execution stack.

    :limitations: it does not resolve class methods or static values such as
        True, False, numbers, string and keywords.
    :param str path: full path to a python element.
    :param bool cache: if True (default), permits to reduce time complexity for
        lookup resolution in using cache memory to save resolved elements.
    :param dict scope: object scrope from where find path. For example, this
        scope can be locals(). Default is globals().
    :param bool safe: use lookup in a safe context. A safe context avoid to
        reach builtins function with I/O consequences.
    :return: python object which is accessible through input path
        or raise an exception if the path is wrong.
    :rtype: object
    :raises ImportError: if path is wrong

def lookup(path, cache=True, scope=None, safe=False):
    """Get element reference from input element.

    The element can be a builtin/globals/scope object or is resolved from the
    current execution stack.

    :limitations: it does not resolve class methods or static values such as
        True, False, numbers, string and keywords.
    :param str path: full path to a python element.
    :param bool cache: if True (default), permits to reduce time complexity for
        lookup resolution in using cache memory to save resolved elements.
    :param dict scope: object scrope from where find path. For example, this
        scope can be locals(). Default is globals().
    :param bool safe: use lookup in a safe context. A safe context avoid to
        reach builtins function with I/O consequences.
    :return: python object which is accessible through input path
        or raise an exception if the path is wrong.
    :rtype: object
    :raises ImportError: if path is wrong
    """

    result = None

    found = path and cache and path in __LOOKUP_CACHE

    if found:
        result = __LOOKUP_CACHE[path]

    elif path:

        _eval = safe_eval if safe else eval

        try:  # search among scope
            result = _eval(path, scope)

        except (NameError, SyntaxError):

            # we generate a result in order to accept the result such as a None
            generated_result = random()
            result = generated_result

            components = path.split('.')
            index = 0
            components_len = len(components)

            module_name = components[0]

            # try to resolve an absolute path
            try:
                result = import_module(module_name)

            except ImportError:
                # resolve element globals or locals of the from previous frame
                previous_frame = currentframe().f_back

                if module_name in previous_frame.f_locals:
                    result = previous_frame.f_locals[module_name]

                elif module_name in previous_frame.f_globals:
                    result = previous_frame.f_globals[module_name]

            found = result is not generated_result

            if found:

                if components_len > 1:

                    index = 1

                    # try to import all sub-modules/packages
                    try:  # check if name is defined from an external module
                        # find the right module
                        while index < components_len:
                            module_name = '{0}.{1}'.format(
                                module_name, components[index]
                            )
                            result = import_module(module_name)
                            index += 1

                    except ImportError:
                        # path sub-module content
                        try:
                            if PY26:  # when __import__ is used
                                index = 1  # restart count of pathing
                            while index < components_len:
                                result = getattr(result, components[index])
                                index += 1

                        except AttributeError:
                            raise ImportError(
                                'Wrong path {0} at {1}'.format(
                                    path, components[:index]
                                )
                            )
                    else:  # in case of PY26
                        if PY26:
                            index = 1
                            while index < components_len:
                                result = getattr(result, components[index])
                                index += 1

        else:
            found = True

    if found:
        if cache:  # save in cache if found
            __LOOKUP_CACHE[path] = result

    else:
        raise ImportError('Wrong path {0}'.format(path))

    return result