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"""Decorator that provides a warning if the wrapped object is never used."""
16from __future__ import absolute_import
17from __future__ import division
18from __future__ import print_function
19
20import functools
21import types
22
23import six  # pylint: disable=unused-import
24
25from tensorflow.python.eager import context
26from tensorflow.python.util import tf_decorator
27# pylint: enable=g-bad-import-order,g-import-not-at-top
28
29
30# TODO(b/65412899): Re-implement to avoid leaking python objects.
31# This function / class remains since the API is public (mark_used()).
32def _add_should_use_warning(x, fatal_error=False):
33  """Wraps object x so that if it is never used, a warning is logged.
34
35  Does nothing when executing eagerly.
36
37  Args:
38    x: Python object.
39    fatal_error: Python bool.  If `True`, tf.logging.fatal is raised
40      if the returned value is never used.
41
42  Returns:
43    An instance of `TFShouldUseWarningWrapper` which subclasses `type(x)`
44    and is a very shallow wrapper for `x` which logs access into `x`.
45  """
46  del fatal_error
47  if x is None or x == []:  # pylint: disable=g-explicit-bool-comparison
48    return x
49
50  if context.in_eager_mode():
51    # Typically not needed when executing eagerly (the main use case is for ops
52    # which need to be incorporated into the graph), and even the no-op wrapper
53    # creates reference cycles which require garbage collection.
54    return x
55
56  def override_method(method):
57    def fn(self, *args, **kwargs):
58      return method(self, *args, **kwargs)
59    return fn
60
61  class TFShouldUseWarningWrapper(type(x)):
62    """Wrapper for objects that keeps track of their use."""
63
64    def __init__(self, true_self):
65      self.__dict__ = true_self.__dict__
66
67    # Not sure why this pylint warning is being used; this is not an
68    # old class form.
69    # pylint: disable=super-on-old-class
70    def __getattribute__(self, name):
71      return super(TFShouldUseWarningWrapper, self).__getattribute__(name)
72
73    def mark_used(self, *args, **kwargs):
74      return
75
76    # pylint: enable=super-on-old-class
77
78  for name in dir(TFShouldUseWarningWrapper):
79    method = getattr(TFShouldUseWarningWrapper, name)
80    if not isinstance(method, types.FunctionType):
81      continue
82    if name in ('__init__', '__getattribute__', '__del__', 'mark_used'):
83      continue
84    setattr(TFShouldUseWarningWrapper, name,
85            functools.wraps(method)(override_method(method)))
86
87  wrapped = TFShouldUseWarningWrapper(x)
88  wrapped.__doc__ = x.__doc__  # functools.wraps fails on some objects.
89  return wrapped
90
91
92def should_use_result(fn):
93  """Function wrapper that ensures the function's output is used.
94
95  If the output is not used, a `tf.logging.error` is logged.
96
97  An output is marked as used if any of its attributes are read, modified, or
98  updated.  Examples when the output is a `Tensor` include:
99
100  - Using it in any capacity (e.g. `y = t + 0`, `sess.run(t)`)
101  - Accessing a property (e.g. getting `t.name` or `t.op`).
102
103  Note, certain behaviors cannot be tracked - for these the object may not
104  be marked as used.  Examples include:
105
106  - `t != 0`.  In this case, comparison is done on types / ids.
107  - `isinstance(t, tf.Tensor)`.  Similar to above.
108
109  Does nothing when executing eagerly.
110
111  Args:
112    fn: The function to wrap.
113
114  Returns:
115    The wrapped function.
116  """
117  def wrapped(*args, **kwargs):
118    return _add_should_use_warning(fn(*args, **kwargs))
119  return tf_decorator.make_decorator(
120      fn, wrapped, 'should_use_result',
121      ((fn.__doc__ or '') +
122       ('\n\n  '
123        '**NOTE** The output of this function should be used.  If it is not, '
124        'a warning will be logged.  To mark the output as used, '
125        'call its .mark_used() method.')))
126
127
128def must_use_result_or_fatal(fn):
129  """Function wrapper that ensures the function's output is used.
130
131  If the output is not used, a `tf.logging.fatal` error is raised.
132
133  An output is marked as used if any of its attributes are read, modified, or
134  updated.  Examples when the output is a `Tensor` include:
135
136  - Using it in any capacity (e.g. `y = t + 0`, `sess.run(t)`)
137  - Accessing a property (e.g. getting `t.name` or `t.op`).
138
139  Note, certain behaviors cannot be tracked - for these the object may not
140  be marked as used.  Examples include:
141
142  - `t != 0`.  In this case, comparison is done on types / ids.
143  - `isinstance(t, tf.Tensor)`.  Similar to above.
144
145  Does nothing when executing eagerly.
146
147  Args:
148    fn: The function to wrap.
149
150  Returns:
151    The wrapped function.
152  """
153  def wrapped(*args, **kwargs):
154    return _add_should_use_warning(fn(*args, **kwargs), fatal_error=True)
155  return tf_decorator.make_decorator(
156      fn, wrapped, 'must_use_result_or_fatal',
157      ((fn.__doc__ or '') +
158       ('\n\n  '
159        '**NOTE** The output of this function must be used.  If it is not, '
160        'a fatal error will be raised.  To mark the output as used, '
161        'call its .mark_used() method.')))
162