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