PageRenderTime 24ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/sklearn/externals/joblib/func_inspect.py

https://gitlab.com/github-cloud-corporation/scikit-learn
Python | 355 lines | 292 code | 20 blank | 43 comment | 14 complexity | eb5fba6aa21d3417d5d6898afcabb0c0 MD5 | raw file
  1. """
  2. My own variation on function-specific inspect-like features.
  3. """
  4. # Author: Gael Varoquaux <gael dot varoquaux at normalesup dot org>
  5. # Copyright (c) 2009 Gael Varoquaux
  6. # License: BSD Style, 3 clauses.
  7. from itertools import islice
  8. import inspect
  9. import warnings
  10. import re
  11. import os
  12. from ._compat import _basestring
  13. from .logger import pformat
  14. from ._memory_helpers import open_py_source
  15. from ._compat import PY3_OR_LATER
  16. def get_func_code(func):
  17. """ Attempts to retrieve a reliable function code hash.
  18. The reason we don't use inspect.getsource is that it caches the
  19. source, whereas we want this to be modified on the fly when the
  20. function is modified.
  21. Returns
  22. -------
  23. func_code: string
  24. The function code
  25. source_file: string
  26. The path to the file in which the function is defined.
  27. first_line: int
  28. The first line of the code in the source file.
  29. Notes
  30. ------
  31. This function does a bit more magic than inspect, and is thus
  32. more robust.
  33. """
  34. source_file = None
  35. try:
  36. code = func.__code__
  37. source_file = code.co_filename
  38. if not os.path.exists(source_file):
  39. # Use inspect for lambda functions and functions defined in an
  40. # interactive shell, or in doctests
  41. source_code = ''.join(inspect.getsourcelines(func)[0])
  42. line_no = 1
  43. if source_file.startswith('<doctest '):
  44. source_file, line_no = re.match(
  45. '\<doctest (.*\.rst)\[(.*)\]\>',
  46. source_file).groups()
  47. line_no = int(line_no)
  48. source_file = '<doctest %s>' % source_file
  49. return source_code, source_file, line_no
  50. # Try to retrieve the source code.
  51. with open_py_source(source_file) as source_file_obj:
  52. first_line = code.co_firstlineno
  53. # All the lines after the function definition:
  54. source_lines = list(islice(source_file_obj, first_line - 1, None))
  55. return ''.join(inspect.getblock(source_lines)), source_file, first_line
  56. except:
  57. # If the source code fails, we use the hash. This is fragile and
  58. # might change from one session to another.
  59. if hasattr(func, '__code__'):
  60. # Python 3.X
  61. return str(func.__code__.__hash__()), source_file, -1
  62. else:
  63. # Weird objects like numpy ufunc don't have __code__
  64. # This is fragile, as quite often the id of the object is
  65. # in the repr, so it might not persist across sessions,
  66. # however it will work for ufuncs.
  67. return repr(func), source_file, -1
  68. def _clean_win_chars(string):
  69. """Windows cannot encode some characters in filename."""
  70. import urllib
  71. if hasattr(urllib, 'quote'):
  72. quote = urllib.quote
  73. else:
  74. # In Python 3, quote is elsewhere
  75. import urllib.parse
  76. quote = urllib.parse.quote
  77. for char in ('<', '>', '!', ':', '\\'):
  78. string = string.replace(char, quote(char))
  79. return string
  80. def get_func_name(func, resolv_alias=True, win_characters=True):
  81. """ Return the function import path (as a list of module names), and
  82. a name for the function.
  83. Parameters
  84. ----------
  85. func: callable
  86. The func to inspect
  87. resolv_alias: boolean, optional
  88. If true, possible local aliases are indicated.
  89. win_characters: boolean, optional
  90. If true, substitute special characters using urllib.quote
  91. This is useful in Windows, as it cannot encode some filenames
  92. """
  93. if hasattr(func, '__module__'):
  94. module = func.__module__
  95. else:
  96. try:
  97. module = inspect.getmodule(func)
  98. except TypeError:
  99. if hasattr(func, '__class__'):
  100. module = func.__class__.__module__
  101. else:
  102. module = 'unknown'
  103. if module is None:
  104. # Happens in doctests, eg
  105. module = ''
  106. if module == '__main__':
  107. try:
  108. filename = os.path.abspath(inspect.getsourcefile(func))
  109. except:
  110. filename = None
  111. if filename is not None:
  112. # mangling of full path to filename
  113. parts = filename.split(os.sep)
  114. if parts[-1].startswith('<ipython-input'):
  115. # function is defined in an IPython session. The filename
  116. # will change with every new kernel instance. This hack
  117. # always returns the same filename
  118. parts[-1] = '__ipython-input__'
  119. filename = '-'.join(parts)
  120. if filename.endswith('.py'):
  121. filename = filename[:-3]
  122. module = module + '-' + filename
  123. module = module.split('.')
  124. if hasattr(func, 'func_name'):
  125. name = func.func_name
  126. elif hasattr(func, '__name__'):
  127. name = func.__name__
  128. else:
  129. name = 'unknown'
  130. # Hack to detect functions not defined at the module-level
  131. if resolv_alias:
  132. # TODO: Maybe add a warning here?
  133. if hasattr(func, 'func_globals') and name in func.func_globals:
  134. if not func.func_globals[name] is func:
  135. name = '%s-alias' % name
  136. if inspect.ismethod(func):
  137. # We need to add the name of the class
  138. if hasattr(func, 'im_class'):
  139. klass = func.im_class
  140. module.append(klass.__name__)
  141. if os.name == 'nt' and win_characters:
  142. # Stupid windows can't encode certain characters in filenames
  143. name = _clean_win_chars(name)
  144. module = [_clean_win_chars(s) for s in module]
  145. return module, name
  146. def getfullargspec(func):
  147. """Compatibility function to provide inspect.getfullargspec in Python 2
  148. This should be rewritten using a backport of Python 3 signature
  149. once we drop support for Python 2.6. We went for a simpler
  150. approach at the time of writing because signature uses OrderedDict
  151. which is not available in Python 2.6.
  152. """
  153. try:
  154. return inspect.getfullargspec(func)
  155. except AttributeError:
  156. arg_spec = inspect.getargspec(func)
  157. import collections
  158. tuple_fields = ('args varargs varkw defaults kwonlyargs '
  159. 'kwonlydefaults annotations')
  160. tuple_type = collections.namedtuple('FullArgSpec', tuple_fields)
  161. return tuple_type(args=arg_spec.args,
  162. varargs=arg_spec.varargs,
  163. varkw=arg_spec.keywords,
  164. defaults=arg_spec.defaults,
  165. kwonlyargs=[],
  166. kwonlydefaults=None,
  167. annotations={})
  168. def _signature_str(function_name, arg_spec):
  169. """Helper function to output a function signature"""
  170. # inspect.formatargspec can not deal with the same
  171. # number of arguments in python 2 and 3
  172. arg_spec_for_format = arg_spec[:7 if PY3_OR_LATER else 4]
  173. arg_spec_str = inspect.formatargspec(*arg_spec_for_format)
  174. return '{0}{1}'.format(function_name, arg_spec_str)
  175. def _function_called_str(function_name, args, kwargs):
  176. """Helper function to output a function call"""
  177. template_str = '{0}({1}, {2})'
  178. args_str = repr(args)[1:-1]
  179. kwargs_str = ', '.join('%s=%s' % (k, v)
  180. for k, v in kwargs.items())
  181. return template_str.format(function_name, args_str,
  182. kwargs_str)
  183. def filter_args(func, ignore_lst, args=(), kwargs=dict()):
  184. """ Filters the given args and kwargs using a list of arguments to
  185. ignore, and a function specification.
  186. Parameters
  187. ----------
  188. func: callable
  189. Function giving the argument specification
  190. ignore_lst: list of strings
  191. List of arguments to ignore (either a name of an argument
  192. in the function spec, or '*', or '**')
  193. *args: list
  194. Positional arguments passed to the function.
  195. **kwargs: dict
  196. Keyword arguments passed to the function
  197. Returns
  198. -------
  199. filtered_args: list
  200. List of filtered positional and keyword arguments.
  201. """
  202. args = list(args)
  203. if isinstance(ignore_lst, _basestring):
  204. # Catch a common mistake
  205. raise ValueError(
  206. 'ignore_lst must be a list of parameters to ignore '
  207. '%s (type %s) was given' % (ignore_lst, type(ignore_lst)))
  208. # Special case for functools.partial objects
  209. if (not inspect.ismethod(func) and not inspect.isfunction(func)):
  210. if ignore_lst:
  211. warnings.warn('Cannot inspect object %s, ignore list will '
  212. 'not work.' % func, stacklevel=2)
  213. return {'*': args, '**': kwargs}
  214. arg_spec = getfullargspec(func)
  215. arg_names = arg_spec.args + arg_spec.kwonlyargs
  216. arg_defaults = arg_spec.defaults or ()
  217. arg_defaults = arg_defaults + tuple(arg_spec.kwonlydefaults[k]
  218. for k in arg_spec.kwonlyargs)
  219. arg_varargs = arg_spec.varargs
  220. arg_varkw = arg_spec.varkw
  221. if inspect.ismethod(func):
  222. # First argument is 'self', it has been removed by Python
  223. # we need to add it back:
  224. args = [func.__self__, ] + args
  225. # XXX: Maybe I need an inspect.isbuiltin to detect C-level methods, such
  226. # as on ndarrays.
  227. _, name = get_func_name(func, resolv_alias=False)
  228. arg_dict = dict()
  229. arg_position = -1
  230. for arg_position, arg_name in enumerate(arg_names):
  231. if arg_position < len(args):
  232. # Positional argument or keyword argument given as positional
  233. if arg_name not in arg_spec.kwonlyargs:
  234. arg_dict[arg_name] = args[arg_position]
  235. else:
  236. raise ValueError(
  237. "Keyword-only parameter '%s' was passed as "
  238. 'positional parameter for %s:\n'
  239. ' %s was called.'
  240. % (arg_name,
  241. _signature_str(name, arg_spec),
  242. _function_called_str(name, args, kwargs))
  243. )
  244. else:
  245. position = arg_position - len(arg_names)
  246. if arg_name in kwargs:
  247. arg_dict[arg_name] = kwargs.pop(arg_name)
  248. else:
  249. try:
  250. arg_dict[arg_name] = arg_defaults[position]
  251. except (IndexError, KeyError):
  252. # Missing argument
  253. raise ValueError(
  254. 'Wrong number of arguments for %s:\n'
  255. ' %s was called.'
  256. % (_signature_str(name, arg_spec),
  257. _function_called_str(name, args, kwargs))
  258. )
  259. varkwargs = dict()
  260. for arg_name, arg_value in sorted(kwargs.items()):
  261. if arg_name in arg_dict:
  262. arg_dict[arg_name] = arg_value
  263. elif arg_varkw is not None:
  264. varkwargs[arg_name] = arg_value
  265. else:
  266. raise TypeError("Ignore list for %s() contains an unexpected "
  267. "keyword argument '%s'" % (name, arg_name))
  268. if arg_varkw is not None:
  269. arg_dict['**'] = varkwargs
  270. if arg_varargs is not None:
  271. varargs = args[arg_position + 1:]
  272. arg_dict['*'] = varargs
  273. # Now remove the arguments to be ignored
  274. for item in ignore_lst:
  275. if item in arg_dict:
  276. arg_dict.pop(item)
  277. else:
  278. raise ValueError("Ignore list: argument '%s' is not defined for "
  279. "function %s"
  280. % (item,
  281. _signature_str(name, arg_spec))
  282. )
  283. # XXX: Return a sorted list of pairs?
  284. return arg_dict
  285. def format_signature(func, *args, **kwargs):
  286. # XXX: Should this use inspect.formatargvalues/formatargspec?
  287. module, name = get_func_name(func)
  288. module = [m for m in module if m]
  289. if module:
  290. module.append(name)
  291. module_path = '.'.join(module)
  292. else:
  293. module_path = name
  294. arg_str = list()
  295. previous_length = 0
  296. for arg in args:
  297. arg = pformat(arg, indent=2)
  298. if len(arg) > 1500:
  299. arg = '%s...' % arg[:700]
  300. if previous_length > 80:
  301. arg = '\n%s' % arg
  302. previous_length = len(arg)
  303. arg_str.append(arg)
  304. arg_str.extend(['%s=%s' % (v, pformat(i)) for v, i in kwargs.items()])
  305. arg_str = ', '.join(arg_str)
  306. signature = '%s(%s)' % (name, arg_str)
  307. return module_path, signature
  308. def format_call(func, args, kwargs, object_name="Memory"):
  309. """ Returns a nicely formatted statement displaying the function
  310. call with the given arguments.
  311. """
  312. path, signature = format_signature(func, *args, **kwargs)
  313. msg = '%s\n[%s] Calling %s...\n%s' % (80 * '_', object_name,
  314. path, signature)
  315. return msg
  316. # XXX: Not using logging framework
  317. #self.debug(msg)