Source code for mlprodict.tools.code_helper

"""
A couple of tools unrelated to what the package does.


:githublink:`%|py|5`
"""
import pickle
import keyword
import re
import types
import numpy


[docs]def change_style(name): """ Switches from *AaBb* into *aa_bb*. :param name: name to convert :return: converted name :githublink:`%|py|18` """ s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) s2 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() return s2 if not keyword.iskeyword(s2) else s2 + "_"
[docs]def numpy_min_max(x, fct, minmax=False): """ Returns the minimum of an array. Deals with text as well. :githublink:`%|py|28` """ try: if hasattr(x, 'todense'): x = x.todense() if (x.dtype.kind[0] not in 'Uc' or x.dtype in {numpy.uint8}): return fct(x) try: # pragma: no cover x = x.ravel() except AttributeError: # pragma: no cover pass keep = list(filter(lambda s: isinstance(s, str), x)) if len(keep) == 0: # pragma: no cover return numpy.nan keep.sort(reverse=minmax) val = keep[0] if len(val) > 10: # pragma: no cover val = val[:10] + '...' return "%r" % val except (ValueError, TypeError, AttributeError): return '?'
[docs]def numpy_min(x): """ Returns the maximum of an array. Deals with text as well. :githublink:`%|py|55` """ return numpy_min_max(x, lambda x: x.min(), minmax=False)
[docs]def numpy_max(x): """ Returns the maximum of an array. Deals with text as well. :githublink:`%|py|63` """ return numpy_min_max(x, lambda x: x.max(), minmax=True)
[docs]def debug_dump(clname, obj, folder=None, ops=None): """ Dumps an object for debug purpose. :param clname: class name :param obj: object :param folder: folder :param ops: operator to dump :return: filename :githublink:`%|py|76` """ def debug_print_(obj, prefix=''): name = clname if isinstance(obj, dict): if 'in' in obj and 'out' in obj: nan_in = any(map(lambda o: any(map(numpy.isnan, o.ravel())), obj['in'])) nan_out = any(map(lambda o: any(map(numpy.isnan, o.ravel())), obj['out'])) if not nan_in and nan_out: print("NAN-notin-out ", name, prefix, {k: getattr(ops, k, '?') for k in getattr(ops, 'atts', {})}) return True return False # pragma: no cover for k, v in obj.items(): # pragma: no cover debug_print_([v], k) return None # pragma: no cover if isinstance(obj, list): for i, o in enumerate(obj): if o is None: continue if any(map(numpy.isnan, o.ravel())): print("NAN", prefix, i, name, o.shape) return None raise NotImplementedError( # pragma: no cover "Unable to debug object of type {}.".format(type(obj))) dump = debug_print_(obj) if dump: name = 'cpu-{}-{}-{}.pkl'.format( clname, id(obj), id(ops)) if folder is not None: name = "/".join([folder, name]) with open(name, 'wb') as f: pickle.dump(obj, f) return name return None
[docs]def debug_print(k, obj, printed): """ Displays informations on an object. :param k: name :param obj: object :param printed: memorizes already printed object :githublink:`%|py|122` """ if k not in printed: printed[k] = obj if hasattr(obj, 'shape'): print("-='{}' shape={} dtype={} min={} max={}{}".format( k, obj.shape, obj.dtype, numpy_min(obj), numpy_max(obj), ' (sparse)' if 'coo_matrix' in str(type(obj)) else '')) elif (isinstance(obj, list) and len(obj) > 0 and not isinstance(obj[0], dict)): # pragma: no cover print("-='{}' list len={} min={} max={}".format( k, len(obj), min(obj), max(obj))) else: # pragma: no cover print("-='{}' type={}".format(k, type(obj)))
[docs]def make_callable(fct, obj, code, gl, debug): """ Creates a callable function able to cope with default values as the combination of functions *compile* and *exec* does not seem able to take them into account. :param fct: function name :param obj: output of function *compile* :param code: code including the signature :param gl: context (local and global) :param debug: add debug function :return: callable functions :githublink:`%|py|151` """ cst = "def " + fct + "(" sig = None for line in code.split('\n'): if line.startswith(cst): sig = line break if sig is None: # pragma: no cover raise ValueError( "Unable to find function '{}' in\n{}".format(fct, code)) reg = re.compile( "([a-z][A-Za-z_0-9]*)=((None)|(False)|(True)|([0-9.e+-]+))") fall = reg.findall(sig) defs = [] for name_value in fall: name = name_value[0] value = name_value[1] if value == 'None': defs.append((name, None)) continue if value == 'True': defs.append((name, True)) continue if value == 'False': defs.append((name, False)) continue f = float(value) if int(f) == f: f = int(f) defs.append((name, f)) # debug if debug: gl = gl.copy() gl['debug_print'] = debug_print gl['print'] = print # specific if "value=array([0.], dtype=float32)" in sig: defs.append(('value', numpy.array([0.], dtype=numpy.float32))) res = types.FunctionType(obj, gl, fct, tuple(_[1] for _ in defs)) if res.__defaults__ != tuple(_[1] for _ in defs): # pylint: disable=E1101 # See https://docs.python.org/3/library/inspect.html # See https://stackoverflow.com/questions/11291242/python-dynamically-create-function-at-runtime lines = [str(sig)] # pragma: no cover for name in ['co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']: # pragma: no cover v = getattr(res.__code__, name, None) # pylint: disable=E1101 if v is not None: lines.append('%s=%r' % (name, v)) raise RuntimeError( # pragma: no cover "Defaults values of function '{}' (defaults={}) are missing.\nDefault: " "{}\n{}\n----\n{}".format( fct, res.__defaults__, defs, "\n".join(lines), code)) # pylint: disable=E1101 return res