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