<commit_before># -*- coding: utf-8 -*-
"""Generic utility methods not part of main API.
"""

from __future__ import absolute_import

from collections import Iterable
from functools import wraps
import inspect
import re
import warnings

import pydash as pyd
from ._compat import iteritems


class _NoValue(object):
    """Represents an unset value. Used to differeniate between an explicit
    ``None`` and an unset value.
    """
    pass


#: Singleton object that differeniates between an explicit ``None`` value and
#: an unset value.
NoValue = _NoValue()


def call_callback(callback, *args):
    """Inspect argspec of `callback` function and only pass the supported
    arguments when calling it.
    """
    maxargs = len(args)
    argcount = get_argcount(callback, maxargs)
    argstop = min([maxargs, argcount])

    return callback(*args[:argstop])


def get_argcount(callback, maxargs):
    """Return argument count of callback function."""
    if hasattr(callback, '_argcount'):
        # Optimization feature where argcount of callback is known and properly
        # set by initator.
        return callback._argcount

    argspec = None

    try:
        argspec = inspect.getargspec(callback)
    except TypeError:
        call = getattr(callback, '__call__', None)
        if call:
            try:
                argspec = inspect.getargspec(call)
            except TypeError:  # pragma: no cover
                pass
    finally:
        if isinstance(callback, type):
            # Only pass single argument to type callbacks. This is for things
            # like int(), float(), str(), etc.
            argcount = 1
        elif pyd.is_builtin(callback):
            argcount = guess_builtin_argcount(callback) or maxargs
        elif argspec and argspec.varargs:
            # Callback supports variable arguments.
            argcount = maxargs
        elif argspec:
            # Use inspected arg count.
            argcount = len(argspec.args)
        else:  # pragma: no cover
            argcount = maxargs

    return argcount


def guess_builtin_argcount(obj):
    """Return guess as to how many arguments can be supplied to a builtin
    function or method. This relies on the fact that the docstring for builtins
    follows a predictable pattern.
    """
    try:
        # Try to split the arguments between the first set of "(...)" which
        # would correspond to argument list of the function.
        count = len((re.search(r'\((.+)\)',
                               obj.__doc__.split('\n')[0])
                     .groups()[0]
                     .split(',')))
    except Exception:  # pragma: no cover pylint: disable=broad-except
        count = None

    return count


def itercallback(obj, callback=None, reverse=False):
    """Return iterative callback based on collection type."""
    cbk = pyd.iteratee(callback)
    items = iterator(obj)

    if reverse:
        items = reversed(tuple(items))

    for key, item in items:
        yield (call_callback(cbk, item, key, obj), item, key, obj)


def iterator(obj):
    """Return iterative based on object type."""
    if isinstance(obj, dict):
        return iteritems(obj)
    elif hasattr(obj, 'iteritems'):<commit_msg>Precompute argcount of callback before calling call_callback().<commit_after># -*- coding: utf-8 -*-
"""Generic utility methods not part of main API.
"""

from __future__ import absolute_import

from collections import Iterable
from functools import wraps
import inspect
import re
import warnings

import pydash as pyd
from ._compat import iteritems


class _NoValue(object):
    """Represents an unset value. Used to differeniate between an explicit
    ``None`` and an unset value.
    """
    pass


#: Singleton object that differeniates between an explicit ``None`` value and
#: an unset value.
NoValue = _NoValue()


def call_callback(callback, *args, **kargs):
    """Inspect argspec of `callback` function and only pass the supported
    arguments when calling it.
    """
    maxargs = len(args)
    argcount = (kargs['argcount'] if 'argcount' in kargs
                else get_argcount(callback, maxargs))
    argstop = min([maxargs, argcount])

    return callback(*args[:argstop])


def get_argcount(callback, maxargs):
    """Return argument count of callback function."""
    if hasattr(callback, '_argcount'):
        # Optimization feature where argcount of callback is known and properly
        # set by initator.
        return callback._argcount

    argspec = None

    try:
        argspec = inspect.getargspec(callback)
    except TypeError:
        call = getattr(callback, '__call__', None)
        if call:
            try:
                argspec = inspect.getargspec(call)
            except TypeError:  # pragma: no cover
                pass
    finally:
        if isinstance(callback, type):
            # Only pass single argument to type callbacks. This is for things
            # like int(), float(), str(), etc.
            argcount = 1
        elif pyd.is_builtin(callback):
            argcount = guess_builtin_argcount(callback) or maxargs
        elif argspec and argspec.varargs:
            # Callback supports variable arguments.
            argcount = maxargs
        elif argspec:
            # Use inspected arg count.
            argcount = len(argspec.args)
        else:  # pragma: no cover
            argcount = maxargs

    return argcount


def guess_builtin_argcount(obj):
    """Return guess as to how many arguments can be supplied to a builtin
    function or method. This relies on the fact that the docstring for builtins
    follows a predictable pattern.
    """
    try:
        # Try to split the arguments between the first set of "(...)" which
        # would correspond to argument list of the function.
        count = len((re.search(r'\((.+)\)',
                               obj.__doc__.split('\n')[0])
                     .groups()[0]
                     .split(',')))
    except Exception:  # pragma: no cover pylint: disable=broad-except
        count = None

    return count


def itercallback(obj, callback=None, reverse=False):
    """Return iterative callback based on collection type."""
    cbk = pyd.iteratee(callback)
    items = iterator(obj)

    if reverse:
        items = reversed(tuple(items))

    # Precompute argcount to avoid repeated calculations during callback loop.
    argcount = get_argcount(cbk, maxargs=3)

    for key, item in items:
        yield (call_callback(cbk, item, key, obj, argcount=argcount),
               item,
               key,
               obj)


def iterator(obj):
    """Return iterative based on object type."""
    if isinstance(obj, dict):
        return iteritems(obj)
    elif hasattr(obj, 'iteritems'):
{'fraction_non_alphanumeric': 0.09072073405357407, 'fraction_numerical': 0.0013319520497262099, 'mean_word_length': 2.428716387620497, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1601723', 'n_tokens_mistral': 1831, 'n_tokens_neox': 1786, 'n_words': 657}