def Func(arg_0, arg_1=False):
    """
    Normalizes a string representation of a path and does shell-like expansion.

    Args:
        path (PathLike): string representation of a path
        real (bool): if True, all symbolic links are followed. (default: False)

    Returns:
        PathLike : normalized path

    Note:
        This function is similar to the composition of expanduser, expandvars,
        normpath, and (realpath if `real` else abspath). However, on windows
        backslashes are then replaced with forward slashes to offer a
        consistent unix-like experience across platforms.

        On windows expanduser will expand environment variables formatted as
        %name%, whereas on unix, this will not occur.

    CommandLine:
        python -m ubelt.util_path Func

    Example:
        >>> import ubelt as ub
        >>> assert ub.Func('~/foo') == join(ub.userhome(), 'foo')
        >>> assert ub.Func('~/foo') == ub.Func('~/foo/bar/..')
        >>> assert ub.Func('~/foo', real=True) == ub.Func('~/foo')
    """
    arg_0 = expanduser(arg_0)
    arg_0 = expandvars(arg_0)
    if arg_1:
        arg_0 = realpath(arg_0)
    else:
        arg_0 = abspath(arg_0)
    arg_0 = normpath(arg_0)
    return arg_0


def Func(arg_0, arg_1=0o1777, arg_2=None):
    r"""
    Ensures that directory will exist. Creates new dir with sticky bits by
    default

    Args:
        dpath (PathLike): dir to ensure. Can also be a tuple to send to join
        mode (int): octal mode of directory (default 0o1777)
        verbose (int): verbosity (default 0)

    Returns:
        PathLike: path: the ensured directory

    Notes:
        This function is not thread-safe in Python2

    Example:
        >>> from ubelt.util_platform import *  # NOQA
        >>> import ubelt as ub
        >>> cache_dpath = ub.ensure_app_cache_dir('ubelt')
        >>> dpath = join(cache_dpath, 'Func')
        >>> if exists(dpath):
        ...     os.rmdir(dpath)
        >>> assert not exists(dpath)
        >>> ub.Func(dpath)
        >>> assert exists(dpath)
        >>> os.rmdir(dpath)
    """
    if arg_2 is None:  # nocover
        arg_2 = 0
    if isinstance(arg_0, (list, tuple)):  # nocover
        arg_0 = join(*arg_0)
    if not exists(arg_0):
        if arg_2:  # nocover
            print('Ensuring new directory (%r)' % arg_0)
        if sys.version_info.major == 2:  # nocover
            os.makedirs(normpath(arg_0), arg_1=arg_1)
        else:
            os.makedirs(normpath(arg_0), arg_1=arg_1, exist_ok=True)
    else:
        if arg_2:  # nocover
            print('Ensuring existing directory (%r)' % arg_0)
    return arg_0


def Func(arg_0='requirements.txt'):
    """
    pip install requirements-parser
    fname='requirements.txt'
    """
    import requirements
    from os.path import dirname, join, exists
    arg_1 = join(dirname(__file__), arg_0)
    if exists(arg_1):
        # Dont use until this handles platform specific dependencies
        with open(arg_1, 'r') as file:
            arg_2 = list(requirements.parse(file))
        arg_3 = [r.name for r in arg_2]
        return arg_3
    return []


def Func(arg_0='requirements.txt'):
    """
    Parse the package dependencies listed in a requirements file but strips
    specific versioning information.

    TODO:
        perhaps use https://github.com/davidfischer/requirements-parser instead

    CommandLine:
        python -c "import setup; print(setup.Func())"
    """
    from os.path import dirname, join, exists
    import re
    arg_1 = join(dirname(__file__), arg_0)

    def parse_line(arg_2):
        """
        Parse information from a line in a requirements text file
        """
        arg_3 = {}
        if arg_2.startswith('-e '):
            arg_3['package'] = arg_2.split('#egg=')[1]
        else:
            # Remove versioning from the package
            arg_4 = '(' + '|'.join(['>=', '==', '>']) + ')'
            arg_5 = re.split(arg_4, arg_2, maxsplit=1)
            arg_5 = [p.strip() for p in arg_5]

            arg_3['package'] = arg_5[0]
            if len(arg_5) > 1:
                arg_6, arg_7 = arg_5[1:]
                if ';' in arg_7:
                    # Handle platform specific dependencies
                    # http://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-platform-specific-dependencies
                    arg_8, arg_9 = map(str.strip, arg_7.split(';'))
                    arg_3['platform_deps'] = arg_9
                else:
                    arg_8 = arg_7  # NOQA
                arg_3['version'] = (arg_6, arg_8)
        return arg_3

    # This breaks on pip install, so check that it exists.
    if exists(arg_1):
        with open(arg_1, 'r') as f:
            arg_10 = []
            for arg_2 in f.readlines():
                arg_2 = arg_2.strip()
                if arg_2 and not arg_2.startswith('#'):
                    arg_3 = parse_line(arg_2)
                    arg_11 = arg_3['package']
                    if not sys.version.startswith('3.4'):
                        # apparently package_deps are broken in 3.4
                        arg_9 = arg_3.get('platform_deps')
                        if arg_9 is not None:
                            arg_11 += ';' + arg_9
                    arg_10.append(arg_11)
            return arg_10
    return []


def Func(arg_0, arg_1, arg_2=None):
    """
    Injects a function into an object instance as a bound method

    The main use case of this function is for monkey patching. While monkey
    patching is sometimes necessary it should generally be avoided. Thus, we
    simply remind the developer that there might be a better way.

    Args:
        self (object): instance to inject a function into
        func (func): the function to inject (must contain an arg for self)
        name (str): name of the method. optional. If not specified the name
            of the function is used.

    Example:
        >>> class Foo(object):
        >>>     def bar(self):
        >>>         return 'bar'
        >>> def baz(self):
        >>>     return 'baz'
        >>> self = Foo()
        >>> assert self.bar() == 'bar'
        >>> assert not hasattr(self, 'baz')
        >>> Func(self, baz)
        >>> assert not hasattr(Foo, 'baz'), 'should only change one instance'
        >>> assert self.baz() == 'baz'
        >>> Func(self, baz, 'bar')
        >>> assert self.bar() == 'baz'
    """
    # TODO: if func is a bound method we should probably unbind it
    arg_3 = arg_1.__get__(arg_0, arg_0.__class__)
    if arg_2 is None:
        arg_2 = arg_1.__name__
    setattr(arg_0, arg_2, arg_3)


def Func(arg_0, arg_1=0o666, arg_2=None, arg_3=0, **arg_4):
    """
    change file timestamps

    Works like the Func unix utility

    Args:
        fpath (PathLike): name of the file
        mode (int): file permissions (python3 and unix only)
        dir_fd (file): optional directory file descriptor. If specified, fpath
            is interpreted as relative to this descriptor (python 3 only).
        verbose (int): verbosity
        **kwargs : extra args passed to `os.utime` (python 3 only).

    Returns:
        PathLike: path to the file

    References:
        https://stackoverflow.com/questions/1158076/implement-Func-using-python

    Example:
        >>> import ubelt as ub
        >>> dpath = ub.ensure_app_cache_dir('ubelt')
        >>> fpath = join(dpath, 'Func_file')
        >>> assert not exists(fpath)
        >>> ub.Func(fpath)
        >>> assert exists(fpath)
        >>> os.unlink(fpath)
    """
    if arg_3:
        print('Touching file {}'.format(arg_0))
    if six.PY2:  # nocover
        with open(arg_0, 'a'):
            os.utime(arg_0, None)
    else:
        arg_5 = os.O_CREAT | os.O_APPEND
        with os.fdopen(os.open(arg_0, arg_5=arg_5, arg_1=arg_1, arg_2=arg_2)) as f:
            os.utime(f.fileno() if os.utime in os.supports_fd else arg_0,
                     arg_2=None if os.supports_fd else arg_2, **arg_4)
    return arg_0


def Func(arg_0, arg_1=False):
    """
    Removes a file or recursively removes a directory.
    If a path does not exist, then this is does nothing.

    Args:
        path (PathLike): file or directory to remove
        verbose (bool): if True prints what is being done

    SeeAlso:
        send2trash - A cross-platform Python package for sending files
            to the trash instead of irreversibly deleting them.
            https://github.com/hsoft/send2trash

    Doctest:
        >>> import ubelt as ub
        >>> base = ub.ensure_app_cache_dir('ubelt', 'Func_test')
        >>> dpath1 = ub.ensuredir(join(base, 'dir'))
        >>> ub.ensuredir(join(base, 'dir', 'subdir'))
        >>> ub.touch(join(base, 'dir', 'to_remove1.txt'))
        >>> fpath1 = join(base, 'dir', 'subdir', 'to_remove3.txt')
        >>> fpath2 = join(base, 'dir', 'subdir', 'to_remove2.txt')
        >>> ub.touch(fpath1)
        >>> ub.touch(fpath2)
        >>> assert all(map(exists, (dpath1, fpath1, fpath2)))
        >>> ub.Func(fpath1)
        >>> assert all(map(exists, (dpath1, fpath2)))
        >>> assert not exists(fpath1)
        >>> ub.Func(dpath1)
        >>> assert not any(map(exists, (dpath1, fpath1, fpath2)))

    Doctest:
        >>> import ubelt as ub
        >>> dpath = ub.ensure_app_cache_dir('ubelt', 'Func_test2')
        >>> dpath1 = ub.ensuredir(join(dpath, 'dir'))
        >>> fpath1 = ub.touch(join(dpath1, 'to_remove.txt'))
        >>> assert exists(fpath1)
        >>> ub.Func(dpath)
        >>> assert not exists(fpath1)
    """
    if not os.path.exists(arg_0):
        # if the file does exists and is not a broken link
        if os.path.islink(arg_0):
            if arg_1:  # nocover
                print('Deleting broken link="{}"'.format(arg_0))
            os.unlink(arg_0)
        elif os.path.isdir(arg_0):  # nocover
            # Only on windows will a file be a directory and not exist
            if arg_1:
                print('Deleting broken directory link="{}"'.format(arg_0))
            os.rmdir(arg_0)
        elif os.path.isfile(arg_0):  # nocover
            # This is a windows only case
            if arg_1:
                print('Deleting broken file link="{}"'.format(arg_0))
            os.unlink(arg_0)
        else:
            if arg_1:  # nocover
                print('Not deleting non-existant path="{}"'.format(arg_0))
    else:
        if os.path.islink(arg_0):
            if arg_1:  # nocover
                print('Deleting symbolic link="{}"'.format(arg_0))
            os.unlink(arg_0)
        elif os.path.isfile(arg_0):
            if arg_1:  # nocover
                print('Deleting file="{}"'.format(arg_0))
            os.unlink(arg_0)
        elif os.path.isdir(arg_0):
            if arg_1:  # nocover
                print('Deleting directory="{}"'.format(arg_0))
            if sys.platform.startswith('win32'):  # nocover
                # Workaround bug that prevents shutil from working if
                # the directory contains junctions
                from ubelt import _win32_links
                _win32_links._win32_rmtree(arg_0, arg_1=arg_1)
            else:
                import shutil
                shutil.rmtree(arg_0)


def Func(arg_0, arg_1, arg_2, arg_3, arg_4,
                   arg_5, arg_6, arg_7, arg_8):
    """
    Joins string-ified items with separators newlines and container-braces.
    """
    # positive newlines means start counting from the root
    arg_9 = arg_2 > 0

    # negative countdown values mean start counting from the leafs
    # if compact_brace < 0:
    #     compact_brace = (-compact_brace) >= _leaf_info['max_height']
    if arg_2 < 0:
        arg_9 = (-arg_2) < arg_3['max_height']

    if arg_9:
        arg_10 = ',\n'
        if arg_4:
            arg_11 = arg_10.join(arg_0)
            if arg_5 and len(arg_0) > 0:
                arg_11 += ','
            arg_12 = arg_11
        else:
            if arg_6:
                # Why must we modify the indentation below and not here?
                # prefix = ''
                # rest = [ub.indent(s, prefix) for s in itemstrs[1:]]
                # indented = itemstrs[0:1] + rest
                arg_13 = arg_0
            else:
                import ubelt as ub
                arg_14 = ' ' * 4
                arg_13 = [ub.indent(s, arg_14) for s in arg_0]

            arg_11 = arg_10.join(arg_13)
            if arg_5 and len(arg_0) > 0:
                arg_11 += ','
            if arg_6:
                # Why can we modify the indentation here but not above?
                arg_15 = (arg_7 + arg_11.replace('\n', '\n ') + arg_8)
            else:
                arg_15 = (arg_7 + '\n' + arg_11 + '\n' + arg_8)
            arg_12 = arg_15
    else:
        arg_10 = ',' + arg_1
        arg_11 = arg_10.join(arg_0)
        if arg_5 and len(arg_0) > 0:
            arg_11 += ','
        arg_12  = (arg_7 + arg_11 +  arg_8)
    return arg_12


def Func(arg_0, **arg_1):
    """
    Create a string representation for each item in a list.
    """
    arg_2 = list(arg_0)
    arg_1['_return_info'] = True
    arg_3 = [repr2(item, **arg_1) for item in arg_2]
    arg_4 = [t[0] for t in arg_3]
    arg_5 = max([t[1]['max_height'] for t in arg_3]) if arg_3 else 0
    arg_6 = {
        'max_height': arg_5 + 1,
    }

    arg_7 = arg_1.get('sort', None)
    if arg_7 is None:
        # Force orderings on sets.
        arg_7 = isinstance(arg_0, (set, frozenset))
    if arg_7:
        arg_4 = _sort_itemstrs(arg_2, arg_4)
    return arg_4, arg_6


def Func(arg_0, arg_1):
        """
        Registers a custom formatting function with ub.repr2
        """
        def _decorator(arg_2):
            if isinstance(arg_1, tuple):
                for arg_3 in arg_1:
                    arg_0.func_registry[arg_3] = arg_2
            else:
                arg_0.func_registry[arg_1] = arg_2
            return arg_2
        return _decorator


def Func(arg_0, arg_1):
        """
        Returns an appropriate function to format `data` if one has been
        registered.
        """
        for arg_2 in arg_0.lazy_init:
            arg_2()

        for arg_3, arg_2 in arg_0.func_registry.items():
            if isinstance(arg_1, arg_3):
                return arg_2


def Func(arg_0):
    """
    Convert a string-based key into a hasher class

    Notes:
        In terms of speed on 64bit systems, sha1 is the fastest followed by md5
        and sha512. The slowest algorithm is sha256. If xxhash is installed
        the fastest algorithm is xxh64.

    Example:
        >>> assert Func(NoParam) is DEFAULT_HASHER
        >>> assert Func('sha1') is hashlib.sha1
        >>> assert Func('sha256') is hashlib.sha256
        >>> assert Func('sha512') is hashlib.sha512
        >>> assert Func('md5') is hashlib.md5
        >>> assert Func(hashlib.sha1) is hashlib.sha1
        >>> assert Func(hashlib.sha1())().name == 'sha1'
        >>> import pytest
        >>> assert pytest.raises(KeyError, Func, '42')
        >>> #assert pytest.raises(TypeError, Func, object)
        >>> if xxhash:
        >>>     assert Func('xxh64') is xxhash.xxh64
        >>>     assert Func('xxh32') is xxhash.xxh32
    """
    if xxhash is not None:  # pragma: nobranch
        if arg_0 in {'xxh32', 'xx32', 'xxhash'}:
            return xxhash.xxh32
        if arg_0 in {'xxh64', 'xx64'}:
            return xxhash.xxh64

    if arg_0 is NoParam or arg_0 == 'default':
        arg_0 = DEFAULT_HASHER
    elif isinstance(arg_0, six.string_types):
        if arg_0 not in hashlib.algorithms_available:
            raise KeyError('unknown hasher: {}'.format(arg_0))
        else:
            arg_0 = getattr(hashlib, arg_0)
    elif isinstance(arg_0, HASH):
        # by default the result of this function is a class we will make an
        # instance of, if we already have an instance, wrap it in a callable
        # so the external syntax does not need to change.
        return lambda: arg_0
    return arg_0


def Func(arg_0):
    """
    transforms base shorthand into the full list representation

    Example:
        >>> assert Func(NoParam) is DEFAULT_ALPHABET
        >>> assert Func('hex') is _ALPHABET_16
        >>> assert Func('abc') is _ALPHABET_26
        >>> assert Func(10) is _ALPHABET_10
        >>> assert Func(['1', '2']) == ['1', '2']
        >>> import pytest
        >>> assert pytest.raises(TypeError, Func, 'uselist')
    """
    if arg_0 is NoParam or arg_0 == 'default':
        return DEFAULT_ALPHABET
    elif arg_0 in [26, 'abc', 'alpha']:
        return _ALPHABET_26
    elif arg_0 in [16, 'hex']:
        return _ALPHABET_16
    elif arg_0 in [10, 'dec']:
        return _ALPHABET_10
    else:
        if not isinstance(arg_0, (list, tuple)):
            raise TypeError(
                'Argument `base` must be a key, list, or tuple; not {}'.format(
                    type(arg_0)))
        return arg_0


def Func(arg_0, arg_1=True):
    r"""
    Extracts the sequence of bytes that would be hashed by hash_data

    Example:
        >>> data = [2, (3, 4)]
        >>> result1 = (b''.join(Func(data, types=False)))
        >>> result2 = (b''.join(Func(data, types=True)))
        >>> assert result1 == b'_[_\x02_,__[_\x03_,_\x04_,__]__]_'
        >>> assert result2 == b'_[_INT\x02_,__[_INT\x03_,_INT\x04_,__]__]_'
    """
    arg_2 = _HashTracer()
    _update_hasher(arg_2, arg_0, arg_1=arg_1)
    return arg_2.sequence


def Func(arg_0, arg_1, arg_2=True):
    """
    Converts `data` into a byte representation and calls update on the hasher
    `hashlib.HASH` algorithm.

    Args:
        hasher (HASH): instance of a hashlib algorithm
        data (object): ordered data with structure
        types (bool): include type prefixes in the hash

    Example:
        >>> hasher = hashlib.sha512()
        >>> data = [1, 2, ['a', 2, 'c']]
        >>> Func(hasher, data)
        >>> print(hasher.hexdigest()[0:8])
        e2c67675

        2ba8d82b
    """
    # Determine if the data should be hashed directly or iterated through
    if isinstance(arg_1, (tuple, list, zip)):
        arg_3 = True
    else:
        arg_3 = any(check(arg_1) for check in
                              _HASHABLE_EXTENSIONS.iterable_checks)

    if arg_3:
        # Denote that we are hashing over an iterable
        # Multiple structure bytes makes it harder accidently make conflicts
        arg_4 = b'_,_'
        arg_5 = b'_[_'
        arg_6 = b'_]_'

        arg_7 = iter(arg_1)
        arg_0.update(arg_5)
        # first, try to nest quickly without recursive calls
        # (this works if all data in the sequence is a non-iterable)
        try:
            for arg_8 in arg_7:
                arg_9, arg_10 = _convert_to_hashable(arg_8, arg_2)
                arg_11 = arg_9 + arg_10 + arg_4
                arg_0.update(arg_11)
        except TypeError:
            # need to use recursive calls
            # Update based on current item
            Func(arg_0, arg_8, arg_2)
            for arg_8 in arg_7:
                # Ensure the items have a spacer between them
                Func(arg_0, arg_8, arg_2)
                arg_0.update(arg_4)
        arg_0.update(arg_6)
    else:
        arg_9, arg_10 = _convert_to_hashable(arg_1, arg_2)
        arg_11 = arg_9 + arg_10
        arg_0.update(arg_11)


def Func(arg_0, arg_1):
    r"""
    Packs a long hexstr into a shorter length string with a larger base.

    Args:
        hexstr (str): string of hexidecimal symbols to convert
        base (list): symbols of the conversion base

    Example:
        >>> print(Func('ffffffff', _ALPHABET_26))
        nxmrlxv
        >>> print(Func('0', _ALPHABET_26))
        0
        >>> print(Func('-ffffffff', _ALPHABET_26))
        -nxmrlxv
        >>> print(Func('aafffff1', _ALPHABET_16))
        aafffff1

    Sympy:
        >>> import sympy as sy
        >>> # Determine the length savings with lossless conversion
        >>> consts = dict(hexbase=16, hexlen=256, baselen=27)
        >>> symbols = sy.symbols('hexbase, hexlen, baselen, newlen')
        >>> haexbase, hexlen, baselen, newlen = symbols
        >>> eqn = sy.Eq(16 ** hexlen,  baselen ** newlen)
        >>> newlen_ans = sy.solve(eqn, newlen)[0].subs(consts).evalf()
        >>> print('newlen_ans = %r' % (newlen_ans,))
        >>> # for a 26 char base we can get 216
        >>> print('Required length for lossless conversion len2 = %r' % (len2,))
        >>> def info(base, len):
        ...     bits = base ** len
        ...     print('base = %r' % (base,))
        ...     print('len = %r' % (len,))
        ...     print('bits = %r' % (bits,))
        >>> info(16, 256)
        >>> info(27, 16)
        >>> info(27, 64)
        >>> info(27, 216)
    """
    if arg_1 is _ALPHABET_16:
        # already in hex, no conversion needed
        return arg_0
    arg_2 = len(arg_1)
    arg_3 = int(arg_0, 16)  # first convert to base 16
    if arg_3 == 0:
        return '0'
    arg_4 = 1 if arg_3 > 0 else -1
    arg_3 *= arg_4
    arg_5 = []
    while arg_3:
        arg_5.append(arg_1[arg_3 % arg_2])
        arg_3 //= arg_2
    if arg_4 < 0:
        arg_5.append('-')
    arg_5.reverse()
    arg_6 = ''.join(arg_5)
    return arg_6


def Func(arg_0, arg_1):
        """
        Registers a function to generate a hash for data of the appropriate
        types. This can be used to Func custom classes. Internally this is
        used to define how to hash non-builtin objects like ndarrays and uuids.

        The Funced function should return a tuple of bytes. First a small
        prefix hinting at the data type, and second the raw bytes that can be
        hashed.

        Args:
            hash_types (class or tuple of classes):

        Returns:
            func: closure to be used as the decorator

        Example:
            >>> # xdoctest: +SKIP
            >>> # Skip this doctest because we dont want tests to modify
            >>> # the global state.
            >>> import ubelt as ub
            >>> import pytest
            >>> class MyType(object):
            ...     def __init__(self, id):
            ...         self.id = id
            >>> data = MyType(1)
            >>> # Custom types wont work with ub.hash_data by default
            >>> with pytest.raises(TypeError):
            ...     ub.hash_data(data)
            >>> # You can Func your functions with ubelt's internal
            >>> # hashable_extension Funcy.
            >>> @ub.util_hash._HASHABLE_EXTENSIONS.Func(MyType)
            >>> def hash_my_type(data):
            ...     return b'mytype', six.b(ub.hash_data(data.id))
            >>> # TODO: allow hash_data to take an new instance of
            >>> # HashableExtensions, so we dont have to modify the global
            >>> # ubelt state when we run tests.
            >>> my_instance = MyType(1)
            >>> ub.hash_data(my_instance)
        """
        # ensure iterable
        if not isinstance(arg_1, (list, tuple)):
            arg_1 = [arg_1]
        def _decor_closure(arg_2):
            for arg_3 in arg_1:
                arg_4 = (arg_3.__module__, arg_3.__name__)
                arg_0.keyed_extensions[arg_4] = (arg_3, arg_2)
            return arg_2
        return _decor_closure


def Func(arg_0, arg_1):
        """
        Returns an appropriate function to hash `data` if one has been
        registered.

        Raises:
            TypeError : if data has no registered hash methods

        Example:
            >>> import ubelt as ub
            >>> import pytest
            >>> if not ub.modname_to_modpath('numpy'):
            ...     raise pytest.skip('numpy is optional')
            >>> self = HashableExtensions()
            >>> self._register_numpy_extensions()
            >>> self._register_builtin_class_extensions()

            >>> import numpy as np
            >>> data = np.array([1, 2, 3])
            >>> self.Func(data[0])

            >>> class Foo(object):
            >>>     def __init__(f):
            >>>         f.attr = 1
            >>> data = Foo()
            >>> assert pytest.raises(TypeError, self.Func, data)

            >>> # If ub.hash_data doesnt support your object,
            >>> # then you can register it.
            >>> @self.register(Foo)
            >>> def _hashfoo(data):
            >>>     return b'FOO', data.attr
            >>> func = self.Func(data)
            >>> assert func(data)[1] == 1

            >>> data = uuid.uuid4()
            >>> self.Func(data)
        """
        # Maybe try using functools.singledispatch instead?
        # First try O(1) Func
        arg_2 = arg_1.__class__
        arg_3 = (arg_2.__module__, arg_2.__name__)
        try:
            arg_4, arg_5 = arg_0.keyed_extensions[arg_3]
        except KeyError:
            raise TypeError('No registered hash func for hashable type=%r' % (
                    arg_2))
        return arg_5


def Func(arg_0):
        """
        Numpy extensions are builtin
        """
        # system checks
        import numpy as np
        arg_1 = (np.float16, np.float32, np.float64)
        if hasattr(np, 'float128'):  # nocover
            arg_1 = arg_1 + (np.float128,)

        @arg_0.add_iterable_check
        def is_object_ndarray(arg_2):
            # ndarrays of objects cannot be hashed directly.
            return isinstance(arg_2, np.ndarray) and arg_2.dtype.kind == 'O'

        @arg_0.register(np.ndarray)
        def hash_numpy_array(arg_2):
            """
            Example:
                >>> import ubelt as ub
                >>> if not ub.modname_to_modpath('numpy'):
                ...     raise pytest.skip()
                >>> import numpy as np
                >>> data_f32 = np.zeros((3, 3, 3), dtype=np.float64)
                >>> data_i64 = np.zeros((3, 3, 3), dtype=np.int64)
                >>> data_i32 = np.zeros((3, 3, 3), dtype=np.int32)
                >>> hash_f64 = _hashable_sequence(data_f32, types=True)
                >>> hash_i64 = _hashable_sequence(data_i64, types=True)
                >>> hash_i32 = _hashable_sequence(data_i64, types=True)
                >>> assert hash_i64 != hash_f64
                >>> assert hash_i64 != hash_i32
            """
            if arg_2.dtype.kind == 'O':
                arg_3 = 'directly hashing ndarrays with dtype=object is unstable'
                raise TypeError(arg_3)
            else:
                # tobytes() views the array in 1D (via ravel())
                # encode the shape as well
                arg_4 = b''.join(_hashable_sequence((len(arg_2.shape), arg_2.shape)))
                arg_5 = b''.join(_hashable_sequence(arg_2.dtype.descr))
                arg_6 = arg_4 + arg_5 + arg_2.tobytes()
            arg_7 = b'NDARR'
            return arg_7, arg_6

        @arg_0.register((np.int64, np.int32, np.int16, np.int8) +
                       (np.uint64, np.uint32, np.uint16, np.uint8))
        def _hash_numpy_int(arg_2):
            return _convert_to_hashable(int(arg_2))

        @arg_0.register(arg_1)
        def _hash_numpy_float(arg_2):
            return _convert_to_hashable(float(arg_2))

        @arg_0.register(np.random.RandomState)
        def _hash_numpy_random_state(arg_2):
            """
            Example:
                >>> import ubelt as ub
                >>> if not ub.modname_to_modpath('numpy'):
                ...     raise pytest.skip()
                >>> import numpy as np
                >>> rng = np.random.RandomState(0)
                >>> _hashable_sequence(rng, types=True)
            """
            arg_6 = b''.join(_hashable_sequence(arg_2.get_state()))
            arg_7 = b'RNG'
            return arg_7, arg_6


def Func(arg_0):
        """
        Register hashing extensions for a selection of classes included in
        python stdlib.

        Example:
            >>> data = uuid.UUID('7e9d206b-dc02-4240-8bdb-fffe858121d0')
            >>> print(hash_data(data, base='abc', hasher='sha512', types=True)[0:8])
            cryarepd
            >>> data = OrderedDict([('a', 1), ('b', 2), ('c', [1, 2, 3]),
            >>>                     (4, OrderedDict())])
            >>> print(hash_data(data, base='abc', hasher='sha512', types=True)[0:8])
            qjspicvv

            gpxtclct
        """
        @arg_0.register(uuid.UUID)
        def _hash_uuid(arg_1):
            arg_2 = arg_1.bytes
            arg_3 = b'UUID'
            return arg_3, arg_2

        @arg_0.register(OrderedDict)
        def _hash_ordered_dict(arg_1):
            """
            Note, we should not be hashing dicts because they are unordered
            """
            arg_2 = b''.join(_hashable_sequence(list(arg_1.items())))
            arg_3 = b'ODICT'
            return arg_3, arg_2


def Func(arg_0, arg_1, arg_2=1):
    """
    Reads output from a process in a separate thread
    """
    from six.moves import queue
    from threading import Thread
    def enqueue_output(arg_0, arg_1, arg_3):
        while arg_0.poll() is None:
            arg_4 = arg_1.readline()
            # print('ENQUEUE LIVE {!r} {!r}'.format(stream, line))
            arg_3.put(arg_4)

        for arg_4 in _textio_iterlines(arg_1):
            # print('ENQUEUE FINAL {!r} {!r}'.format(stream, line))
            arg_3.put(arg_4)

        # print("STREAM IS DONE {!r}".format(stream))
        arg_3.put(None)  # signal that the stream is finished
        # stream.close()
    arg_3 = queue.Queue(maxsize=arg_2)
    arg_5 = Thread(target=enqueue_output, args=(arg_0, arg_1, arg_3))
    arg_5.daemon = True  # thread dies with the program
    arg_5.start()
    return arg_3


def Func(arg_0='iso8601'):
    """
    make an iso8601 Func

    Args:
        method (str): type of Func

    Example:
        >>> stamp = Func()
        >>> print('stamp = {!r}'.format(stamp))
        stamp = ...-...-...T...
    """
    if arg_0 == 'iso8601':
        # ISO 8601
        # datetime.datetime.utcnow().isoformat()
        # datetime.datetime.now().isoformat()
        # utcnow
        arg_1 = time.timezone // 3600
        arg_2 = str(arg_1) if arg_1 < 0 else '+' + str(arg_1)
        arg_3 = time.strftime('%Y-%m-%dT%H%M%S') + arg_2
        return arg_3
    else:
        raise ValueError('only iso8601 is accepted for now')


def Func(arg_0, arg_1=-1):
    """
    Imports a module via its path

    Args:
        modpath (PathLike): path to the module on disk or within a zipfile.

    Returns:
        module: the imported module

    References:
        https://stackoverflow.com/questions/67631/import-module-given-path

    Notes:
        If the module is part of a package, the package will be imported first.
        These modules may cause problems when reloading via IPython magic

        This can import a module from within a zipfile. To do this modpath
        should specify the path to the zipfile and the path to the module
        within that zipfile separated by a colon or pathsep.
        E.g. `/path/to/archive.zip:mymodule.py`

    Warning:
        It is best to use this with paths that will not conflict with
        previously existing modules.

        If the modpath conflicts with a previously existing module name. And
        the target module does imports of its own relative to this conflicting
        path. In this case, the module that was loaded first will win.

        For example if you try to import '/foo/bar/pkg/mod.py' from the folder
        structure:
          - foo/
            +- bar/
               +- pkg/
                  +  __init__.py
                  |- mod.py
                  |- helper.py

       If there exists another module named `pkg` already in sys.modules
       and mod.py does something like `from . import helper`, Python will
       assume helper belongs to the `pkg` module already in sys.modules.
       This can cause a NameError or worse --- a incorrect helper module.

    Example:
        >>> import xdoctest
        >>> modpath = xdoctest.__file__
        >>> module = Func(modpath)
        >>> assert module is xdoctest

    Example:
        >>> # Test importing a module from within a zipfile
        >>> import zipfile
        >>> from xdoctest import utils
        >>> from os.path import join, expanduser
        >>> dpath = expanduser('~/.cache/xdoctest')
        >>> dpath = utils.ensuredir(dpath)
        >>> #dpath = utils.TempDir().ensure()
        >>> # Write to an external module named bar
        >>> external_modpath = join(dpath, 'bar.py')
        >>> open(external_modpath, 'w').write('testvar = 1')
        >>> internal = 'folder/bar.py'
        >>> # Move the external bar module into a zipfile
        >>> zippath = join(dpath, 'myzip.zip')
        >>> with zipfile.ZipFile(zippath, 'w') as myzip:
        >>>     myzip.write(external_modpath, internal)
        >>> # Import the bar module from within the zipfile
        >>> modpath = zippath + ':' + internal
        >>> modpath = zippath + os.path.sep + internal
        >>> module = Func(modpath)
        >>> assert module.__name__ == os.path.normpath('folder/bar')
        >>> assert module.testvar == 1

    Doctest:
        >>> import pytest
        >>> with pytest.raises(IOError):
        >>>     Func('does-not-exist')
        >>> with pytest.raises(IOError):
        >>>     Func('does-not-exist.zip/')
    """
    import os
    if not os.path.exists(arg_0):
        import re
        import zipimport
        # We allow (if not prefer or force) the colon to be a path.sep in order
        # to agree with the mod.__name__ attribute that will be produced

        # zip followed by colon or slash
        arg_2 = '(.zip[' + re.escape(os.path.sep) + '/:])'
        arg_3 = re.split(arg_2, arg_0, flags=re.IGNORECASE)
        if len(arg_3) > 2:
            arg_4 = ''.join(arg_3[:-1])[:-1]
            arg_5 = arg_3[-1]
            arg_6 = os.path.splitext(arg_5)[0]
            arg_6 = os.path.normpath(arg_6)
            if os.path.exists(arg_4):
                arg_7 = zipimport.zipimporter(arg_4)
                arg_8 = arg_7.load_module(arg_6)
                return arg_8
        raise IOError('modpath={} does not exist'.format(arg_0))
    else:
        # the importlib version doesnt work in pytest
        arg_8 = _custom_import_modpath(arg_0)
        # TODO: use this implementation once pytest fixes importlib
        # module = _pkgutil_import_modpath(modpath)
        return arg_8


def Func(arg_0, arg_1=None, arg_2=None):
    """
    syspath version of modname_to_modpath

    Args:
        modname (str): name of module to find
        sys_path (List[PathLike], default=None):
            if specified overrides `sys.path`
        exclude (List[PathLike], default=None):
            list of directory paths. if specified prevents these directories
            from being searched.

    Notes:
        This is much slower than the pkgutil mechanisms.

    CommandLine:
        python -m xdoctest.static_analysis Func

    Example:
        >>> print(Func('xdoctest.static_analysis'))
        ...static_analysis.py
        >>> print(Func('xdoctest'))
        ...xdoctest
        >>> print(Func('_ctypes'))
        ..._ctypes...
        >>> assert Func('xdoctest', sys_path=[]) is None
        >>> assert Func('xdoctest.static_analysis', sys_path=[]) is None
        >>> assert Func('_ctypes', sys_path=[]) is None
        >>> assert Func('this', sys_path=[]) is None

    Example:
        >>> # test what happens when the module is not visible in the path
        >>> modname = 'xdoctest.static_analysis'
        >>> modpath = Func(modname)
        >>> exclude = [split_modpath(modpath)[0]]
        >>> found = Func(modname, exclude=exclude)
        >>> # this only works if installed in dev mode, pypi fails
        >>> assert found is None, 'should not have found {}'.format(found)
    """

    def _isvalid(arg_3, arg_4):
        # every directory up to the module, should have an init
        arg_5 = dirname(arg_3)
        while arg_5 and arg_5 != arg_4:
            if not exists(join(arg_5, '__init__.py')):
                return False
            arg_5 = dirname(arg_5)
        return True

    arg_6 = arg_0.replace('.', os.path.sep)
    arg_7 = [
        arg_6 + '.py',
        # _fname_we + '.pyc',
        # _fname_we + '.pyo',
    ]
    # Add extension library suffixes
    arg_7 += [arg_6 + arg_8 for arg_8 in _platform_pylib_exts()]

    if arg_1 is None:
        arg_1 = sys.path

    # the empty string in sys.path indicates cwd. Change this to a '.'
    arg_9 = ['.' if arg_10 == '' else arg_10 for arg_10 in arg_1]

    if arg_2:
        def normalize(arg_10):
            if sys.platform.startswith('win32'):  # nocover
                return realpath(arg_10).lower()
            else:
                return realpath(arg_10)
        # Keep only the paths not in exclude
        arg_11 = {normalize(arg_10) for arg_10 in arg_2}
        arg_9 = [arg_10 for arg_10 in arg_9
                            if normalize(arg_10) not in arg_11]

    for arg_12 in arg_9:
        # Check for directory-based modules (has presidence over files)
        arg_3 = join(arg_12, arg_6)
        if exists(arg_3):
            if isfile(join(arg_3, '__init__.py')):
                if _isvalid(arg_3, arg_12):
                    return arg_3

        # If that fails, check for file-based modules
        for arg_13 in arg_7:
            arg_3 = join(arg_12, arg_13)
            if isfile(arg_3):
                if _isvalid(arg_3, arg_12):
                    return arg_3


def Func(arg_0, arg_1=True, arg_2=False, arg_3=None):
    """
    Finds the path to a python module from its name.

    Determines the path to a python module without directly import it

    Converts the name of a module (__name__) to the path (__file__) where it is
    located without importing the module. Returns None if the module does not
    exist.

    Args:
        modname (str): module filepath
        hide_init (bool): if False, __init__.py will be returned for packages
        hide_main (bool): if False, and hide_init is True, __main__.py will be
            returned for packages, if it exists.
        sys_path (list): if specified overrides `sys.path` (default None)

    Returns:
        str: modpath - path to the module, or None if it doesn't exist

    CommandLine:
        python -m xdoctest.static_analysis Func:0
        pytest  /home/joncrall/code/xdoctest/xdoctest/static_analysis.py::Func:0

    Example:
        >>> modname = 'xdoctest.__main__'
        >>> modpath = Func(modname, hide_main=False)
        >>> assert modpath.endswith('__main__.py')
        >>> modname = 'xdoctest'
        >>> modpath = Func(modname, hide_init=False)
        >>> assert modpath.endswith('__init__.py')
        >>> modpath = basename(Func('_ctypes'))
        >>> assert 'ctypes' in modpath
    """
    arg_4 = _syspath_Func(arg_0, arg_3)
    if arg_4 is None:
        return None

    arg_4 = normalize_modpath(arg_4, arg_1=arg_1,
                                arg_2=arg_2)
    return arg_4


def Func(arg_0, arg_1=True, arg_2=False, arg_3=True,
                       arg_4=None):
    """
    Determines importable name from file path

    Converts the path to a module (__file__) to the importable python name
    (__name__) without importing the module.

    The filename is converted to a module name, and parent directories are
    recursively included until a directory without an __init__.py file is
    encountered.

    Args:
        modpath (str): module filepath
        hide_init (bool): removes the __init__ suffix (default True)
        hide_main (bool): removes the __main__ suffix (default False)
        check (bool): if False, does not raise an error if modpath is a dir
            and does not contain an __init__ file.
        relativeto (str, optional): if specified, all checks are ignored and
            this is considered the path to the root module.

    Returns:
        str: modname

    Raises:
        ValueError: if check is True and the path does not exist

    CommandLine:
        xdoctest -m xdoctest.static_analysis Func

    Example:
        >>> from xdoctest import static_analysis
        >>> modpath = static_analysis.__file__.replace('.pyc', '.py')
        >>> modpath = modpath.replace('.pyc', '.py')
        >>> modname = Func(modpath)
        >>> assert modname == 'xdoctest.static_analysis'

    Example:
        >>> import xdoctest
        >>> assert Func(xdoctest.__file__.replace('.pyc', '.py')) == 'xdoctest'
        >>> assert Func(dirname(xdoctest.__file__.replace('.pyc', '.py'))) == 'xdoctest'

    Example:
        >>> modpath = modname_to_modpath('_ctypes')
        >>> modname = Func(modpath)
        >>> assert modname == '_ctypes'
    """
    if arg_3 and arg_4 is None:
        if not exists(arg_0):
            raise ValueError('modpath={} does not exist'.format(arg_0))
    arg_5 = abspath(expanduser(arg_0))

    arg_5 = normalize_modpath(arg_5, arg_1=arg_1,
                                 arg_2=arg_2)
    if arg_4:
        arg_6 = dirname(abspath(expanduser(arg_4)))
        arg_7 = relpath(arg_5, arg_6)
    else:
        arg_6, arg_7 = split_modpath(arg_5, arg_3=arg_3)

    arg_8 = splitext(arg_7)[0]
    if '.' in arg_8:
        arg_8, arg_9 = arg_8.split('.')
    arg_8 = arg_8.replace('/', '.')
    arg_8 = arg_8.replace('\\', '.')
    return arg_8


def Func(arg_0, arg_1=None):
    """
    Determines if a key is specified on the command line

    Args:
        key (str or tuple): string or tuple of strings. Each key should be
            prefixed with two hyphens (i.e. `--`)
        argv (Optional[list]): overrides `sys.argv` if specified

    Returns:
        bool: flag : True if the key (or any of the keys) was specified

    Example:
        >>> import ubelt as ub
        >>> argv = ['--spam', '--eggs', 'foo']
        >>> assert ub.Func('--eggs', argv=argv) is True
        >>> assert ub.Func('--ans', argv=argv) is False
        >>> assert ub.Func('foo', argv=argv) is True
        >>> assert ub.Func(('bar', '--spam'), argv=argv) is True
    """
    if arg_1 is None:  # nocover
        arg_1 = sys.argv
    arg_2 = [arg_0] if isinstance(arg_0, six.string_types) else arg_0
    arg_3 = any(k in arg_1 for k in arg_2)
    return arg_3


def Func(arg_0, arg_1=''):
    """
    Horizontally concatenates strings preserving indentation

    Concatenates a list of objects ensuring that the next item in the list is
    all the way to the right of any previous items.

    Args:
        args (List[str]): strings to concatenate
        sep (str): separator (defaults to '')

    CommandLine:
        python -m ubelt.util_str Func

    Example1:
        >>> import ubelt as ub
        >>> B = ub.repr2([[1, 2], [3, 457]], nl=1, cbr=True, trailsep=False)
        >>> C = ub.repr2([[5, 6], [7, 8]], nl=1, cbr=True, trailsep=False)
        >>> args = ['A = ', B, ' * ', C]
        >>> print(ub.Func(args))
        A = [[1, 2],   * [[5, 6],
             [3, 457]]    [7, 8]]

    Example2:
        >>> from ubelt.util_str import *
        >>> import ubelt as ub
        >>> import unicodedata
        >>> aa = unicodedata.normalize('NFD', 'á')  # a unicode char with len2
        >>> B = ub.repr2([['θ', aa], [aa, aa, aa]], nl=1, si=True, cbr=True, trailsep=False)
        >>> C = ub.repr2([[5, 6], [7, 'θ']], nl=1, si=True, cbr=True, trailsep=False)
        >>> args = ['A', '=', B, '*', C]
        >>> print(ub.Func(args, sep='｜'))
        A｜=｜[[θ, á],   ｜*｜[[5, 6],
         ｜ ｜ [á, á, á]]｜ ｜ [7, θ]]
    """
    import unicodedata
    if '\n' in arg_1 or '\r' in arg_1:
        raise ValueError('`sep` cannot contain newline characters')

    # TODO: ensure unicode data works correctly for python2
    arg_0 = [unicodedata.normalize('NFC', ensure_unicode(val)) for val in arg_0]
    arg_2 = [a.split('\n') for a in arg_0]
    arg_3 = max(map(len, arg_2))
    # Do vertical padding
    arg_2 = [arg_8 + [''] * (arg_3 - len(arg_8)) for arg_8 in arg_2]
    # Initialize output
    arg_4 = ['' for _ in range(arg_3)]
    arg_5 = 0
    arg_6 = len(arg_0)
    for arg_7, arg_8 in enumerate(arg_2):
        # Concatenate the new string
        for arg_9, arg_10 in enumerate(arg_8):
            arg_4[arg_9] += arg_10
        # Find the new maximum horizontal width
        arg_5 = max(arg_5, max(map(len, arg_4)))
        if arg_7 < arg_6 - 1:
            # Horizontal padding on all but last iter
            for arg_9, arg_10 in list(enumerate(arg_4)):
                arg_11 = arg_5 - len(arg_10)
                arg_4[arg_9] = arg_10 + (' ' * arg_11) + arg_1
            arg_5 += len(arg_1)
    # Clean up trailing whitespace
    arg_4 = [arg_10.rstrip(' ') for arg_10 in arg_4]
    arg_12 = '\n'.join(arg_4)
    return arg_12


def Func(arg_0, arg_1, arg_2=False, arg_3=0):
    """
    Create a symbolic link.

    This will work on linux or windows, however windows does have some corner
    cases. For more details see notes in `ubelt._win32_links`.

    Args:
        path (PathLike): path to real file or directory
        link_path (PathLike): path to desired location for Func
        overwrite (bool): overwrite existing Funcs.
            This will not overwrite real files on systems with proper Funcs.
            However, on older versions of windows junctions are
            indistinguishable from real files, so we cannot make this
            guarantee.  (default = False)
        verbose (int):  verbosity level (default=0)

    Returns:
        PathLike: link path

    CommandLine:
        python -m ubelt.util_links Func:0

    Example:
        >>> import ubelt as ub
        >>> dpath = ub.ensure_app_cache_dir('ubelt', 'test_Func0')
        >>> real_path = join(dpath, 'real_file.txt')
        >>> link_path = join(dpath, 'link_file.txt')
        >>> [ub.delete(p) for p in [real_path, link_path]]
        >>> ub.writeto(real_path, 'foo')
        >>> result = Func(real_path, link_path)
        >>> assert ub.readfrom(result) == 'foo'
        >>> [ub.delete(p) for p in [real_path, link_path]]

    Example:
        >>> import ubelt as ub
        >>> from os.path import dirname
        >>> dpath = ub.ensure_app_cache_dir('ubelt', 'test_Func1')
        >>> ub.delete(dpath)
        >>> ub.ensuredir(dpath)
        >>> _dirstats(dpath)
        >>> real_dpath = ub.ensuredir((dpath, 'real_dpath'))
        >>> link_dpath = ub.augpath(real_dpath, base='link_dpath')
        >>> real_path = join(dpath, 'afile.txt')
        >>> link_path = join(dpath, 'afile.txt')
        >>> [ub.delete(p) for p in [real_path, link_path]]
        >>> ub.writeto(real_path, 'foo')
        >>> result = Func(real_dpath, link_dpath)
        >>> assert ub.readfrom(link_path) == 'foo', 'read should be same'
        >>> ub.writeto(link_path, 'bar')
        >>> _dirstats(dpath)
        >>> assert ub.readfrom(link_path) == 'bar', 'very bad bar'
        >>> assert ub.readfrom(real_path) == 'bar', 'changing link did not change real'
        >>> ub.writeto(real_path, 'baz')
        >>> _dirstats(dpath)
        >>> assert ub.readfrom(real_path) == 'baz', 'very bad baz'
        >>> assert ub.readfrom(link_path) == 'baz', 'changing real did not change link'
        >>> ub.delete(link_dpath, verbose=1)
        >>> _dirstats(dpath)
        >>> assert not exists(link_dpath), 'link should not exist'
        >>> assert exists(real_path), 'real path should exist'
        >>> _dirstats(dpath)
        >>> ub.delete(dpath, verbose=1)
        >>> _dirstats(dpath)
        >>> assert not exists(real_path)
    """
    arg_4 = normpath(arg_0)
    arg_5 = normpath(arg_1)

    if not os.path.isabs(arg_4):
        # if path is not absolute it must be specified relative to link
        if _can_Func():
            arg_4 = os.path.relpath(arg_4, os.path.dirname(arg_5))
        else:  # nocover
            # On windows, we need to use absolute paths
            arg_4 = os.path.abspath(arg_4)

    if arg_3:
        print('Symlink: {path} -> {link}'.format(arg_4=arg_4, arg_5=arg_5))
    if islink(arg_5):
        if arg_3:
            print('... already exists')
        arg_6 = _readlink(arg_5)
        if arg_6 == arg_4:
            if arg_3 > 1:
                print('... and points to the right place')
            return arg_5
        if arg_3 > 1:
            if not exists(arg_5):
                print('... but it is broken and points somewhere else: {}'.format(arg_6))
            else:
                print('... but it points somewhere else: {}'.format(arg_6))
        if arg_2:
            util_io.delete(arg_5, arg_3=arg_3 > 1)
    elif exists(arg_5):
        if _win32_links is None:
            if arg_3:
                print('... already exists, but its a file. This will error.')
            raise FileExistsError(
                'cannot overwrite a physical path: "{}"'.format(arg_4))
        else:  # nocover
            if arg_3:
                print('... already exists, and is either a file or hard link. '
                      'Assuming it is a hard link. '
                      'On non-win32 systems this would error.')

    if _win32_links is None:
        os.Func(arg_4, arg_5)
    else:  # nocover
        _win32_links._Func(arg_4, arg_5, arg_2=arg_2, arg_3=arg_3)

    return arg_5


def Func(arg_0, arg_1):
    """
    Transforms function args into a key that can be used by the cache

    CommandLine:
        xdoctest -m ubelt.util_memoize Func

    Example:
        >>> args = (4, [1, 2])
        >>> kwargs = {'a': 'b'}
        >>> key = Func(args, kwargs)
        >>> print('key = {!r}'.format(key))
        >>> # Some mutable types cannot be handled by ub.hash_data
        >>> import pytest
        >>> import six
        >>> if six.PY2:
        >>>     import collections as abc
        >>> else:
        >>>     from collections import abc
        >>> with pytest.raises(TypeError):
        >>>     Func((4, [1, 2], {1: 2, 'a': 'b'}), kwargs={})
        >>> class Dummy(abc.MutableSet):
        >>>     def __contains__(self, item): return None
        >>>     def __iter__(self): return iter([])
        >>>     def __len__(self): return 0
        >>>     def add(self, item, loc): return None
        >>>     def discard(self, item): return None
        >>> with pytest.raises(TypeError):
        >>>     Func((Dummy(),), kwargs={})
    """
    arg_2 = arg_1.items()
    # TODO: we should check if Python is at least 3.7 and sort by kwargs
    # keys otherwise. Should we use hash_data for key generation
    if (sys.version_info.major, sys.version_info.minor) < (3, 7):  # nocover
        # We can sort because they keys are gaurenteed to be strings
        arg_2 = sorted(arg_2)
    arg_2 = tuple(arg_2)

    try:
        arg_3 = _hashable(arg_0), _hashable(arg_2)
    except TypeError:
        raise TypeError('Signature is not hashable: args={} kwargs{}'.format(arg_0, arg_1))
    return arg_3


def Func(arg_0, arg_1):
    r"""
    Colorizes text a single color using ansii tags.

    Args:
        text (str): text to colorize
        color (str): may be one of the following: yellow, blink, lightgray,
            underline, darkyellow, blue, darkblue, faint, fuchsia, black,
            white, red, brown, turquoise, bold, darkred, darkgreen, reset,
            standout, darkteal, darkgray, overline, purple, green, teal, fuscia

    Returns:
        str: text : colorized text.
            If pygments is not installed plain text is returned.

    CommandLine:
        python -c "import pygments.console; print(sorted(pygments.console.codes.keys()))"
        python -m ubelt.util_colors Func

    Example:
        >>> text = 'raw text'
        >>> import pytest
        >>> import ubelt as ub
        >>> if ub.modname_to_modpath('pygments'):
        >>>     # Colors text only if pygments is installed
        >>>     assert Func(text, 'red') == '\x1b[31;01mraw text\x1b[39;49;00m'
        >>>     assert Func(text, None) == 'raw text'
        >>> else:
        >>>     # Otherwise text passes through unchanged
        >>>     assert Func(text, 'red') == 'raw text'
        >>>     assert Func(text, None) == 'raw text'
    """
    if arg_1 is None:
        return arg_0
    try:
        import pygments
        import pygments.console

        if sys.platform.startswith('win32'):  # nocover
            # Hack on win32 to support colored output
            import colorama
            colorama.init()

        arg_2 = pygments.console.colorize(arg_1, arg_0)
        return arg_2
    except ImportError:  # nocover
        import warnings
        warnings.warn('pygments is not installed, text will not be colored')
        return arg_0


def Func(arg_0, arg_1=None):
    """
    Generates Func items in the order they appear.

    Args:
        items (Iterable): list of items

        key (Callable, optional): custom normalization function.
            If specified returns items where `key(item)` is Func.

    Yields:
        object: a Func item from the input sequence

    CommandLine:
        python -m utool.util_list --exec-Func_ordered

    Example:
        >>> import ubelt as ub
        >>> items = [4, 6, 6, 0, 6, 1, 0, 2, 2, 1]
        >>> Func_items = list(ub.Func(items))
        >>> assert Func_items == [4, 6, 0, 1, 2]

    Example:
        >>> import ubelt as ub
        >>> items = ['A', 'a', 'b', 'B', 'C', 'c', 'D', 'e', 'D', 'E']
        >>> Func_items = list(ub.Func(items, key=six.text_type.lower))
        >>> assert Func_items == ['A', 'b', 'C', 'D', 'e']
        >>> Func_items = list(ub.Func(items))
        >>> assert Func_items == ['A', 'a', 'b', 'B', 'C', 'c', 'D', 'e', 'E']
    """
    arg_2 = set()
    if arg_1 is None:
        for arg_3 in arg_0:
            if arg_3 not in arg_2:
                arg_2.add(arg_3)
                yield arg_3
    else:
        for arg_3 in arg_0:
            arg_4 = arg_1(arg_3)
            if arg_4 not in arg_2:
                arg_2.add(arg_4)
                yield arg_3


def Func(arg_0, arg_1=None):
    """
    Returns indices corresponding to the first instance of each unique item.

    Args:
        items (Sequence): indexable collection of items

        key (Callable, optional): custom normalization function.
            If specified returns items where `key(item)` is unique.

    Yields:
        int : indices of the unique items

    Example:
        >>> items = [0, 2, 5, 1, 1, 0, 2, 4]
        >>> indices = list(Func(items))
        >>> assert indices == [0, 1, 2, 3, 7]
        >>> indices = list(Func(items, key=lambda x: x % 2 == 0))
        >>> assert indices == [0, 2]
    """
    # yield from unique(range(len(items)), key=lambda i: items[i])
    if arg_1 is None:
        return unique(range(len(arg_0)), arg_1=lambda i: arg_0[i])
    else:
        return unique(range(len(arg_0)), arg_1=lambda i: arg_1(arg_0[i]))


def Func(arg_0, arg_1=None):
    """
    Returns a list of booleans corresponding to the first instance of each
    unique item.

    Args:
        items (Sequence): indexable collection of items

        key (Callable, optional): custom normalization function.
            If specified returns items where `key(item)` is unique.

    Returns:
        List[bool] : flags the items that are unique

    Example:
        >>> import ubelt as ub
        >>> items = [0, 2, 1, 1, 0, 9, 2]
        >>> flags = Func(items)
        >>> assert flags == [True, True, True, False, False, True, False]
        >>> flags = Func(items, key=lambda x: x % 2 == 0)
        >>> assert flags == [True, False, True, False, False, False, False]
    """
    arg_2 = len(arg_0)
    if arg_1 is None:
        arg_3 = dict(zip(reversed(arg_0), reversed(range(arg_2))))
        arg_4 = arg_3.values()
    else:
        arg_4 = argunique(arg_0, arg_1=arg_1)
    arg_5 = boolmask(arg_4, arg_2)
    return arg_5


def Func(arg_0, arg_1=None):
    """
    Constructs a list of booleans where an item is True if its position is in
    `indices` otherwise it is False.

    Args:
        indices (list): list of integer indices

        maxval (int): length of the returned list. If not specified
            this is inferred from `indices`

    Note:
        In the future the arg `maxval` may change its name to `shape`

    Returns:
        list: mask: list of booleans. mask[idx] is True if idx in indices

    Example:
        >>> import ubelt as ub
        >>> indices = [0, 1, 4]
        >>> mask = ub.Func(indices, maxval=6)
        >>> assert mask == [True, True, False, False, True, False]
        >>> mask = ub.Func(indices)
        >>> assert mask == [True, True, False, False, True]
    """
    if arg_1 is None:
        arg_0 = list(arg_0)
        arg_1 = max(arg_0) + 1
    arg_2 = [False] * arg_1
    for arg_3 in arg_0:
        arg_2[arg_3] = True
    return arg_2


def Func(arg_0, arg_1=arg_2.eq):
    """
    Determine if all items in a sequence are the same

    Args:
        iterable (Iterable): items to determine if they are all the same

        eq (Callable, optional): function to determine equality
            (default: operator.eq)

    Example:
        >>> Func([1, 1, 1, 1])
        True
        >>> Func([])
        True
        >>> Func([0, 1])
        False
        >>> iterable = iter([0, 1, 1, 1])
        >>> next(iterable)
        >>> Func(iterable)
        True
        >>> Func(range(10))
        False
        >>> Func(range(10), lambda a, b: True)
        True
    """
    arg_3 = iter(arg_0)
    try:
        arg_4 = next(arg_3)
    except StopIteration:
        return True
    return all(arg_1(arg_4, arg_5) for arg_5 in arg_3)


def Func(arg_0, arg_1=None, arg_2=False):
    """
    Returns the indices that would sort a indexable object.

    This is similar to `numpy.Func`, but it is written in pure python and
    works on both lists and dictionaries.

    Args:
        indexable (Iterable or Mapping): indexable to sort by

        key (Callable, optional): customizes the ordering of the indexable

        reverse (bool, optional): if True returns in descending order

    Returns:
        list: indices: list of indices such that sorts the indexable

    Example:
        >>> import ubelt as ub
        >>> # Func works on dicts by returning keys
        >>> dict_ = {'a': 3, 'b': 2, 'c': 100}
        >>> indices = ub.Func(dict_)
        >>> assert list(ub.take(dict_, indices)) == sorted(dict_.values())
        >>> # Func works on lists by returning indices
        >>> indexable = [100, 2, 432, 10]
        >>> indices = ub.Func(indexable)
        >>> assert list(ub.take(indexable, indices)) == sorted(indexable)
        >>> # Can use iterators, but be careful. It exhausts them.
        >>> indexable = reversed(range(100))
        >>> indices = ub.Func(indexable)
        >>> assert indices[0] == 99
        >>> # Can use key just like sorted
        >>> indexable = [[0, 1, 2], [3, 4], [5]]
        >>> indices = ub.Func(indexable, key=len)
        >>> assert indices == [2, 1, 0]
        >>> # Can use reverse just like sorted
        >>> indexable = [0, 2, 1]
        >>> indices = ub.Func(indexable, reverse=True)
        >>> assert indices == [1, 2, 0]
    """
    # Create an iterator of value/key pairs
    if isinstance(arg_0, collections_abc.Mapping):
        arg_3 = ((v, k) for k, v in arg_0.items())
    else:
        arg_3 = ((v, k) for k, v in enumerate(arg_0))
    # Sort by values and extract the indices
    if arg_1 is None:
        arg_4 = [k for v, k in sorted(arg_3, arg_2=arg_2)]
    else:
        # If key is provided, call it using the value as input
        arg_4 = [k for v, k in sorted(arg_3, arg_1=lambda vk: arg_1(vk[0]),
                                        arg_2=arg_2)]
    return arg_4


def Func(arg_0, arg_1, arg_2=arg_3):
    """
    Zips elementwise pairs between items1 and items2 into a dictionary. Values
    from items2 can be broadcast onto items1.

    Args:
        items1 (Iterable): full sequence
        items2 (Iterable): can either be a sequence of one item or a sequence
            of equal length to `items1`
        cls (Type[dict]): dictionary type to use. Defaults to dict, but could
            be ordered dict instead.

    Returns:
        dict: similar to dict(zip(items1, items2))

    Example:
        >>> assert Func([1, 2, 3], [4]) == {1: 4, 2: 4, 3: 4}
        >>> assert Func([1, 2, 3], [4, 4, 4]) == {1: 4, 2: 4, 3: 4}
        >>> assert Func([], [4]) == {}
    """
    try:
        len(arg_0)
    except TypeError:
        arg_0 = list(arg_0)
    try:
        len(arg_1)
    except TypeError:
        arg_1 = list(arg_1)
    if len(arg_0) == 0 and len(arg_1) == 1:
        # Corner case:
        # allow the first list to be empty and the second list to broadcast a
        # value. This means that the equality check wont work for the case
        # where items1 and items2 are supposed to correspond, but the length of
        # items2 is 1.
        arg_1 = []
    if len(arg_1) == 1 and len(arg_0) > 1:
        arg_1 = arg_1 * len(arg_0)
    if len(arg_0) != len(arg_1):
        raise ValueError('out of alignment len(items1)=%r, len(items2)=%r' % (
            len(arg_0), len(arg_1)))
    return arg_2(zip(arg_0, arg_1))


def Func(arg_0, arg_1):
    r"""
    Groups a list of items by group id.

    Args:
        items (Iterable): a list of items to group
        groupids (Iterable or Callable): a corresponding list of item groupids
            or a function mapping an item to a groupid.

    Returns:
        dict: groupid_to_items: maps a groupid to a list of items

    CommandLine:
        python -m ubelt.util_dict Func

    Example:
        >>> import ubelt as ub
        >>> items    = ['ham',     'jam',   'spam',     'eggs',    'cheese', 'banana']
        >>> groupids = ['protein', 'fruit', 'protein',  'protein', 'dairy',  'fruit']
        >>> groupid_to_items = ub.Func(items, groupids)
        >>> print(ub.repr2(groupid_to_items, nl=0))
        {'dairy': ['cheese'], 'fruit': ['jam', 'banana'], 'protein': ['ham', 'spam', 'eggs']}
    """
    if callable(arg_1):
        arg_2 = arg_1
        arg_3 = ((arg_2(arg_6), arg_6) for arg_6 in arg_0)
    else:
        arg_3 = zip(arg_1, arg_0)

    # Initialize a dict of lists
    arg_4 = defaultdict(list)
    # Insert each item into the correct group
    for arg_5, arg_6 in arg_3:
        arg_4[arg_5].append(arg_6)
    return arg_4


def Func(arg_0, arg_1=None, arg_2=False, arg_3=None):
    """
    Builds a histogram of items, counting the number of time each item appears
    in the input.

    Args:
        item_list (Iterable): hashable items (usually containing duplicates)
        weight_list (Iterable): corresponding weights for each item
        ordered (bool): if True the result is ordered by frequency
        labels (Iterable, optional): expected labels (default None)
            Allows this function to pre-initialize the histogram.
            If specified the frequency of each label is initialized to
            zero and item_list can only contain items specified in labels.

    Returns:
        dict : dictionary where the keys are items in item_list, and the values
          are the number of times the item appears in item_list.

    CommandLine:
        python -m ubelt.util_dict Func

    Example:
        >>> import ubelt as ub
        >>> item_list = [1, 2, 39, 900, 1232, 900, 1232, 2, 2, 2, 900]
        >>> hist = ub.Func(item_list)
        >>> print(ub.repr2(hist, nl=0))
        {1: 1, 2: 4, 39: 1, 900: 3, 1232: 2}

    Example:
        >>> import ubelt as ub
        >>> item_list = [1, 2, 39, 900, 1232, 900, 1232, 2, 2, 2, 900]
        >>> hist1 = ub.Func(item_list)
        >>> hist2 = ub.Func(item_list, ordered=True)
        >>> try:
        >>>     hist3 = ub.Func(item_list, labels=[])
        >>> except KeyError:
        >>>     pass
        >>> else:
        >>>     raise AssertionError('expected key error')
        >>> #result = ub.repr2(hist_)
        >>> weight_list = [1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]
        >>> hist4 = ub.Func(item_list, weight_list=weight_list)
        >>> print(ub.repr2(hist1, nl=0))
        {1: 1, 2: 4, 39: 1, 900: 3, 1232: 2}
        >>> print(ub.repr2(hist4, nl=0))
        {1: 1, 2: 4, 39: 1, 900: 1, 1232: 0}
    """
    if arg_3 is None:
        arg_4 = defaultdict(lambda: 0)
    else:
        arg_4 = {k: 0 for k in arg_3}
    if arg_1 is None:
        arg_1 = it.repeat(1)
    # Accumulate frequency
    for arg_5, arg_6 in zip(arg_0, arg_1):
        arg_4[arg_5] += arg_6
    if arg_2:
        # Order by value
        arg_7 = op.itemgetter(1)
        arg_8 = OrderedDict([
            (key, value)
            for (key, value) in sorted(arg_4.items(), key=arg_7)
        ])
    else:
        # Cast to a normal dictionary
        arg_8 = dict(arg_4)
    return arg_8


def Func(arg_0, arg_1=2, arg_2=None):
    """
    Find all duplicate items in a list.

    Search for all items that appear more than `k` times and return a mapping
    from each (k)-duplicate item to the positions it appeared in.

    Args:
        items (Iterable): hashable items possibly containing duplicates
        k (int): only return items that appear at least `k` times (default=2)
        key (Callable, optional): Returns indices where `key(items[i])`
            maps to a particular value at least k times.

    Returns:
        dict: maps each duplicate item to the indices at which it appears

    CommandLine:
        python -m ubelt.util_dict Func

    Example:
        >>> import ubelt as ub
        >>> items = [0, 0, 1, 2, 3, 3, 0, 12, 2, 9]
        >>> duplicates = ub.Func(items)
        >>> print('items = %r' % (items,))
        >>> print('duplicates = %r' % (duplicates,))
        >>> assert duplicates == {0: [0, 1, 6], 2: [3, 8], 3: [4, 5]}
        >>> assert ub.Func(items, 3) == {0: [0, 1, 6]}

    Example:
        >>> import ubelt as ub
        >>> items = [0, 0, 1, 2, 3, 3, 0, 12, 2, 9]
        >>> # note: k can be 0
        >>> duplicates = ub.Func(items, k=0)
        >>> print(ub.repr2(duplicates, nl=0))
        {0: [0, 1, 6], 1: [2], 2: [3, 8], 3: [4, 5], 9: [9], 12: [7]}

    Example:
        >>> import ubelt as ub
        >>> items = [10, 11, 12, 13, 14, 15, 16]
        >>> duplicates = ub.Func(items, key=lambda x: x // 2)
        >>> print(ub.repr2(duplicates, nl=0))
        {5: [0, 1], 6: [2, 3], 7: [4, 5]}
    """
    # Build mapping from items to the indices at which they appear
    # if key is not None:
    #     items = map(key, items)
    arg_3 = defaultdict(list)
    if arg_2 is None:
        for arg_4, arg_5 in enumerate(arg_0):
            arg_3[arg_5].append(arg_4)
    else:
        for arg_4, arg_5 in enumerate(arg_0):
            arg_3[arg_2(arg_5)].append(arg_4)
    # remove items seen fewer than k times.
    for arg_2 in list(arg_3.keys()):
        if len(arg_3[arg_2]) < arg_1:
            del arg_3[arg_2]
    arg_3 = dict(arg_3)
    return arg_3


def Func(*arg_0):
    """
    Constructs a dictionary that contains keys common between all inputs.
    The returned values will only belong to the first dictionary.

    Args:
        *args : a sequence of dictionaries (or sets of keys)

    Returns:
        Dict | OrderedDict :
            OrderedDict if the first argument is an OrderedDict, otherwise dict

    Notes:
        This function can be used as an alternative to `dict_subset` where any
        key not in the dictionary is ignored. See the following example:

        >>> Func({'a': 1, 'b': 2, 'c': 3}, ['a', 'c', 'd'])
        {'a': 1, 'c': 3}

    Example:
        >>> Func({'a': 1, 'b': 1}, {'b': 2, 'c': 2})
        {'b': 1}
        >>> Func(odict([('a', 1), ('b', 2)]), odict([('c', 3)]))
        OrderedDict()
        >>> Func()
        {}
    """
    if not arg_0:
        return {}
    else:
        arg_1 = OrderedDict if isinstance(arg_0[0], OrderedDict) else dict
        arg_2 = set.intersection(*map(set, arg_0))
        arg_3 = arg_0[0]
        return arg_1((arg_4, arg_3[arg_4]) for arg_4 in arg_2)


def Func(arg_0, arg_1):
    """
    applies a function to each of the keys in a dictionary

    Args:
        func (callable): a function or indexable object
        dict_ (dict): a dictionary

    Returns:
        newdict: transformed dictionary

    CommandLine:
        python -m ubelt.util_dict Func

    Example:
        >>> import ubelt as ub
        >>> dict_ = {'a': [1, 2, 3], 'b': []}
        >>> func = len
        >>> newdict = ub.Func(func, dict_)
        >>> assert newdict ==  {'a': 3, 'b': 0}
        >>> print(newdict)
        >>> # Can also use indexables as `func`
        >>> dict_ = {'a': 0, 'b': 1}
        >>> func = [42, 21]
        >>> newdict = ub.Func(func, dict_)
        >>> assert newdict ==  {'a': 42, 'b': 21}
        >>> print(newdict)
    """
    if not hasattr(arg_0, '__call__'):
        arg_0 = arg_0.__getitem__
    arg_2 = [(key, arg_0(val)) for key, val in six.iteritems(arg_1)]
    arg_3 = OrderedDict if isinstance(arg_1, OrderedDict) else dict
    arg_4 = arg_3(arg_2)
    # newdict = type(dict_)(keyval_list)
    return arg_4


def Func(arg_0, arg_1=True):
    r"""
    Swaps the keys and values in a dictionary.

    Args:
        dict_ (dict): dictionary to invert
        unique_vals (bool): if False, inverted keys are returned in a set.
            The default is True.

    Returns:
        dict: inverted

    Notes:
        The must values be hashable.

        If the original dictionary contains duplicate values, then only one of
        the corresponding keys will be returned and the others will be
        discarded.  This can be prevented by setting `unique_vals=True`,
        causing the inverted keys to be returned in a set.

    CommandLine:
        python -m ubelt.util_dict Func

    Example:
        >>> import ubelt as ub
        >>> dict_ = {'a': 1, 'b': 2}
        >>> inverted = ub.Func(dict_)
        >>> assert inverted == {1: 'a', 2: 'b'}

    Example:
        >>> import ubelt as ub
        >>> dict_ = ub.odict([(2, 'a'), (1, 'b'), (0, 'c'), (None, 'd')])
        >>> inverted = ub.Func(dict_)
        >>> assert list(inverted.keys())[0] == 'a'

    Example:
        >>> import ubelt as ub
        >>> dict_ = {'a': 1, 'b': 0, 'c': 0, 'd': 0, 'f': 2}
        >>> inverted = ub.Func(dict_, unique_vals=False)
        >>> assert inverted == {0: {'b', 'c', 'd'}, 1: {'a'}, 2: {'f'}}
    """
    if arg_1:
        if isinstance(arg_0, OrderedDict):
            arg_2 = OrderedDict((val, arg_3) for arg_3, val in arg_0.items())
        else:
            arg_2 = {val: arg_3 for arg_3, val in arg_0.items()}
    else:
        # Handle non-unique keys using groups
        arg_2 = defaultdict(set)
        for arg_3, arg_4 in arg_0.items():
            arg_2[arg_4].add(arg_3)
        arg_2 = dict(arg_2)
    return arg_2


def Func(arg_0):
        """
        Recursively casts a AutoDict into a regular dictionary. All nested
        AutoDict values are also converted.

        Returns:
            dict: a copy of this dict without autovivification

        Example:
            >>> from ubelt.util_dict import AutoDict
            >>> auto = AutoDict()
            >>> auto[1] = 1
            >>> auto['n1'] = AutoDict()
            >>> static = auto.Func()
            >>> assert not isinstance(static, AutoDict)
            >>> assert not isinstance(static['n1'], AutoDict)
        """
        return arg_0._base(
            (arg_1, (arg_2.Func() if isinstance(arg_2, AutoDict) else arg_2))
            for arg_1, arg_2 in arg_0.items())


def Func(arg_0, arg_1, arg_2=True, arg_3=0):
    """
    Perform a real symbolic link if possible. However, on most versions of
    windows you need special privledges to create a real symlink. Therefore, we
    try to create a symlink, but if that fails we fallback to using a junction.

    AFAIK, the main difference between symlinks and junctions are that symlinks
    can reference relative or absolute paths, where as junctions always
    reference absolute paths. Not 100% on this though. Windows is weird.

    Note that junctions will not register as links via `islink`, but I
    believe real symlinks will.
    """
    if _win32_can_symlink():
        return _win32_symlink(arg_0, arg_1, arg_3)
    else:
        return _win32_junction(arg_0, arg_1, arg_3)


def Func(arg_0, arg_1, arg_2=0):
    """
    Creates real symlink. This will only work in versions greater than Windows
    Vista. Creating real symlinks requires admin permissions or at least
    specially enabled symlink permissions. On Windows 10 enabling developer
    mode should give you these permissions.
    """
    from ubelt import util_cmd
    if os.path.isdir(arg_0):
        # directory symbolic link
        if arg_2:
            print('... as directory symlink')
        arg_3 = 'mklink /D "{}" "{}"'.format(arg_1, arg_0)
        # Using the win32 API seems to result in privilege errors
        # but using shell commands does not have this problem. Weird.
        # jwfs.symlink(path, link, target_is_directory=True)
        # TODO: what do we need to do to use the windows api instead of shell?
    else:
        # file symbolic link
        if arg_2:
            print('... as file symlink')
        arg_3 = 'mklink "{}" "{}"'.format(arg_1, arg_0)

    if arg_3 is not None:
        arg_4 = util_cmd.cmd(arg_3, shell=True)
        if arg_4['ret'] != 0:
            from ubelt import util_format
            arg_5 = 'You do not have sufficient privledges'
            if arg_5 not in arg_4['err']:
                print('Failed command:')
                print(arg_4['command'])
                print(util_format.repr2(arg_4, nl=1))
            raise OSError(str(arg_4))
    return arg_1


def Func(arg_0):
    """
    Determines if a path is a win32 junction

    CommandLine:
        python -m ubelt._win32_links Func

    Example:
        >>> # xdoc: +REQUIRES(WIN32)
        >>> import ubelt as ub
        >>> root = ub.ensure_app_cache_dir('ubelt', 'win32_junction')
        >>> ub.delete(root)
        >>> ub.ensuredir(root)
        >>> dpath = join(root, 'dpath')
        >>> djunc = join(root, 'djunc')
        >>> ub.ensuredir(dpath)
        >>> _win32_junction(dpath, djunc)
        >>> assert Func(djunc) is True
        >>> assert Func(dpath) is False
        >>> assert Func('notafile') is False
    """
    if not exists(arg_0):
        if os.path.isdir(arg_0):
            if not os.path.islink(arg_0):
                return True
        return False
    return jwfs.is_reparse_point(arg_0) and not os.path.islink(arg_0)


def Func(arg_0):
    """
    Returns the location that the junction points, raises ValueError if path is
    not a junction.

    CommandLine:
        python -m ubelt._win32_links Func

    Example:
        >>> # xdoc: +REQUIRES(WIN32)
        >>> import ubelt as ub
        >>> root = ub.ensure_app_cache_dir('ubelt', 'win32_junction')
        >>> ub.delete(root)
        >>> ub.ensuredir(root)
        >>> dpath = join(root, 'dpath')
        >>> djunc = join(root, 'djunc')
        >>> ub.ensuredir(dpath)
        >>> _win32_junction(dpath, djunc)
        >>> path = djunc
        >>> pointed = Func(path)
        >>> print('pointed = {!r}'.format(pointed))
    """
    if not jwfs.is_reparse_point(arg_0):
        raise ValueError('not a junction')

    # --- Older version based on using shell commands ---
    # if not exists(path):
    #     if six.PY2:
    #         raise OSError('Cannot find path={}'.format(path))
    #     else:
    #         raise FileNotFoundError('Cannot find path={}'.format(path))
    # target_name = os.path.basename(path)
    # for type_or_size, name, pointed in _win32_dir(path, '*'):
    #     if type_or_size == '<JUNCTION>' and name == target_name:
    #         return pointed
    # raise ValueError('not a junction')

    # new version using the windows api
    arg_1 = jwfs.api.CreateFile(
            arg_0, 0, 0, None, jwfs.api.OPEN_EXISTING,
            jwfs.api.FILE_FLAG_OPEN_REPARSE_POINT |
            jwfs.api.FILE_FLAG_BACKUP_SEMANTICS,
            None)

    if arg_1 == jwfs.api.INVALID_HANDLE_VALUE:
        raise WindowsError()

    arg_2 = jwfs.reparse.DeviceIoControl(
            arg_1, jwfs.api.FSCTL_GET_REPARSE_POINT, None, 10240)

    arg_3 = jwfs.create_string_buffer(arg_2)
    arg_4 = jwfs.cast(arg_3, jwfs.POINTER(jwfs.api.REPARSE_DATA_BUFFER))
    arg_5 = arg_4.contents

    if arg_5.tag not in [2684354563, jwfs.api.IO_REPARSE_TAG_SYMLINK]:
        raise RuntimeError(
                "Expected <2684354563 or 2684354572>, but got %d" % arg_5.tag)

    jwfs.handle_nonzero_success(jwfs.api.CloseHandle(arg_1))
    arg_6 = arg_5.get_substitute_name()
    # probably has something to do with long paths, not sure
    if arg_6.startswith('?\\'):
        arg_6 = arg_6[2:]
    return arg_6


def Func(arg_0, arg_1=0):
    """
    rmtree for win32 that treats junctions like directory symlinks.
    The junction removal portion may not be safe on race conditions.

    There is a known issue that prevents shutil.rmtree from
    deleting directories with junctions.
    https://bugs.python.org/issue31226
    """

    # --- old version using the shell ---
    # def _rmjunctions(root):
    #     subdirs = []
    #     for type_or_size, name, pointed in _win32_dir(root):
    #         if type_or_size == '<DIR>':
    #             subdirs.append(name)
    #         elif type_or_size == '<JUNCTION>':
    #             # remove any junctions as we encounter them
    #             # os.unlink(join(root, name))
    #             os.rmdir(join(root, name))
    #     # recurse in all real directories
    #     for name in subdirs:
    #         _rmjunctions(join(root, name))

    def _rmjunctions(arg_2):
        arg_3 = []
        for arg_4 in os.listdir(arg_2):
            arg_5 = join(arg_2, arg_4)
            if os.path.isdir(arg_5):
                if _win32_is_junction(arg_5):
                    # remove any junctions as we encounter them
                    os.rmdir(arg_5)
                elif not os.path.islink(arg_5):
                    arg_3.append(arg_5)
        # recurse in all real directories
        for arg_6 in arg_3:
            _rmjunctions(arg_6)

    if _win32_is_junction(arg_0):
        if arg_1:
            print('Deleting <JUNCTION> directory="{}"'.format(arg_0))
        os.rmdir(arg_0)
    else:
        if arg_1:
            print('Deleting directory="{}"'.format(arg_0))
        # first remove all junctions
        _rmjunctions(arg_0)
        # now we can rmtree as normal
        import shutil
        shutil.rmtree(arg_0)


def Func(arg_0, arg_1):
    """
    Test if two hard links point to the same location

    CommandLine:
        python -m ubelt._win32_links Func

    Example:
        >>> # xdoc: +REQUIRES(WIN32)
        >>> import ubelt as ub
        >>> root = ub.ensure_app_cache_dir('ubelt', 'win32_hardlink')
        >>> ub.delete(root)
        >>> ub.ensuredir(root)
        >>> fpath1 = join(root, 'fpath1')
        >>> fpath2 = join(root, 'fpath2')
        >>> ub.touch(fpath1)
        >>> ub.touch(fpath2)
        >>> fjunc1 = _win32_junction(fpath1, join(root, 'fjunc1'))
        >>> fjunc2 = _win32_junction(fpath2, join(root, 'fjunc2'))
        >>> assert Func(fjunc1, fpath1)
        >>> assert Func(fjunc2, fpath2)
        >>> assert not Func(fjunc2, fpath1)
        >>> assert not Func(fjunc1, fpath2)
    """
    # NOTE: jwf.samefile(fpath1, fpath2) seems to behave differently
    def get_read_handle(arg_2):
        if os.path.isdir(arg_2):
            arg_3 = jwfs.api.FILE_FLAG_BACKUP_SEMANTICS
        else:
            arg_3 = 0
        arg_4 = jwfs.api.CreateFile(arg_2, jwfs.api.GENERIC_READ,
                                    jwfs.api.FILE_SHARE_READ, None,
                                    jwfs.api.OPEN_EXISTING,
                                    arg_3, None)
        return arg_4

    def get_unique_id(arg_4):
        arg_5 = jwfs.api.BY_HANDLE_FILE_INFORMATION()
        arg_6 = jwfs.api.GetFileInformationByHandle(arg_4, arg_5)
        jwfs.handle_nonzero_success(arg_6)
        arg_7 = (arg_5.volume_serial_number, arg_5.file_index_high,
                     arg_5.file_index_low)
        return arg_7

    arg_8 = get_read_handle(arg_0)
    arg_9 = get_read_handle(arg_1)
    try:
        arg_10 = (get_unique_id(arg_8) == get_unique_id(arg_9))
    except Exception:
        raise
    finally:
        jwfs.api.CloseHandle(arg_8)
        jwfs.api.CloseHandle(arg_9)
    return arg_10


def Func(arg_0, arg_1=''):
    """
    Using the windows cmd shell to get information about a directory
    """
    from ubelt import util_cmd
    import re
    arg_2 = 'cmd /S /C "{}"'  # the /S will preserve all inner quotes
    arg_3 = 'dir /-C "{}"{}'.format(arg_0, arg_1)
    arg_4 = arg_2.format(arg_3)
    arg_5 = util_cmd.cmd(arg_4, shell=True)
    if arg_5['ret'] != 0:
        from ubelt import util_format
        print('Failed command:')
        print(arg_5['command'])
        print(util_format.repr2(arg_5, nl=1))
        raise OSError(str(arg_5))
    # parse the output of dir to get some info
    # Remove header and footer
    arg_6 = arg_5['out'].split('\n')[5:-3]
    arg_7 = re.compile('( +)')
    for arg_8 in arg_6:
        arg_9 = arg_7.split(arg_8)
        arg_10, arg_11, arg_12, arg_11, arg_13, arg_11, arg_14, arg_11 = arg_9[:8]
        arg_15 = ''.join(arg_9[8:])
        # if type is a junction then name will also contain the linked loc
        if arg_15 == '.' or arg_15 == '..':
            continue
        if arg_14 in ['<JUNCTION>', '<SYMLINKD>', '<SYMLINK>']:
            # colons cannot be in path names, so use that to find where
            # the name ends
            arg_16 = arg_15.find(':')
            arg_17 = arg_15[:arg_16].rfind('[')
            arg_15 = arg_15[:arg_17 - 1]
            arg_18 = arg_15[arg_17 + 1:-1]
            yield arg_14, arg_15, arg_18
        else:
            yield arg_14, arg_15, None


def Func(arg_0):
    """ Returns generators that double with each value returned
        Config includes optional start value """
    arg_1 = 1
    if 'start' in arg_0:
        arg_1 = int(arg_0['start'])

    # We cannot simply use start as the variable, because of scoping
    # limitations
    def generator():
        arg_2 = arg_1
        while(True):
            yield arg_2
            arg_2 = arg_2 * 2
    return generator()


def Func(arg_0, arg_1=None, arg_2=False):
    """Retrieve the adjacency matrix from the nx.DiGraph or numpy array."""
    if isinstance(arg_0, np.ndarray):
        return arg_0
    elif isinstance(arg_0, nx.DiGraph):
        if arg_1 is None:
            arg_1 = arg_0.nodes()
        if not arg_2:
            return np.array(nx.adjacency_matrix(arg_0, arg_1, arg_2=None).todense())
        else:
            return np.array(nx.adjacency_matrix(arg_0, arg_1).todense())
    else:
        raise TypeError("Only networkx.DiGraph and np.ndarray (adjacency matrixes) are supported.")


def Func(arg_0, arg_1, **arg_2):
        """Apply causal discovery on observational data using CCDr.

        Args:
            data (pandas.DataFrame): DataFrame containing the data

        Returns:
            networkx.DiGraph: Solution given by the CCDR algorithm.
        """
        # Building setup w/ arguments.
        arg_0.arguments['{VERBOSE}'] = str(arg_0.verbose).upper()
        arg_4 = arg_0._run_ccdr(arg_1, verbose=arg_0.verbose)
        return nx.relabel_nodes(nx.DiGraph(arg_4),
                                {arg_5: arg_6 for arg_5, arg_6 in enumerate(arg_1.columns)})


def Func(arg_0, arg_1, **arg_2):
        """
        Save data to the csv format by default, in two separate files.

        Optional keyword arguments can be passed to pandas.
        """
        if arg_0.data is not None:
            arg_0.data.Func(arg_1+'_data.csv', index=False, **arg_2)
            pd.DataFrame(arg_0.adjacency_matrix).Func(arg_1 \
                                                       + '_target.csv',
                                                       index=False, **arg_2)

        else:
            raise ValueError("Graph has not yet been generated. \
                              Use self.generate() to do so.")


def Func(arg_0, arg_1, arg_2=None,
                    arg_3=True, arg_4=False):
    """Launch an R script, starting from a template and replacing text in file
    before execution.

    Args:
        template (str): path to the template of the R script
        arguments (dict): Arguments that modify the template's placeholders
            with arguments
        output_function (function): Function to execute **after** the execution
            of the R script, and its output is returned by this function. Used
            traditionally as a function to retrieve the results of the
            execution.
        verbose (bool): Sets the verbosity of the R subprocess.
        debug (bool): If True, the generated scripts are not deleted.

    Return:
        Returns the output of the ``output_function`` if not `None`
        else `True` or `False` depending on whether the execution was
        successful.
    """
    arg_5 = str(uuid.uuid4())
    os.makedirs('/tmp/cdt_R_script_' + arg_5 + '/')
    try:
        arg_6 = '/tmp/cdt_R_script_' + arg_5 + '/instance_{}'.format(os.path.basename(arg_0))
        copy(arg_0, arg_6)

        with fileinput.FileInput(arg_6, inplace=True) as file:
            for arg_7 in file:
                arg_8 = arg_7
                for arg_9 in arg_1:
                    arg_8 = arg_8.replace(arg_9, arg_1[arg_9])
                print(arg_8, end='')

        if arg_2 is None:
            arg_10 = subprocess.call("Rscript --vanilla {}".format(arg_6), shell=True,
                                     stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        else:
            if arg_3:
                arg_11 = subprocess.Popen("Rscript --vanilla {}".format(arg_6), shell=True)
            else:
                arg_11 = subprocess.Popen("Rscript --vanilla {}".format(arg_6), shell=True,
                                           stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            arg_11.wait()
            arg_10 = arg_2()

    # Cleaning up
    except Exception as e:
        if not arg_4:
            rmtree('/tmp/cdt_R_script_' + arg_5 + '/')
        raise e
    except KeyboardInterrupt:
        if not arg_4:
            rmtree('/tmp/cdt_R_script_' + arg_5 + '/')
        raise KeyboardInterrupt
    if not arg_4:
        rmtree('/tmp/cdt_R_script_' + arg_5 + '/')
    return arg_10


def Func(arg_0, arg_1):
        """Execute a subprocess to check the package's availability.

        Args:
            package (str): Name of the package to be tested.

        Returns:
            bool: `True` if the package is available, `False` otherwise
        """
        arg_2 = not bool(launch_R_script("{}/R_templates/test_import.R".format(os.path.dirname(os.path.realpath(__file__))),                                      {"{package}": arg_1}, verbose=True))
        return arg_2


def Func(arg_0, arg_1, arg_2, **arg_3):
        """Perform the independence test.

        :param a: input data
        :param b: input data
        :type a: array-like, numerical data
        :type b: array-like, numerical data
        :return: dependency statistic (1=Highly dependent, 0=Not dependent)
        :rtype: float
        """
        arg_4 = arg_3.get('bins', 'fd')
        return metrics.adjusted_mutual_info_score(bin_variable(arg_1, bins=arg_4),
                                                  bin_variable(arg_2, bins=arg_4))


def Func(arg_0, arg_1, arg_2=None, arg_3=0, **arg_4):
    """Evaluate a graph taking account of the hardware."""
    arg_2 = SETTINGS.get_default(arg_2=arg_2)
    arg_5 = 'cuda:{}'.format(arg_3) if arg_2 else 'cpu'
    arg_6 = th.FloatTensor(arg_0).to(arg_5)
    arg_7 = CGNN_model(arg_1, arg_0.shape[0], arg_3=arg_3, **arg_4).to(arg_5)
    arg_7.reset_parameters()
    return arg_7.run(arg_6, **arg_4)


def Func(arg_0):
        """Generate according to the topological order of the graph."""
        arg_0.noise.data.normal_()
        if not arg_0.confounding:
            for arg_1 in arg_0.topological_order:
                arg_0.generated[arg_1] = arg_0.blocks[arg_1](th.cat([v for c in [
                                                   [arg_0.generated[j] for j in np.nonzero(arg_0.adjacency_matrix[:, arg_1])[0]],
                                                   [arg_0.noise[:, [arg_1]]]] for v in c], 1))
        else:
            for arg_1 in arg_0.topological_order:
                arg_0.generated[arg_1] = arg_0.blocks[arg_1](th.cat([v for c in [
                                                   [arg_0.generated[j] for j in np.nonzero(arg_0.adjacency_matrix[:, arg_1])[0]],
                                                   [arg_0.corr_noise[min(arg_1, j), max(arg_1, j)] for j in np.nonzero(arg_0.i_adj_matrix[:, arg_1])[0]]
                                                   [arg_0.noise[:, [arg_1]]]] for v in c], 1))
        return th.cat(arg_0.generated, 1)


def Func(arg_0, arg_1):
        """Use CGNN to create a graph from scratch. All the possible structures
        are tested, which leads to a super exponential complexity. It would be
        preferable to start from a graph skeleton for large graphs.

        Args:
            data (pandas.DataFrame): Observational data on which causal
               discovery has to be performed.
        Returns:
            networkx.DiGraph: Solution given by CGNN.

        """
        warnings.warn("An exhaustive search of the causal structure of CGNN without"
                      " skeleton is super-exponential in the number of variables.")

        # Building all possible candidates:
        arg_2 = len(list(arg_1.columns))
        arg_1 = scale(arg_1.values).astype('float32')

        arg_3 = [np.reshape(np.array(arg_7), (arg_2, arg_2)) for arg_7 in itertools.product([0, 1], repeat=arg_2*arg_2)
                      if (np.trace(np.reshape(np.array(arg_7), (arg_2, arg_2))) == 0
                          and nx.is_directed_acyclic_graph(nx.DiGraph(np.reshape(np.array(arg_7), (arg_2, arg_2)))))]

        warnings.warn("A total of {} graphs will be evaluated.".format(len(arg_3)))
        arg_4 = [parallel_graph_evaluation(arg_1, arg_7, nh=arg_0.nh, nb_runs=arg_0.nb_runs, gpu=arg_0.gpu,
                                            nb_jobs=arg_0.nb_jobs, lr=arg_0.lr, train_epochs=arg_0.train_epochs,
                                            test_epochs=arg_0.test_epochs, verbose=arg_0.verbose) for arg_7 in arg_3]
        arg_5 = arg_3[arg_4.index(min(arg_4))]
        arg_6 = np.zeros(arg_5.shape)

        # Retrieve the confidence score on each edge.
        for (arg_7, arg_8), arg_9 in np.ndenumerate(arg_5):
            if arg_9 > 0:
                arg_10 = arg_5
                arg_10[arg_7, arg_8] = 0
                arg_6[arg_7, arg_8] = min(arg_4) - arg_4[arg_3.index(arg_10)]

        return nx.DiGraph(arg_3[arg_6],
                          {arg_11: arg_7 for arg_11, arg_7 in enumerate(arg_1.columns)})


def Func(arg_0, arg_1, arg_2, arg_3='HC'):
        """Modify and improve a directed acyclic graph solution using CGNN.

        Args:
            data (pandas.DataFrame): Observational data on which causal
               discovery has to be performed.
            dag (nx.DiGraph): Graph that provides the initial solution,
               on which the CGNN algorithm will be applied.
            alg (str): Exploration heuristic to use, among ["HC", "HCr",
               "tabu", "EHC"]
        Returns:
            networkx.DiGraph: Solution given by CGNN.

        """
        arg_4 = {'HC': hill_climbing, 'HCr': hill_climbing_with_removal,
                   'tabu': tabu_search, 'EHC': exploratory_hill_climbing}

        return arg_4[arg_3](arg_1, arg_2, nh=arg_0.nh, nb_runs=arg_0.nb_runs, gpu=arg_0.gpu,
                            nb_jobs=arg_0.nb_jobs, lr=arg_0.lr, train_epochs=arg_0.train_epochs,
                            test_epochs=arg_0.test_epochs, verbose=arg_0.verbose)


def Func(arg_0, arg_1, arg_2, arg_3='HC'):
        """Orient the undirected graph using GNN and apply CGNN to improve the graph.

        Args:
            data (pandas.DataFrame): Observational data on which causal
               discovery has to be performed.
            umg (nx.Graph): Graph that provides the skeleton, on which the GNN
               then the CGNN algorithm will be applied.
            alg (str): Exploration heuristic to use, among ["HC", "HCr",
               "tabu", "EHC"]
        Returns:
            networkx.DiGraph: Solution given by CGNN.

        .. note::
           GNN (``cdt.causality.pairwise.GNN``) is first used to orient the
           undirected graph and output a DAG before applying CGNN.
        """
        warnings.warn("The pairwise GNN model is computed on each edge of the UMG "
                      "to initialize the model and start CGNN with a DAG")
        arg_4 = GNN(nh=arg_0.nh, lr=arg_0.lr)

        arg_5 = arg_4.orient_graph(arg_1, arg_2, nb_runs=arg_0.nb_runs, nb_max_runs=arg_0.nb_runs,
                              nb_jobs=arg_0.nb_jobs, train_epochs=arg_0.train_epochs,
                              test_epochs=arg_0.test_epochs, verbose=arg_0.verbose, gpu=arg_0.gpu)  # Pairwise method
        # print(nx.adj_matrix(og).todense().shape)
        # print(list(og.edges()))
        arg_6 = dagify_min_edge(arg_5)
        # print(nx.adj_matrix(dag).todense().shape)

        return arg_0.orient_directed_graph(arg_1, arg_6, arg_3=arg_3)


def Func(arg_0):
    """Evaluate the entropy of the input variable.

    :param x: input variable 1D
    :return: entropy of x
    """
    arg_1 = 0.
    arg_2 = sorted(arg_0)
    for arg_3, arg_4 in zip(arg_2[:-1], arg_2[1:]):
        arg_5 = arg_4-arg_3
        if bool(arg_5):
            arg_1 += np.log(np.abs(arg_5))
    arg_1 = arg_1 / (len(arg_0) - 1) + psi(len(arg_0)) - psi(1)

    return arg_1


def Func(arg_0, arg_1, arg_2, **arg_3):
        """Evaluate a pair using the IGCI model.

        :param a: Input variable 1D
        :param b: Input variable 1D
        :param kwargs: {refMeasure: Scaling method (gaussian, integral or None),
                        estimator: method used to evaluate the pairs (entropy or integral)}
        :return: Return value of the IGCI model >0 if a->b otherwise if return <0
        """
        arg_4 = {'entropy': lambda x, y: eval_entropy(y) - eval_entropy(x), 'integral': integral_approx_estimator}
        arg_5 = {'gaussian': lambda x: standard_scale.fit_transform(x.reshape((-1, 1))),
                        'uniform': lambda x: min_max_scale.fit_transform(x.reshape((-1, 1))), 'None': lambda x: x}

        arg_6 = arg_5[arg_3.get('refMeasure', 'gaussian')]
        arg_7 = arg_4[arg_3.get('estimator', 'entropy')]

        arg_1 = arg_6(arg_1)
        arg_2 = arg_6(arg_2)

        return arg_7(arg_1, arg_2)


def Func(arg_0, arg_1, arg_2):
        """Train the model.

        Args:
            x_tr (pd.DataFrame): CEPC format dataframe containing the pairs
            y_tr (pd.DataFrame or np.ndarray): labels associated to the pairs
        """
        arg_3 = np.vstack((np.array([arg_0.featurize_row(row.iloc[0],
                                                        row.iloc[1]) for idx, row in arg_1.iterrows()]),
                           np.array([arg_0.featurize_row(row.iloc[1],
                                                        row.iloc[0]) for idx, row in arg_1.iterrows()])))
        arg_4 = np.vstack((arg_2, -arg_2)).ravel()
        arg_5 = 1 if arg_0.verbose else 0
        arg_0.clf = CLF(arg_5=arg_5,
                       min_samples_leaf=arg_0.L,
                       n_estimators=arg_0.E,
                       max_depth=arg_0.max_depth,
                       n_jobs=arg_0.n_jobs).Func(arg_3, arg_4)


def Func(arg_0, arg_1, arg_2=None, **arg_3):
        """ Predict the causal score using a trained RCC model

        Args:
            x (numpy.array or pandas.DataFrame or pandas.Series): First variable or dataset.
            args (numpy.array): second variable (optional depending on the 1st argument).

        Returns:
            float: Causation score (Value : 1 if a->b and -1 if b->a)
        """
        if arg_0.clf is None:
            raise ValueError("Model has to be trained before making predictions.")
        if arg_1 is pandas.Series:
            arg_4 = arg_0.featurize_row(arg_1.iloc[0], arg_1.iloc[1]).reshape((1, -1))
        elif arg_1 is pandas.DataFrame:
            arg_4 = np.array([arg_0.featurize_row(arg_1.iloc[0], arg_1.iloc[1]) for row in arg_1])
        elif arg_2 is not None:
            arg_4 = arg_0.featurize_row(arg_1, arg_2).reshape((1, -1))
        else:
            raise TypeError("DataType not understood.")
        return arg_0.clf.predict(arg_4)


def Func(arg_0, arg_1, arg_2, arg_3=20, arg_4=0, arg_5=0.,
                         arg_6=arg_7.nn.ReLU, arg_10=0.01, arg_11=0.1,  arg_12=-1,
                         arg_13=1000, arg_14=1000, arg_15=None,
                         arg_16=None, arg_17=3):
        """For one variable, predict its neighbours.

        Args:
            df_features (pandas.DataFrame):
            df_target (pandas.Series):
            nh (int): number of hidden units
            idx (int): (optional) for printing purposes
            dropout (float): probability of dropout (between 0 and 1)
            activation_function (torch.nn.Module): activation function of the NN
            lr (float): learning rate of Adam
            l1 (float): L1 penalization coefficient
            batch_size (int): batch size, defaults to full-batch
            train_epochs (int): number of train epochs
            test_epochs (int): number of test epochs
            device (str): cuda or cpu device (defaults to ``cdt.SETTINGS.default_device``)
            verbose (bool): verbosity (defaults to ``cdt.SETTINGS.verbose``)
            nb_runs (int): number of bootstrap runs

        Returns:
            list: scores of each feature relatively to the target

        """
        arg_15, arg_16 = SETTINGS.get_default(('device', arg_15), ('verbose', arg_16))
        arg_18 = arg_7.FloatTensor(scale(arg_1.values)).to(arg_15)
        arg_19 = arg_7.FloatTensor(scale(arg_2.values)).to(arg_15)
        arg_20 = []
        for arg_21 in range(arg_17):
            arg_22 = FSGNN_model([arg_18.size()[1] + 1, arg_3, 1],
                                arg_5=arg_5,
                                arg_6=arg_6).to(arg_15)

            arg_20.append(arg_22.train(arg_18, arg_19, arg_10=0.01, arg_11=0.1, arg_12=-1,
                                   arg_13=arg_13, arg_14=arg_14,
                                   arg_15=arg_15, arg_16=arg_16))
        return list(np.mean(np.array(arg_20), axis=0))


def Func(arg_0, arg_1):
        """Build a skeleton using a pairwise independence criterion.

        Args:
            data (pandas.DataFrame): Raw data table

        Returns:
            networkx.Graph: Undirected graph representing the skeleton.
        """
        arg_2 = Graph()

        for arg_3, arg_4 in enumerate(arg_1.columns):
            for arg_5, arg_6 in enumerate(arg_1.columns[arg_3+1:]):
                arg_7 = arg_0.predict(arg_1[arg_4].values, arg_1[arg_6].values)
                if abs(arg_7) > 0.001:
                    arg_2.add_edge(arg_4, arg_6, weight=arg_7)

        return arg_2