• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15"""TFDecorator-aware replacements for the inspect module."""
16from __future__ import absolute_import
17from __future__ import division
18from __future__ import print_function
19
20from collections import namedtuple
21import functools
22import inspect as _inspect
23
24import six
25
26from tensorflow.python.util import tf_decorator
27
28ArgSpec = _inspect.ArgSpec
29
30
31if hasattr(_inspect, 'FullArgSpec'):
32  FullArgSpec = _inspect.FullArgSpec  # pylint: disable=invalid-name
33else:
34  FullArgSpec = namedtuple('FullArgSpec', [
35      'args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', 'kwonlydefaults',
36      'annotations'
37  ])
38
39
40def _convert_maybe_argspec_to_fullargspec(argspec):
41  if isinstance(argspec, FullArgSpec):
42    return argspec
43  return FullArgSpec(
44      args=argspec.args,
45      varargs=argspec.varargs,
46      varkw=argspec.keywords,
47      defaults=argspec.defaults,
48      kwonlyargs=[],
49      kwonlydefaults=None,
50      annotations={})
51
52if hasattr(_inspect, 'getfullargspec'):
53  _getfullargspec = _inspect.getfullargspec  # pylint: disable=invalid-name
54
55  def _getargspec(target):
56    """A python3 version of getargspec.
57
58    Calls `getfullargspec` and assigns args, varargs,
59    varkw, and defaults to a python 2/3 compatible `ArgSpec`.
60
61    The parameter name 'varkw' is changed to 'keywords' to fit the
62    `ArgSpec` struct.
63
64    Args:
65      target: the target object to inspect.
66
67    Returns:
68      An ArgSpec with args, varargs, keywords, and defaults parameters
69      from FullArgSpec.
70    """
71    fullargspecs = getfullargspec(target)
72    argspecs = ArgSpec(
73        args=fullargspecs.args,
74        varargs=fullargspecs.varargs,
75        keywords=fullargspecs.varkw,
76        defaults=fullargspecs.defaults)
77    return argspecs
78else:
79  _getargspec = _inspect.getargspec
80
81  def _getfullargspec(target):
82    """A python2 version of getfullargspec.
83
84    Args:
85      target: the target object to inspect.
86
87    Returns:
88      A FullArgSpec with empty kwonlyargs, kwonlydefaults and annotations.
89    """
90    return _convert_maybe_argspec_to_fullargspec(getargspec(target))
91
92
93def currentframe():
94  """TFDecorator-aware replacement for inspect.currentframe."""
95  return _inspect.stack()[1][0]
96
97
98def getargspec(obj):
99  """TFDecorator-aware replacement for `inspect.getargspec`.
100
101  Note: `getfullargspec` is recommended as the python 2/3 compatible
102  replacement for this function.
103
104  Args:
105    obj: A function, partial function, or callable object, possibly decorated.
106
107  Returns:
108    The `ArgSpec` that describes the signature of the outermost decorator that
109    changes the callable's signature, or the `ArgSpec` that describes
110    the object if not decorated.
111
112  Raises:
113    ValueError: When callable's signature can not be expressed with
114      ArgSpec.
115    TypeError: For objects of unsupported types.
116  """
117  if isinstance(obj, functools.partial):
118    return _get_argspec_for_partial(obj)
119
120  decorators, target = tf_decorator.unwrap(obj)
121
122  spec = next((d.decorator_argspec
123               for d in decorators
124               if d.decorator_argspec is not None), None)
125  if spec:
126    return spec
127
128  try:
129    # Python3 will handle most callables here (not partial).
130    return _getargspec(target)
131  except TypeError:
132    pass
133
134  if isinstance(target, type):
135    try:
136      return _getargspec(target.__init__)
137    except TypeError:
138      pass
139
140    try:
141      return _getargspec(target.__new__)
142    except TypeError:
143      pass
144
145  # The `type(target)` ensures that if a class is received we don't return
146  # the signature of it's __call__ method.
147  return _getargspec(type(target).__call__)
148
149
150def _get_argspec_for_partial(obj):
151  """Implements `getargspec` for `functools.partial` objects.
152
153  Args:
154    obj: The `functools.partial` obeject
155  Returns:
156    An `inspect.ArgSpec`
157  Raises:
158    ValueError: When callable's signature can not be expressed with
159      ArgSpec.
160  """
161  # When callable is a functools.partial object, we construct its ArgSpec with
162  # following strategy:
163  # - If callable partial contains default value for positional arguments (ie.
164  # object.args), then final ArgSpec doesn't contain those positional arguments.
165  # - If callable partial contains default value for keyword arguments (ie.
166  # object.keywords), then we merge them with wrapped target. Default values
167  # from callable partial takes precedence over those from wrapped target.
168  #
169  # However, there is a case where it is impossible to construct a valid
170  # ArgSpec. Python requires arguments that have no default values must be
171  # defined before those with default values. ArgSpec structure is only valid
172  # when this presumption holds true because default values are expressed as a
173  # tuple of values without keywords and they are always assumed to belong to
174  # last K arguments where K is number of default values present.
175  #
176  # Since functools.partial can give default value to any argument, this
177  # presumption may no longer hold in some cases. For example:
178  #
179  # def func(m, n):
180  #   return 2 * m + n
181  # partialed = functools.partial(func, m=1)
182  #
183  # This example will result in m having a default value but n doesn't. This is
184  # usually not allowed in Python and can not be expressed in ArgSpec correctly.
185  #
186  # Thus, we must detect cases like this by finding first argument with default
187  # value and ensures all following arguments also have default values. When
188  # this is not true, a ValueError is raised.
189
190  n_prune_args = len(obj.args)
191  partial_keywords = obj.keywords or {}
192
193  args, varargs, keywords, defaults = getargspec(obj.func)
194
195  # Pruning first n_prune_args arguments.
196  args = args[n_prune_args:]
197
198  # Partial function may give default value to any argument, therefore length
199  # of default value list must be len(args) to allow each argument to
200  # potentially be given a default value.
201  no_default = object()
202  all_defaults = [no_default] * len(args)
203
204  if defaults:
205    all_defaults[-len(defaults):] = defaults
206
207  # Fill in default values provided by partial function in all_defaults.
208  for kw, default in six.iteritems(partial_keywords):
209    idx = args.index(kw)
210    all_defaults[idx] = default
211
212  # Find first argument with default value set.
213  first_default = next(
214      (idx for idx, x in enumerate(all_defaults) if x is not no_default), None)
215
216  # If no default values are found, return ArgSpec with defaults=None.
217  if first_default is None:
218    return ArgSpec(args, varargs, keywords, None)
219
220  # Checks if all arguments have default value set after first one.
221  invalid_default_values = [
222      args[i] for i, j in enumerate(all_defaults)
223      if j is no_default and i > first_default
224  ]
225
226  if invalid_default_values:
227    raise ValueError('Some arguments %s do not have default value, but they '
228                     'are positioned after those with default values. This can '
229                     'not be expressed with ArgSpec.' % invalid_default_values)
230
231  return ArgSpec(args, varargs, keywords, tuple(all_defaults[first_default:]))
232
233
234def getfullargspec(obj):
235  """TFDecorator-aware replacement for `inspect.getfullargspec`.
236
237  This wrapper emulates `inspect.getfullargspec` in[^)]* Python2.
238
239  Args:
240    obj: A callable, possibly decorated.
241
242  Returns:
243    The `FullArgSpec` that describes the signature of
244    the outermost decorator that changes the callable's signature. If the
245    callable is not decorated, `inspect.getfullargspec()` will be called
246    directly on the callable.
247  """
248  decorators, target = tf_decorator.unwrap(obj)
249  return next((_convert_maybe_argspec_to_fullargspec(d.decorator_argspec)
250               for d in decorators
251               if d.decorator_argspec is not None), _getfullargspec(target))
252
253
254def getcallargs(func, *positional, **named):
255  """TFDecorator-aware replacement for inspect.getcallargs.
256
257  Args:
258    func: A callable, possibly decorated
259    *positional: The positional arguments that would be passed to `func`.
260    **named: The named argument dictionary that would be passed to `func`.
261
262  Returns:
263    A dictionary mapping `func`'s named arguments to the values they would
264    receive if `func(*positional, **named)` were called.
265
266  `getcallargs` will use the argspec from the outermost decorator that provides
267  it. If no attached decorators modify argspec, the final unwrapped target's
268  argspec will be used.
269  """
270  argspec = getfullargspec(func)
271  call_args = named.copy()
272  this = getattr(func, 'im_self', None) or getattr(func, '__self__', None)
273  if ismethod(func) and this:
274    positional = (this,) + positional
275  remaining_positionals = [arg for arg in argspec.args if arg not in call_args]
276  call_args.update(dict(zip(remaining_positionals, positional)))
277  default_count = 0 if not argspec.defaults else len(argspec.defaults)
278  if default_count:
279    for arg, value in zip(argspec.args[-default_count:], argspec.defaults):
280      if arg not in call_args:
281        call_args[arg] = value
282  return call_args
283
284
285def getframeinfo(*args, **kwargs):
286  return _inspect.getframeinfo(*args, **kwargs)
287
288
289def getdoc(object):  # pylint: disable=redefined-builtin
290  """TFDecorator-aware replacement for inspect.getdoc.
291
292  Args:
293    object: An object, possibly decorated.
294
295  Returns:
296    The docstring associated with the object.
297
298  The outermost-decorated object is intended to have the most complete
299  documentation, so the decorated parameter is not unwrapped.
300  """
301  return _inspect.getdoc(object)
302
303
304def getfile(object):  # pylint: disable=redefined-builtin
305  """TFDecorator-aware replacement for inspect.getfile."""
306  unwrapped_object = tf_decorator.unwrap(object)[1]
307
308  # Work around for the case when object is a stack frame
309  # and only .pyc files are used. In this case, getfile
310  # might return incorrect path. So, we get the path from f_globals
311  # instead.
312  if (hasattr(unwrapped_object, 'f_globals') and
313      '__file__' in unwrapped_object.f_globals):
314    return unwrapped_object.f_globals['__file__']
315  return _inspect.getfile(unwrapped_object)
316
317
318def getmembers(object, predicate=None):  # pylint: disable=redefined-builtin
319  """TFDecorator-aware replacement for inspect.getmembers."""
320  return _inspect.getmembers(object, predicate)
321
322
323def getmodule(object):  # pylint: disable=redefined-builtin
324  """TFDecorator-aware replacement for inspect.getmodule."""
325  return _inspect.getmodule(object)
326
327
328def getmro(cls):
329  """TFDecorator-aware replacement for inspect.getmro."""
330  return _inspect.getmro(cls)
331
332
333def getsource(object):  # pylint: disable=redefined-builtin
334  """TFDecorator-aware replacement for inspect.getsource."""
335  return _inspect.getsource(tf_decorator.unwrap(object)[1])
336
337
338def getsourcefile(object):  # pylint: disable=redefined-builtin
339  """TFDecorator-aware replacement for inspect.getsourcefile."""
340  return _inspect.getsourcefile(tf_decorator.unwrap(object)[1])
341
342
343def getsourcelines(object):  # pylint: disable=redefined-builtin
344  """TFDecorator-aware replacement for inspect.getsourcelines."""
345  return _inspect.getsourcelines(tf_decorator.unwrap(object)[1])
346
347
348def isbuiltin(object):  # pylint: disable=redefined-builtin
349  """TFDecorator-aware replacement for inspect.isbuiltin."""
350  return _inspect.isbuiltin(tf_decorator.unwrap(object)[1])
351
352
353def isclass(object):  # pylint: disable=redefined-builtin
354  """TFDecorator-aware replacement for inspect.isclass."""
355  return _inspect.isclass(tf_decorator.unwrap(object)[1])
356
357
358def isfunction(object):  # pylint: disable=redefined-builtin
359  """TFDecorator-aware replacement for inspect.isfunction."""
360  return _inspect.isfunction(tf_decorator.unwrap(object)[1])
361
362
363def isframe(object):  # pylint: disable=redefined-builtin
364  """TFDecorator-aware replacement for inspect.ismodule."""
365  return _inspect.isframe(tf_decorator.unwrap(object)[1])
366
367
368def isgenerator(object):  # pylint: disable=redefined-builtin
369  """TFDecorator-aware replacement for inspect.isgenerator."""
370  return _inspect.isgenerator(tf_decorator.unwrap(object)[1])
371
372
373def ismethod(object):  # pylint: disable=redefined-builtin
374  """TFDecorator-aware replacement for inspect.ismethod."""
375  return _inspect.ismethod(tf_decorator.unwrap(object)[1])
376
377
378def ismodule(object):  # pylint: disable=redefined-builtin
379  """TFDecorator-aware replacement for inspect.ismodule."""
380  return _inspect.ismodule(tf_decorator.unwrap(object)[1])
381
382
383def isroutine(object):  # pylint: disable=redefined-builtin
384  """TFDecorator-aware replacement for inspect.isroutine."""
385  return _inspect.isroutine(tf_decorator.unwrap(object)[1])
386
387
388def stack(context=1):
389  """TFDecorator-aware replacement for inspect.stack."""
390  return _inspect.stack(context)[1:]
391
392
393def getsource_no_unwrap(obj):
394  """Return source code for an object. Does not unwrap TFDecorators.
395
396  The source code is returned literally, including indentation for functions not
397  at the top level. This function is analogous to inspect.getsource, with one
398  key difference - it doesn't unwrap decorators. For simplicity, support for
399  some Python object types is dropped (tracebacks, frames, code objects).
400
401  Args:
402      obj: a class, method, or function object.
403
404  Returns:
405      source code as a string
406
407  """
408  lines, lnum = _inspect.findsource(obj)
409  return ''.join(_inspect.getblock(lines[lnum:]))
410