• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 copy
21import sys
22import traceback
23
24import six  # pylint: disable=unused-import
25
26from tensorflow.python.framework import ops
27from tensorflow.python.platform import tf_logging
28from tensorflow.python.util import tf_decorator
29# pylint: enable=g-bad-import-order,g-import-not-at-top
30
31
32class _TFShouldUseHelper(object):
33  """Object stored in TFShouldUse-wrapped objects.
34
35  When it is deleted it will emit a warning or error if its `sate` method
36  has not been called by time of deletion, and Tensorflow is not executing
37  eagerly outside of functions.
38  """
39
40  def __init__(self, type_, repr_, stack_frame, fatal_error_if_unsated):
41    self._type = type_
42    self._repr = repr_
43    self._stack_frame = stack_frame
44    self._fatal_error_if_unsated = fatal_error_if_unsated
45    self._sated = False
46
47  def sate(self):
48    self._sated = True
49    self._type = None
50    self._repr = None
51    self._stack_frame = None
52    self._logging_module = None
53
54  def __del__(self):
55    if ops.executing_eagerly_outside_functions():
56      return
57    if self._sated:
58      return
59    if self._fatal_error_if_unsated:
60      logger = tf_logging.fatal
61    else:
62      logger = tf_logging.error
63    creation_stack = ''.join(
64        [line.rstrip() for line in traceback.format_stack(self._stack_frame)])
65    logger(
66        '==================================\n'
67        'Object was never used (type %s):\n%s\nIf you want to mark it as '
68        'used call its "mark_used()" method.\nIt was originally created '
69        'here:\n%s\n'
70        '==================================' %
71        (self._type, self._repr, creation_stack))
72
73
74def _new__init__(self, true_value, tf_should_use_helper):
75  # pylint: disable=protected-access
76  self._tf_should_use_helper = tf_should_use_helper
77  self._true_value = true_value
78
79
80def _new__setattr__(self, key, value):
81  if key in ('_tf_should_use_helper', '_true_value'):
82    return object.__setattr__(self, key, value)
83  return setattr(
84      object.__getattribute__(self, '_true_value'),
85      key, value)
86
87
88def _new__getattribute__(self, key):
89  if key not in ('_tf_should_use_helper', '_true_value'):
90    object.__getattribute__(self, '_tf_should_use_helper').sate()
91  if key in ('_tf_should_use_helper', 'mark_used', '__setatt__'):
92    return object.__getattribute__(self, key)
93  return getattr(object.__getattribute__(self, '_true_value'), key)
94
95
96def _new_mark_used(self, *args, **kwargs):
97  object.__getattribute__(self, '_tf_should_use_helper').sate()
98  try:
99    mu = object.__getattribute__(
100        object.__getattribute__(self, '_true_value'),
101        'mark_used')
102    return mu(*args, **kwargs)
103  except AttributeError:
104    pass
105
106
107_WRAPPERS = dict()
108
109
110def _get_wrapper(x, tf_should_use_helper):
111  """Create a wrapper for object x, whose class subclasses type(x).
112
113  The wrapper will emit a warning if it is deleted without any of its
114  properties being accessed or methods being called.
115
116  Args:
117    x: The instance to wrap.
118    tf_should_use_helper: The object that tracks usage.
119
120  Returns:
121    An object wrapping `x`, of type `type(x)`.
122  """
123  type_x = type(x)
124  memoized = _WRAPPERS.get(type_x, None)
125  if memoized:
126    return memoized(x, tf_should_use_helper)
127
128  tx = copy.deepcopy(type_x)
129  copy_tx = type(tx.__name__, tx.__bases__, dict(tx.__dict__))
130  copy_tx.__init__ = _new__init__
131  copy_tx.__getattribute__ = _new__getattribute__
132  copy_tx.mark_used = _new_mark_used
133  copy_tx.__setattr__ = _new__setattr__
134  _WRAPPERS[type_x] = copy_tx
135
136  return copy_tx(x, tf_should_use_helper)
137
138
139def _add_should_use_warning(x, fatal_error=False):
140  """Wraps object x so that if it is never used, a warning is logged.
141
142  Args:
143    x: Python object.
144    fatal_error: Python bool.  If `True`, tf.logging.fatal is raised
145      if the returned value is never used.
146
147  Returns:
148    An instance of `TFShouldUseWarningWrapper` which subclasses `type(x)`
149    and is a very shallow wrapper for `x` which logs access into `x`.
150  """
151  if x is None or x == []:  # pylint: disable=g-explicit-bool-comparison
152    return x
153
154  # Extract the current frame for later use by traceback printing.
155  try:
156    raise ValueError()
157  except ValueError:
158    stack_frame = sys.exc_info()[2].tb_frame.f_back
159
160  tf_should_use_helper = _TFShouldUseHelper(
161      type_=type(x),
162      repr_=repr(x),
163      stack_frame=stack_frame,
164      fatal_error_if_unsated=fatal_error)
165
166  return _get_wrapper(x, tf_should_use_helper)
167
168
169def should_use_result(fn):
170  """Function wrapper that ensures the function's output is used.
171
172  If the output is not used, a `tf.logging.error` is logged.
173
174  An output is marked as used if any of its attributes are read, modified, or
175  updated.  Examples when the output is a `Tensor` include:
176
177  - Using it in any capacity (e.g. `y = t + 0`, `sess.run(t)`)
178  - Accessing a property (e.g. getting `t.name` or `t.op`).
179
180  Note, certain behaviors cannot be tracked - for these the object may not
181  be marked as used.  Examples include:
182
183  - `t != 0`.  In this case, comparison is done on types / ids.
184  - `isinstance(t, tf.Tensor)`.  Similar to above.
185
186  Args:
187    fn: The function to wrap.
188
189  Returns:
190    The wrapped function.
191  """
192  def wrapped(*args, **kwargs):
193    return _add_should_use_warning(fn(*args, **kwargs))
194  return tf_decorator.make_decorator(
195      fn, wrapped, 'should_use_result',
196      ((fn.__doc__ or '') +
197       ('\n\n  '
198        '**NOTE** The output of this function should be used.  If it is not, '
199        'a warning will be logged.  To mark the output as used, '
200        'call its .mark_used() method.')))
201
202
203def must_use_result_or_fatal(fn):
204  """Function wrapper that ensures the function's output is used.
205
206  If the output is not used, a `tf.logging.fatal` error is raised.
207
208  An output is marked as used if any of its attributes are read, modified, or
209  updated.  Examples when the output is a `Tensor` include:
210
211  - Using it in any capacity (e.g. `y = t + 0`, `sess.run(t)`)
212  - Accessing a property (e.g. getting `t.name` or `t.op`).
213
214  Note, certain behaviors cannot be tracked - for these the object may not
215  be marked as used.  Examples include:
216
217  - `t != 0`.  In this case, comparison is done on types / ids.
218  - `isinstance(t, tf.Tensor)`.  Similar to above.
219
220  Args:
221    fn: The function to wrap.
222
223  Returns:
224    The wrapped function.
225  """
226  def wrapped(*args, **kwargs):
227    return _add_should_use_warning(fn(*args, **kwargs), fatal_error=True)
228  return tf_decorator.make_decorator(
229      fn, wrapped, 'must_use_result_or_fatal',
230      ((fn.__doc__ or '') +
231       ('\n\n  '
232        '**NOTE** The output of this function must be used.  If it is not, '
233        'a fatal error will be raised.  To mark the output as used, '
234        'call its .mark_used() method.')))
235