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