• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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