Return a :class:`ParallelHelper` object that can be used for easy
    parallelization of computations. *parallel_arg* is an object that lets the
    caller easily specify the kind of parallelization they are interested in.
    Allowed values are:

    False
      Serial processing only.
    True
      Parallel processing using all available cores.
    1
      Equivalent to ``False``.
    Other positive integer
      Parallel processing using the specified number of cores.
    x, 0 < x < 1
      Parallel processing using about ``x * N`` cores, where N is the total
      number of cores in the system. Note that the meanings of ``0.99`` and ``1``
      as arguments are very different.
    :class:`ParallelHelper` instance
      Returns the instance.

    The ``**kwargs`` are passed on to the appropriate :class:`ParallelHelper`
    constructor, if the caller wants to do something tricky.

    Expected usage is::

        from pwkit.parallel import make_parallel_helper

        def sub_operation(arg):
            ... do some computation ...
            return result

        def my_parallelizable_function(arg1, arg2, parallel=True):
            phelp = make_parallel_helper(parallel)

            with phelp.get_map() as map:
                op_results = map(sub_operation, args)

            ... reduce "op_results" in some way ...
            return final_result

    This means that ``my_parallelizable_function`` doesn't have to worry about
    all of the various fancy things the caller might want to do in terms of
    special parallel magic.

    Note that ``sub_operation`` above must be defined in a stand-alone fashion
    because of the way Python's :mod:`multiprocessing` module works. This can
    be worked around somewhat with the special
    :meth:`ParallelHelper.get_ppmap` variant. This returns a
    "partially-Pickling" map operation --- with a different calling signature
    --- that allows un-Pickle-able values to be used. See the documentation
    for :func:`serial_ppmap` for usage information.

def make_parallel_helper(parallel_arg, **kwargs):
    """Return a :class:`ParallelHelper` object that can be used for easy
    parallelization of computations. *parallel_arg* is an object that lets the
    caller easily specify the kind of parallelization they are interested in.
    Allowed values are:

    False
      Serial processing only.
    True
      Parallel processing using all available cores.
    1
      Equivalent to ``False``.
    Other positive integer
      Parallel processing using the specified number of cores.
    x, 0 < x < 1
      Parallel processing using about ``x * N`` cores, where N is the total
      number of cores in the system. Note that the meanings of ``0.99`` and ``1``
      as arguments are very different.
    :class:`ParallelHelper` instance
      Returns the instance.

    The ``**kwargs`` are passed on to the appropriate :class:`ParallelHelper`
    constructor, if the caller wants to do something tricky.

    Expected usage is::

        from pwkit.parallel import make_parallel_helper

        def sub_operation(arg):
            ... do some computation ...
            return result

        def my_parallelizable_function(arg1, arg2, parallel=True):
            phelp = make_parallel_helper(parallel)

            with phelp.get_map() as map:
                op_results = map(sub_operation, args)

            ... reduce "op_results" in some way ...
            return final_result

    This means that ``my_parallelizable_function`` doesn't have to worry about
    all of the various fancy things the caller might want to do in terms of
    special parallel magic.

    Note that ``sub_operation`` above must be defined in a stand-alone fashion
    because of the way Python's :mod:`multiprocessing` module works. This can
    be worked around somewhat with the special
    :meth:`ParallelHelper.get_ppmap` variant. This returns a
    "partially-Pickling" map operation --- with a different calling signature
    --- that allows un-Pickle-able values to be used. See the documentation
    for :func:`serial_ppmap` for usage information.

    """
    if parallel_arg is True: # note: (True == 1) is True
        return MultiprocessingPoolHelper(**kwargs)

    if parallel_arg is False or parallel_arg == 1:
        return SerialHelper(**kwargs)

    if parallel_arg > 0 and parallel_arg < 1:
        from multiprocessing import cpu_count
        n = int(round(parallel_arg * cpu_count()))
        return MultiprocessingPoolHelper(processes=n, **kwargs)

    if isinstance(parallel_arg, ParallelHelper):
        return parallel_arg

    if isinstance(parallel_arg, six.integer_types):
        return MultiprocessingPoolHelper(processes=parallel_arg, **kwargs)

    raise ValueError('don\'t understand make_parallel_helper() argument %r'
                     % parallel_arg)