1# Copyright 2016 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 16"""Tensor utility functions.""" 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import print_function 20 21import collections 22import functools 23import inspect 24import re 25 26from tensorflow.python.platform import tf_logging as logging 27from tensorflow.python.util import decorator_utils 28from tensorflow.python.util import is_in_graph_mode 29from tensorflow.python.util import tf_contextlib 30from tensorflow.python.util import tf_decorator 31from tensorflow.python.util import tf_inspect 32from tensorflow.tools.docs import doc_controls 33 34 35# Allow deprecation warnings to be silenced temporarily with a context manager. 36_PRINT_DEPRECATION_WARNINGS = True 37 38# Remember which deprecation warnings have been printed already. 39_PRINTED_WARNING = {} 40 41 42class DeprecatedNamesAlreadySet(Exception): 43 """Raised when setting deprecated names multiple times for the same symbol.""" 44 pass 45 46 47def _add_deprecated_function_notice_to_docstring(doc, date, instructions): 48 """Adds a deprecation notice to a docstring for deprecated functions.""" 49 main_text = ['THIS FUNCTION IS DEPRECATED. It will be removed %s.' % 50 ('in a future version' if date is None else ('after %s' % date))] 51 if instructions: 52 main_text.append('Instructions for updating:') 53 return decorator_utils.add_notice_to_docstring( 54 doc, instructions, 55 'DEPRECATED FUNCTION', 56 '(deprecated)', main_text) 57 58 59def _add_deprecated_arg_notice_to_docstring(doc, date, instructions, 60 deprecated_names): 61 """Adds a deprecation notice to a docstring for deprecated arguments.""" 62 63 deprecation_string = ', '.join(sorted(deprecated_names)) 64 65 return decorator_utils.add_notice_to_docstring( 66 doc, instructions, 'DEPRECATED FUNCTION ARGUMENTS', 67 '(deprecated arguments)', [ 68 'SOME ARGUMENTS ARE DEPRECATED: `(%s)`. ' 69 'They will be removed %s.' % 70 (deprecation_string, 'in a future version' if date is None else 71 ('after %s' % date)), 'Instructions for updating:' 72 ]) 73 74 75def _add_deprecated_arg_value_notice_to_docstring(doc, date, instructions, 76 deprecated_name_value_dict): 77 """Adds a deprecation notice to a docstring for deprecated arguments.""" 78 79 deprecation_string = ', '.join( 80 '%s=%r' % (key, value) 81 for key, value in sorted(deprecated_name_value_dict.items())) 82 83 when = 'in a future version' if date is None else ('after %s' % date) 84 85 return decorator_utils.add_notice_to_docstring( 86 doc, instructions, 'DEPRECATED FUNCTION ARGUMENT VALUES', 87 '(deprecated argument values)', [ 88 'SOME ARGUMENT VALUES ARE DEPRECATED: `(%s)`. ' 89 'They will be removed %s.' % (deprecation_string, when), 90 'Instructions for updating:' 91 ]) 92 93 94def _validate_deprecation_args(date, instructions): 95 if date is not None and not re.match(r'20\d\d-[01]\d-[0123]\d', date): 96 raise ValueError('Date must be YYYY-MM-DD.') 97 if not instructions: 98 raise ValueError('Don\'t deprecate things without conversion instructions!') 99 100 101def _call_location(outer=False): 102 """Returns call location given level up from current call.""" 103 # Two up: <_call_location>, <_call_location's caller> 104 f = inspect.currentframe().f_back.f_back 105 parent = f.f_back 106 if outer and parent is not None: 107 f = parent 108 return '{}:{}'.format(f.f_code.co_filename, f.f_lineno) 109 110 111def _wrap_decorator(wrapped_function): 112 """Indicate that one function wraps another. 113 114 This decorator wraps a function using `tf_decorator.make_decorator` 115 so that doc generation scripts can pick up original function 116 signature. 117 It would be better to use @functools.wrap decorator, but it would 118 not update function signature to match wrapped function in Python 2. 119 120 Args: 121 wrapped_function: The function that decorated function wraps. 122 123 Returns: 124 Function that accepts wrapper function as an argument and returns 125 `TFDecorator` instance. 126 """ 127 def wrapper(wrapper_func): 128 return tf_decorator.make_decorator(wrapped_function, wrapper_func) 129 return wrapper 130 131 132def deprecated_alias(deprecated_name, name, func_or_class, warn_once=True): 133 """Deprecate a symbol in favor of a new name with identical semantics. 134 135 This function is meant to be used when defining a backwards-compatibility 136 alias for a symbol which has been moved. For example: 137 138 module1.py: 139 ```python 140 class NewNameForClass: pass 141 ``` 142 143 module2.py: 144 ```python 145 import module1 146 147 DeprecatedNameForClass = deprecated_alias( 148 deprecated_name='module2.DeprecatedNameForClass', 149 name='module1.NewNameForClass', 150 func_or_class=module1.NewNameForClass) 151 ``` 152 153 This function works for classes and functions. 154 155 For classes, it creates a new class which is functionally identical (it 156 inherits from the original, and overrides its constructor), but which prints 157 a deprecation warning when an instance is created. It also adds a deprecation 158 notice to the class' docstring. 159 160 For functions, it returns a function wrapped by `tf_decorator.make_decorator`. 161 That function prints a warning when used, and has a deprecation notice in its 162 docstring. This is more or less equivalent (the deprecation warning has 163 slightly different text) to writing: 164 165 ```python 166 @deprecated 167 def deprecated_alias(original_args): 168 real_function(original_args) 169 ``` 170 171 Args: 172 deprecated_name: The name of the symbol that is being deprecated, to be used 173 in the warning message. This should be its fully qualified name to avoid 174 confusion. 175 name: The name of the symbol that is to be used instead of the deprecated 176 name. This should be a fully qualified name to avoid confusion. 177 func_or_class: The (non-deprecated) class or function for which a deprecated 178 alias should be created. 179 warn_once: If True (the default), only print a deprecation warning the first 180 time this function is used, or the class is instantiated. 181 182 Returns: 183 A wrapped version of `func_or_class` which prints a deprecation warning on 184 use and has a modified docstring. 185 """ 186 if tf_inspect.isclass(func_or_class): 187 188 # Make a new class with __init__ wrapped in a warning. 189 class _NewClass(func_or_class): # pylint: disable=missing-docstring 190 __doc__ = decorator_utils.add_notice_to_docstring( 191 func_or_class.__doc__, 'Please use %s instead.' % name, 192 'DEPRECATED CLASS', 193 '(deprecated)', ['THIS CLASS IS DEPRECATED. ' 194 'It will be removed in a future version. ']) 195 __name__ = func_or_class.__name__ 196 __module__ = _call_location(outer=True) 197 198 @_wrap_decorator(func_or_class.__init__) 199 def __init__(self, *args, **kwargs): 200 if hasattr(_NewClass.__init__, '__func__'): 201 # Python 2 202 _NewClass.__init__.__func__.__doc__ = func_or_class.__init__.__doc__ 203 else: 204 # Python 3 205 _NewClass.__init__.__doc__ = func_or_class.__init__.__doc__ 206 207 if _PRINT_DEPRECATION_WARNINGS: 208 # We're making the alias as we speak. The original may have other 209 # aliases, so we cannot use it to check for whether it's already been 210 # warned about. 211 if _NewClass.__init__ not in _PRINTED_WARNING: 212 if warn_once: 213 _PRINTED_WARNING[_NewClass.__init__] = True 214 logging.warning( 215 'From %s: The name %s is deprecated. Please use %s instead.\n', 216 _call_location(), deprecated_name, name) 217 super(_NewClass, self).__init__(*args, **kwargs) 218 219 return _NewClass 220 else: 221 decorator_utils.validate_callable(func_or_class, 'deprecated') 222 223 # Make a wrapper for the original 224 @functools.wraps(func_or_class) 225 def new_func(*args, **kwargs): # pylint: disable=missing-docstring 226 if _PRINT_DEPRECATION_WARNINGS: 227 # We're making the alias as we speak. The original may have other 228 # aliases, so we cannot use it to check for whether it's already been 229 # warned about. 230 if new_func not in _PRINTED_WARNING: 231 if warn_once: 232 _PRINTED_WARNING[new_func] = True 233 logging.warning( 234 'From %s: The name %s is deprecated. Please use %s instead.\n', 235 _call_location(), deprecated_name, name) 236 return func_or_class(*args, **kwargs) 237 return tf_decorator.make_decorator( 238 func_or_class, new_func, 'deprecated', 239 _add_deprecated_function_notice_to_docstring( 240 func_or_class.__doc__, None, 'Please use %s instead.' % name)) 241 242 243def deprecated_endpoints(*args): 244 """Decorator for marking endpoints deprecated. 245 246 This decorator does not print deprecation messages. 247 TODO(annarev): eventually start printing deprecation warnings when 248 @deprecation_endpoints decorator is added. 249 250 Args: 251 *args: Deprecated endpoint names. 252 253 Returns: 254 A function that takes symbol as an argument and adds 255 _tf_deprecated_api_names to that symbol. 256 _tf_deprecated_api_names would be set to a list of deprecated 257 endpoint names for the symbol. 258 """ 259 def deprecated_wrapper(func): 260 # pylint: disable=protected-access 261 if '_tf_deprecated_api_names' in func.__dict__: 262 raise DeprecatedNamesAlreadySet( 263 'Cannot set deprecated names for %s to %s. ' 264 'Deprecated names are already set to %s.' % ( 265 func.__name__, str(args), str(func._tf_deprecated_api_names))) 266 func._tf_deprecated_api_names = args 267 # pylint: disable=protected-access 268 return func 269 return deprecated_wrapper 270 271 272def deprecated(date, instructions, warn_once=True): 273 """Decorator for marking functions or methods deprecated. 274 275 This decorator logs a deprecation warning whenever the decorated function is 276 called. It has the following format: 277 278 <function> (from <module>) is deprecated and will be removed after <date>. 279 Instructions for updating: 280 <instructions> 281 282 If `date` is None, 'after <date>' is replaced with 'in a future version'. 283 <function> will include the class name if it is a method. 284 285 It also edits the docstring of the function: ' (deprecated)' is appended 286 to the first line of the docstring and a deprecation notice is prepended 287 to the rest of the docstring. 288 289 Args: 290 date: String or None. The date the function is scheduled to be removed. 291 Must be ISO 8601 (YYYY-MM-DD), or None. 292 instructions: String. Instructions on how to update code using the 293 deprecated function. 294 warn_once: Boolean. Set to `True` to warn only the first time the decorated 295 function is called. Otherwise, every call will log a warning. 296 297 Returns: 298 Decorated function or method. 299 300 Raises: 301 ValueError: If date is not None or in ISO 8601 format, or instructions are 302 empty. 303 """ 304 _validate_deprecation_args(date, instructions) 305 306 def deprecated_wrapper(func_or_class): 307 """Deprecation wrapper.""" 308 if isinstance(func_or_class, type): 309 # If a class is deprecated, you actually want to wrap the constructor. 310 cls = func_or_class 311 if cls.__new__ is object.__new__: 312 func = cls.__init__ 313 constructor_name = '__init__' 314 else: 315 func = cls.__new__ 316 constructor_name = '__new__' 317 318 else: 319 cls = None 320 constructor_name = None 321 func = func_or_class 322 323 decorator_utils.validate_callable(func, 'deprecated') 324 @functools.wraps(func) 325 def new_func(*args, **kwargs): # pylint: disable=missing-docstring 326 if _PRINT_DEPRECATION_WARNINGS: 327 if func not in _PRINTED_WARNING: 328 if warn_once: 329 _PRINTED_WARNING[func] = True 330 logging.warning( 331 'From %s: %s (from %s) is deprecated and will be removed %s.\n' 332 'Instructions for updating:\n%s', 333 _call_location(), decorator_utils.get_qualified_name(func), 334 func.__module__, 335 'in a future version' if date is None else ('after %s' % date), 336 instructions) 337 return func(*args, **kwargs) 338 339 doc_controls.set_deprecated(new_func) 340 new_func = tf_decorator.make_decorator( 341 func, new_func, 'deprecated', 342 _add_deprecated_function_notice_to_docstring(func.__doc__, date, 343 instructions)) 344 345 if cls is None: 346 return new_func 347 else: 348 # Insert the wrapped function as the constructor 349 setattr(cls, constructor_name, new_func) 350 351 # And update the docstring of the class. 352 cls.__doc__ = _add_deprecated_function_notice_to_docstring( 353 cls.__doc__, date, instructions) 354 355 return cls 356 357 return deprecated_wrapper 358 359 360DeprecatedArgSpec = collections.namedtuple( 361 'DeprecatedArgSpec', ['position', 'has_ok_value', 'ok_value']) 362 363 364def deprecated_args(date, instructions, *deprecated_arg_names_or_tuples, 365 **kwargs): 366 """Decorator for marking specific function arguments as deprecated. 367 368 This decorator logs a deprecation warning whenever the decorated function is 369 called with the deprecated argument. It has the following format: 370 371 Calling <function> (from <module>) with <arg> is deprecated and will be 372 removed after <date>. Instructions for updating: 373 <instructions> 374 375 If `date` is None, 'after <date>' is replaced with 'in a future version'. 376 <function> includes the class name if it is a method. 377 378 It also edits the docstring of the function: ' (deprecated arguments)' is 379 appended to the first line of the docstring and a deprecation notice is 380 prepended to the rest of the docstring. 381 382 Args: 383 date: String or None. The date the function is scheduled to be removed. 384 Must be ISO 8601 (YYYY-MM-DD), or None. 385 instructions: String. Instructions on how to update code using the 386 deprecated function. 387 *deprecated_arg_names_or_tuples: String or 2-Tuple(String, 388 [ok_vals]). The string is the deprecated argument name. 389 Optionally, an ok-value may be provided. If the user provided 390 argument equals this value, the warning is suppressed. 391 **kwargs: If `warn_once=False` is passed, every call with a deprecated 392 argument will log a warning. The default behavior is to only warn the 393 first time the function is called with any given deprecated argument. 394 All other kwargs raise `ValueError`. 395 396 Returns: 397 Decorated function or method. 398 399 Raises: 400 ValueError: If date is not None or in ISO 8601 format, instructions are 401 empty, the deprecated arguments are not present in the function 402 signature, the second element of a deprecated_tuple is not a 403 list, or if a kwarg other than `warn_once` is passed. 404 """ 405 _validate_deprecation_args(date, instructions) 406 if not deprecated_arg_names_or_tuples: 407 raise ValueError('Specify which argument is deprecated.') 408 if kwargs and list(kwargs.keys()) != ['warn_once']: 409 kwargs.pop('warn_once', None) 410 raise ValueError('Illegal argument to deprecated_args: %s' % kwargs) 411 warn_once = kwargs.get('warn_once', True) 412 413 def _get_arg_names_to_ok_vals(): 414 """Returns a dict mapping arg_name to DeprecatedArgSpec w/o position.""" 415 d = {} 416 for name_or_tuple in deprecated_arg_names_or_tuples: 417 if isinstance(name_or_tuple, tuple): 418 d[name_or_tuple[0]] = DeprecatedArgSpec(-1, True, name_or_tuple[1]) 419 else: 420 d[name_or_tuple] = DeprecatedArgSpec(-1, False, None) 421 return d 422 423 def _get_deprecated_positional_arguments(names_to_ok_vals, arg_spec): 424 """Builds a dictionary from deprecated arguments to their spec. 425 426 Returned dict is keyed by argument name. 427 Each value is a DeprecatedArgSpec with the following fields: 428 position: The zero-based argument position of the argument 429 within the signature. None if the argument isn't found in 430 the signature. 431 ok_values: Values of this argument for which warning will be 432 suppressed. 433 434 Args: 435 names_to_ok_vals: dict from string arg_name to a list of values, 436 possibly empty, which should not elicit a warning. 437 arg_spec: Output from tf_inspect.getfullargspec on the called function. 438 439 Returns: 440 Dictionary from arg_name to DeprecatedArgSpec. 441 """ 442 arg_name_to_pos = { 443 name: pos for pos, name in enumerate(arg_spec.args)} 444 deprecated_positional_args = {} 445 for arg_name, spec in iter(names_to_ok_vals.items()): 446 if arg_name in arg_name_to_pos: 447 pos = arg_name_to_pos[arg_name] 448 deprecated_positional_args[arg_name] = DeprecatedArgSpec( 449 pos, spec.has_ok_value, spec.ok_value) 450 return deprecated_positional_args 451 452 deprecated_arg_names = _get_arg_names_to_ok_vals() 453 454 def deprecated_wrapper(func): 455 """Deprecation decorator.""" 456 decorator_utils.validate_callable(func, 'deprecated_args') 457 458 arg_spec = tf_inspect.getfullargspec(func) 459 deprecated_positions = _get_deprecated_positional_arguments( 460 deprecated_arg_names, arg_spec) 461 462 is_varargs_deprecated = arg_spec.varargs in deprecated_arg_names 463 is_kwargs_deprecated = arg_spec.varkw in deprecated_arg_names 464 465 if (len(deprecated_positions) + is_varargs_deprecated + is_kwargs_deprecated 466 != len(deprecated_arg_names_or_tuples)): 467 known_args = arg_spec.args + [arg_spec.varargs, arg_spec.varkw] 468 missing_args = [arg_name for arg_name in deprecated_arg_names 469 if arg_name not in known_args] 470 raise ValueError('The following deprecated arguments are not present ' 471 'in the function signature: %s. ' 472 'Found next arguments: %s.' % (missing_args, known_args)) 473 474 def _same_value(a, b): 475 """A comparison operation that works for multiple object types. 476 477 Returns True for two empty lists, two numeric values with the 478 same value, etc. 479 480 Returns False for (pd.DataFrame, None), and other pairs which 481 should not be considered equivalent. 482 483 Args: 484 a: value one of the comparison. 485 b: value two of the comparison. 486 487 Returns: 488 A boolean indicating whether the two inputs are the same value 489 for the purposes of deprecation. 490 """ 491 if a is b: 492 return True 493 try: 494 equality = a == b 495 if isinstance(equality, bool): 496 return equality 497 except TypeError: 498 return False 499 return False 500 501 @functools.wraps(func) 502 def new_func(*args, **kwargs): 503 """Deprecation wrapper.""" 504 # TODO(apassos) figure out a way to have reasonable performance with 505 # deprecation warnings and eager mode. 506 if is_in_graph_mode.IS_IN_GRAPH_MODE() and _PRINT_DEPRECATION_WARNINGS: 507 invalid_args = [] 508 named_args = tf_inspect.getcallargs(func, *args, **kwargs) 509 for arg_name, spec in iter(deprecated_positions.items()): 510 if (spec.position < len(args) and 511 not (spec.has_ok_value and 512 _same_value(named_args[arg_name], spec.ok_value))): 513 invalid_args.append(arg_name) 514 if is_varargs_deprecated and len(args) > len(arg_spec.args): 515 invalid_args.append(arg_spec.varargs) 516 if is_kwargs_deprecated and kwargs: 517 invalid_args.append(arg_spec.varkw) 518 for arg_name in deprecated_arg_names: 519 if (arg_name in kwargs and 520 not (deprecated_positions[arg_name].has_ok_value and 521 _same_value(named_args[arg_name], 522 deprecated_positions[arg_name].ok_value))): 523 invalid_args.append(arg_name) 524 for arg_name in invalid_args: 525 if (func, arg_name) not in _PRINTED_WARNING: 526 if warn_once: 527 _PRINTED_WARNING[(func, arg_name)] = True 528 logging.warning( 529 'From %s: calling %s (from %s) with %s is deprecated and will ' 530 'be removed %s.\nInstructions for updating:\n%s', 531 _call_location(), decorator_utils.get_qualified_name(func), 532 func.__module__, arg_name, 533 'in a future version' if date is None else ('after %s' % date), 534 instructions) 535 return func(*args, **kwargs) 536 537 doc = _add_deprecated_arg_notice_to_docstring( 538 func.__doc__, date, instructions, sorted(deprecated_arg_names.keys())) 539 return tf_decorator.make_decorator(func, new_func, 'deprecated', doc) 540 541 return deprecated_wrapper 542 543 544def deprecated_arg_values(date, instructions, warn_once=True, 545 **deprecated_kwargs): 546 """Decorator for marking specific function argument values as deprecated. 547 548 This decorator logs a deprecation warning whenever the decorated function is 549 called with the deprecated argument values. It has the following format: 550 551 Calling <function> (from <module>) with <arg>=<value> is deprecated and 552 will be removed after <date>. Instructions for updating: 553 <instructions> 554 555 If `date` is None, 'after <date>' is replaced with 'in a future version'. 556 <function> will include the class name if it is a method. 557 558 It also edits the docstring of the function: ' (deprecated arguments)' is 559 appended to the first line of the docstring and a deprecation notice is 560 prepended to the rest of the docstring. 561 562 Args: 563 date: String or None. The date the function is scheduled to be removed. 564 Must be ISO 8601 (YYYY-MM-DD), or None 565 instructions: String. Instructions on how to update code using the 566 deprecated function. 567 warn_once: If `True`, warn only the first time this function is called with 568 deprecated argument values. Otherwise, every call (with a deprecated 569 argument value) will log a warning. 570 **deprecated_kwargs: The deprecated argument values. 571 572 Returns: 573 Decorated function or method. 574 575 Raises: 576 ValueError: If date is not None or in ISO 8601 format, or instructions are 577 empty. 578 """ 579 _validate_deprecation_args(date, instructions) 580 if not deprecated_kwargs: 581 raise ValueError('Specify which argument values are deprecated.') 582 583 def deprecated_wrapper(func): 584 """Deprecation decorator.""" 585 decorator_utils.validate_callable(func, 'deprecated_arg_values') 586 @functools.wraps(func) 587 def new_func(*args, **kwargs): 588 """Deprecation wrapper.""" 589 if _PRINT_DEPRECATION_WARNINGS: 590 named_args = tf_inspect.getcallargs(func, *args, **kwargs) 591 for arg_name, arg_value in deprecated_kwargs.items(): 592 if arg_name in named_args and named_args[arg_name] == arg_value: 593 if (func, arg_name) not in _PRINTED_WARNING: 594 if warn_once: 595 _PRINTED_WARNING[(func, arg_name)] = True 596 logging.warning( 597 'From %s: calling %s (from %s) with %s=%s is deprecated and ' 598 'will be removed %s.\nInstructions for updating:\n%s', 599 _call_location(), decorator_utils.get_qualified_name(func), 600 func.__module__, arg_name, arg_value, 'in a future version' 601 if date is None else ('after %s' % date), instructions) 602 return func(*args, **kwargs) 603 604 doc = _add_deprecated_arg_value_notice_to_docstring( 605 func.__doc__, date, instructions, deprecated_kwargs) 606 return tf_decorator.make_decorator(func, new_func, 'deprecated', doc) 607 608 return deprecated_wrapper 609 610 611def deprecated_argument_lookup(new_name, new_value, old_name, old_value): 612 """Looks up deprecated argument name and ensures both are not used. 613 614 Args: 615 new_name: new name of argument 616 new_value: value of new argument (or None if not used) 617 old_name: old name of argument 618 old_value: value of old argument (or None if not used) 619 Returns: 620 The effective argument that should be used. 621 Raises: 622 ValueError: if new_value and old_value are both non-null 623 """ 624 if old_value is not None: 625 if new_value is not None: 626 raise ValueError("Cannot specify both '%s' and '%s'" % 627 (old_name, new_name)) 628 return old_value 629 return new_value 630 631 632def rewrite_argument_docstring(old_doc, old_argument, new_argument): 633 return old_doc.replace('`%s`' % old_argument, '`%s`' % new_argument).replace( 634 '%s:' % old_argument, '%s:' % new_argument) 635 636 637@tf_contextlib.contextmanager 638def silence(): 639 """Temporarily silence deprecation warnings.""" 640 global _PRINT_DEPRECATION_WARNINGS 641 print_deprecation_warnings = _PRINT_DEPRECATION_WARNINGS 642 _PRINT_DEPRECATION_WARNINGS = False 643 yield 644 _PRINT_DEPRECATION_WARNINGS = print_deprecation_warnings 645 646 647class HiddenTfApiAttribute(property): 648 """Hides a class attribute from the public API. 649 650 Attributes in public classes can be hidden from the API by having an '_' in 651 front of the name (e.g. ClassName._variables). This doesn't work when 652 attributes or methods are inherited from a parent class. To hide inherited 653 attributes, set their values to be `deprecation.hide_attribute_from_api`. 654 For example, this is used in V2 Estimator to hide the deprecated 655 export_savedmodel method: 656 class EstimatorV2(Estimator): 657 export_savedmodel = deprecation.hide_attribute_from_api('...') 658 """ 659 660 def __init__(self, deprecation_message): 661 662 def raise_error(unused_self): 663 raise AttributeError(deprecation_message) 664 665 super(HiddenTfApiAttribute, self).__init__(raise_error) 666 667 668hide_attribute_from_api = HiddenTfApiAttribute # pylint: disable=invalid-name 669 670# TODO(kathywu): Remove once cl/246395236 is submitted. 671HIDDEN_ATTRIBUTE = HiddenTfApiAttribute('This attribute has been deprecated.') 672