Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tensorflow/python/util/tf_inspect.py: 56%
154 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
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."""
16import collections
17import functools
18import inspect as _inspect
20import six
22from tensorflow.python.util import tf_decorator
25# inspect.signature() is preferred over inspect.getfullargspec() in PY3.
26# Note that while it can handle TFDecorators, it will ignore a TFDecorator's
27# provided ArgSpec/FullArgSpec and instead return the signature of the
28# inner-most function.
29def signature(obj, *, follow_wrapped=True):
30 """TFDecorator-aware replacement for inspect.signature."""
31 return _inspect.signature(
32 tf_decorator.unwrap(obj)[1], follow_wrapped=follow_wrapped)
35Parameter = _inspect.Parameter
36Signature = _inspect.Signature
38if hasattr(_inspect, 'ArgSpec'):
39 ArgSpec = _inspect.ArgSpec
40else:
41 ArgSpec = collections.namedtuple(
42 'ArgSpec',
43 [
44 'args',
45 'varargs',
46 'keywords',
47 'defaults',
48 ],
49 )
52if hasattr(_inspect, 'FullArgSpec'):
53 FullArgSpec = _inspect.FullArgSpec # pylint: disable=invalid-name
54else:
55 FullArgSpec = collections.namedtuple('FullArgSpec', [
56 'args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', 'kwonlydefaults',
57 'annotations'
58 ])
61def _convert_maybe_argspec_to_fullargspec(argspec):
62 if isinstance(argspec, FullArgSpec):
63 return argspec
64 return FullArgSpec(
65 args=argspec.args,
66 varargs=argspec.varargs,
67 varkw=argspec.keywords,
68 defaults=argspec.defaults,
69 kwonlyargs=[],
70 kwonlydefaults=None,
71 annotations={})
73if hasattr(_inspect, 'getfullargspec'):
74 _getfullargspec = _inspect.getfullargspec # pylint: disable=invalid-name
76 def _getargspec(target):
77 """A python3 version of getargspec.
79 Calls `getfullargspec` and assigns args, varargs,
80 varkw, and defaults to a python 2/3 compatible `ArgSpec`.
82 The parameter name 'varkw' is changed to 'keywords' to fit the
83 `ArgSpec` struct.
85 Args:
86 target: the target object to inspect.
88 Returns:
89 An ArgSpec with args, varargs, keywords, and defaults parameters
90 from FullArgSpec.
91 """
92 fullargspecs = getfullargspec(target)
94 defaults = fullargspecs.defaults or ()
95 if fullargspecs.kwonlydefaults:
96 defaults += tuple(fullargspecs.kwonlydefaults.values())
98 if not defaults:
99 defaults = None
101 argspecs = ArgSpec(
102 args=fullargspecs.args + fullargspecs.kwonlyargs,
103 varargs=fullargspecs.varargs,
104 keywords=fullargspecs.varkw,
105 defaults=defaults,
106 )
107 return argspecs
108else:
109 _getargspec = _inspect.getargspec
111 def _getfullargspec(target):
112 """A python2 version of getfullargspec.
114 Args:
115 target: the target object to inspect.
117 Returns:
118 A FullArgSpec with empty kwonlyargs, kwonlydefaults and annotations.
119 """
120 return _convert_maybe_argspec_to_fullargspec(getargspec(target))
123def currentframe():
124 """TFDecorator-aware replacement for inspect.currentframe."""
125 return _inspect.stack()[1][0]
128def getargspec(obj):
129 """TFDecorator-aware replacement for `inspect.getargspec`.
131 Note: `getfullargspec` is recommended as the python 2/3 compatible
132 replacement for this function.
134 Args:
135 obj: A function, partial function, or callable object, possibly decorated.
137 Returns:
138 The `ArgSpec` that describes the signature of the outermost decorator that
139 changes the callable's signature, or the `ArgSpec` that describes
140 the object if not decorated.
142 Raises:
143 ValueError: When callable's signature can not be expressed with
144 ArgSpec.
145 TypeError: For objects of unsupported types.
146 """
147 if isinstance(obj, functools.partial):
148 return _get_argspec_for_partial(obj)
150 decorators, target = tf_decorator.unwrap(obj)
152 spec = next((d.decorator_argspec
153 for d in decorators
154 if d.decorator_argspec is not None), None)
155 if spec:
156 return spec
158 try:
159 # Python3 will handle most callables here (not partial).
160 return _getargspec(target)
161 except TypeError:
162 pass
164 if isinstance(target, type):
165 try:
166 return _getargspec(target.__init__)
167 except TypeError:
168 pass
170 try:
171 return _getargspec(target.__new__)
172 except TypeError:
173 pass
175 # The `type(target)` ensures that if a class is received we don't return
176 # the signature of its __call__ method.
177 return _getargspec(type(target).__call__)
180def _get_argspec_for_partial(obj):
181 """Implements `getargspec` for `functools.partial` objects.
183 Args:
184 obj: The `functools.partial` object
185 Returns:
186 An `inspect.ArgSpec`
187 Raises:
188 ValueError: When callable's signature can not be expressed with
189 ArgSpec.
190 """
191 # When callable is a functools.partial object, we construct its ArgSpec with
192 # following strategy:
193 # - If callable partial contains default value for positional arguments (ie.
194 # object.args), then final ArgSpec doesn't contain those positional arguments.
195 # - If callable partial contains default value for keyword arguments (ie.
196 # object.keywords), then we merge them with wrapped target. Default values
197 # from callable partial takes precedence over those from wrapped target.
198 #
199 # However, there is a case where it is impossible to construct a valid
200 # ArgSpec. Python requires arguments that have no default values must be
201 # defined before those with default values. ArgSpec structure is only valid
202 # when this presumption holds true because default values are expressed as a
203 # tuple of values without keywords and they are always assumed to belong to
204 # last K arguments where K is number of default values present.
205 #
206 # Since functools.partial can give default value to any argument, this
207 # presumption may no longer hold in some cases. For example:
208 #
209 # def func(m, n):
210 # return 2 * m + n
211 # partialed = functools.partial(func, m=1)
212 #
213 # This example will result in m having a default value but n doesn't. This is
214 # usually not allowed in Python and can not be expressed in ArgSpec correctly.
215 #
216 # Thus, we must detect cases like this by finding first argument with default
217 # value and ensures all following arguments also have default values. When
218 # this is not true, a ValueError is raised.
220 n_prune_args = len(obj.args)
221 partial_keywords = obj.keywords or {}
223 args, varargs, keywords, defaults = getargspec(obj.func)
225 # Pruning first n_prune_args arguments.
226 args = args[n_prune_args:]
228 # Partial function may give default value to any argument, therefore length
229 # of default value list must be len(args) to allow each argument to
230 # potentially be given a default value.
231 no_default = object()
232 all_defaults = [no_default] * len(args)
234 if defaults:
235 all_defaults[-len(defaults):] = defaults
237 # Fill in default values provided by partial function in all_defaults.
238 for kw, default in six.iteritems(partial_keywords):
239 if kw in args:
240 idx = args.index(kw)
241 all_defaults[idx] = default
242 elif not keywords:
243 raise ValueError(f'{obj} does not have a **kwargs parameter, but '
244 f'contains an unknown partial keyword {kw}.')
246 # Find first argument with default value set.
247 first_default = next(
248 (idx for idx, x in enumerate(all_defaults) if x is not no_default), None)
250 # If no default values are found, return ArgSpec with defaults=None.
251 if first_default is None:
252 return ArgSpec(args, varargs, keywords, None)
254 # Checks if all arguments have default value set after first one.
255 invalid_default_values = [
256 args[i] for i, j in enumerate(all_defaults)
257 if j is no_default and i > first_default
258 ]
260 if invalid_default_values:
261 raise ValueError(f'{obj} has some keyword-only arguments, which are not'
262 f' supported: {invalid_default_values}.')
264 return ArgSpec(args, varargs, keywords, tuple(all_defaults[first_default:]))
267def getfullargspec(obj):
268 """TFDecorator-aware replacement for `inspect.getfullargspec`.
270 This wrapper emulates `inspect.getfullargspec` in[^)]* Python2.
272 Args:
273 obj: A callable, possibly decorated.
275 Returns:
276 The `FullArgSpec` that describes the signature of
277 the outermost decorator that changes the callable's signature. If the
278 callable is not decorated, `inspect.getfullargspec()` will be called
279 directly on the callable.
280 """
281 decorators, target = tf_decorator.unwrap(obj)
283 for d in decorators:
284 if d.decorator_argspec is not None:
285 return _convert_maybe_argspec_to_fullargspec(d.decorator_argspec)
286 return _getfullargspec(target)
289def getcallargs(*func_and_positional, **named):
290 """TFDecorator-aware replacement for inspect.getcallargs.
292 Args:
293 *func_and_positional: A callable, possibly decorated, followed by any
294 positional arguments that would be passed to `func`.
295 **named: The named argument dictionary that would be passed to `func`.
297 Returns:
298 A dictionary mapping `func`'s named arguments to the values they would
299 receive if `func(*positional, **named)` were called.
301 `getcallargs` will use the argspec from the outermost decorator that provides
302 it. If no attached decorators modify argspec, the final unwrapped target's
303 argspec will be used.
304 """
305 func = func_and_positional[0]
306 positional = func_and_positional[1:]
307 argspec = getfullargspec(func)
308 call_args = named.copy()
309 this = getattr(func, 'im_self', None) or getattr(func, '__self__', None)
310 if ismethod(func) and this:
311 positional = (this,) + positional
312 remaining_positionals = [arg for arg in argspec.args if arg not in call_args]
313 call_args.update(dict(zip(remaining_positionals, positional)))
314 default_count = 0 if not argspec.defaults else len(argspec.defaults)
315 if default_count:
316 for arg, value in zip(argspec.args[-default_count:], argspec.defaults):
317 if arg not in call_args:
318 call_args[arg] = value
319 if argspec.kwonlydefaults is not None:
320 for k, v in argspec.kwonlydefaults.items():
321 if k not in call_args:
322 call_args[k] = v
323 return call_args
326def getframeinfo(*args, **kwargs):
327 return _inspect.getframeinfo(*args, **kwargs)
330def getdoc(object): # pylint: disable=redefined-builtin
331 """TFDecorator-aware replacement for inspect.getdoc.
333 Args:
334 object: An object, possibly decorated.
336 Returns:
337 The docstring associated with the object.
339 The outermost-decorated object is intended to have the most complete
340 documentation, so the decorated parameter is not unwrapped.
341 """
342 return _inspect.getdoc(object)
345def getfile(object): # pylint: disable=redefined-builtin
346 """TFDecorator-aware replacement for inspect.getfile."""
347 unwrapped_object = tf_decorator.unwrap(object)[1]
349 # Work around for the case when object is a stack frame
350 # and only .pyc files are used. In this case, getfile
351 # might return incorrect path. So, we get the path from f_globals
352 # instead.
353 if (hasattr(unwrapped_object, 'f_globals') and
354 '__file__' in unwrapped_object.f_globals):
355 return unwrapped_object.f_globals['__file__']
356 return _inspect.getfile(unwrapped_object)
359def getmembers(object, predicate=None): # pylint: disable=redefined-builtin
360 """TFDecorator-aware replacement for inspect.getmembers."""
361 return _inspect.getmembers(object, predicate)
364def getmodule(object): # pylint: disable=redefined-builtin
365 """TFDecorator-aware replacement for inspect.getmodule."""
366 return _inspect.getmodule(object)
369def getmro(cls):
370 """TFDecorator-aware replacement for inspect.getmro."""
371 return _inspect.getmro(cls)
374def getsource(object): # pylint: disable=redefined-builtin
375 """TFDecorator-aware replacement for inspect.getsource."""
376 return _inspect.getsource(tf_decorator.unwrap(object)[1])
379def getsourcefile(object): # pylint: disable=redefined-builtin
380 """TFDecorator-aware replacement for inspect.getsourcefile."""
381 return _inspect.getsourcefile(tf_decorator.unwrap(object)[1])
384def getsourcelines(object): # pylint: disable=redefined-builtin
385 """TFDecorator-aware replacement for inspect.getsourcelines."""
386 return _inspect.getsourcelines(tf_decorator.unwrap(object)[1])
389def isbuiltin(object): # pylint: disable=redefined-builtin
390 """TFDecorator-aware replacement for inspect.isbuiltin."""
391 return _inspect.isbuiltin(tf_decorator.unwrap(object)[1])
394def isclass(object): # pylint: disable=redefined-builtin
395 """TFDecorator-aware replacement for inspect.isclass."""
396 return _inspect.isclass(tf_decorator.unwrap(object)[1])
399def isfunction(object): # pylint: disable=redefined-builtin
400 """TFDecorator-aware replacement for inspect.isfunction."""
401 return _inspect.isfunction(tf_decorator.unwrap(object)[1])
404def isframe(object): # pylint: disable=redefined-builtin
405 """TFDecorator-aware replacement for inspect.ismodule."""
406 return _inspect.isframe(tf_decorator.unwrap(object)[1])
409def isgenerator(object): # pylint: disable=redefined-builtin
410 """TFDecorator-aware replacement for inspect.isgenerator."""
411 return _inspect.isgenerator(tf_decorator.unwrap(object)[1])
414def isgeneratorfunction(object): # pylint: disable=redefined-builtin
415 """TFDecorator-aware replacement for inspect.isgeneratorfunction."""
416 return _inspect.isgeneratorfunction(tf_decorator.unwrap(object)[1])
419def ismethod(object): # pylint: disable=redefined-builtin
420 """TFDecorator-aware replacement for inspect.ismethod."""
421 return _inspect.ismethod(tf_decorator.unwrap(object)[1])
424def isanytargetmethod(object): # pylint: disable=redefined-builtin
425 # pylint: disable=g-doc-args,g-doc-return-or-yield
426 """Checks if `object` or a TF Decorator wrapped target contains self or cls.
428 This function could be used along with `tf_inspect.getfullargspec` to
429 determine if the first argument of `object` argspec is self or cls. If the
430 first argument is self or cls, it needs to be excluded from argspec when we
431 compare the argspec to the input arguments and, if provided, the tf.function
432 input_signature.
434 Like `tf_inspect.getfullargspec` and python `inspect.getfullargspec`, it
435 does not unwrap python decorators.
437 Args:
438 obj: An method, function, or functool.partial, possibly decorated by
439 TFDecorator.
441 Returns:
442 A bool indicates if `object` or any target along the chain of TF decorators
443 is a method.
444 """
445 decorators, target = tf_decorator.unwrap(object)
446 for decorator in decorators:
447 if _inspect.ismethod(decorator.decorated_target):
448 return True
450 # TODO(b/194845243): Implement the long term solution with inspect.signature.
451 # A functools.partial object is not a function or method. But if the wrapped
452 # func is a method, the argspec will contain self/cls.
453 while isinstance(target, functools.partial):
454 target = target.func
456 # `target` is a method or an instance with __call__
457 return callable(target) and not _inspect.isfunction(target)
460def ismodule(object): # pylint: disable=redefined-builtin
461 """TFDecorator-aware replacement for inspect.ismodule."""
462 return _inspect.ismodule(tf_decorator.unwrap(object)[1])
465def isroutine(object): # pylint: disable=redefined-builtin
466 """TFDecorator-aware replacement for inspect.isroutine."""
467 return _inspect.isroutine(tf_decorator.unwrap(object)[1])
470def stack(context=1):
471 """TFDecorator-aware replacement for inspect.stack."""
472 return _inspect.stack(context)[1:]