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 textwrap 23import traceback 24import types 25 26from tensorflow.python.eager import context 27from tensorflow.python.framework import ops 28from tensorflow.python.platform import tf_logging 29from tensorflow.python.util import tf_decorator 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 or inside a tf.function (which use autodeps and resolve the 38 main issues this wrapper warns about). 39 """ 40 41 def __init__(self, type_, repr_, stack_frame, error_in_function, 42 warn_in_eager): 43 self._type = type_ 44 self._repr = repr_ 45 self._stack_frame = stack_frame 46 self._error_in_function = error_in_function 47 if context.executing_eagerly(): 48 # If warn_in_eager, sated == False. Otherwise true. 49 self._sated = not warn_in_eager 50 elif ops.inside_function(): 51 if error_in_function: 52 self._sated = False 53 ops.add_exit_callback_to_default_func_graph( 54 lambda: self._check_sated(raise_error=True)) 55 else: 56 self._sated = True 57 else: 58 # TF1 graph building mode 59 self._sated = False 60 61 def sate(self): 62 self._sated = True 63 self._type = None 64 self._repr = None 65 self._stack_frame = None 66 self._logging_module = None 67 68 def _check_sated(self, raise_error): 69 """Check if the object has been sated.""" 70 if self._sated: 71 return 72 creation_stack = ''.join( 73 [line.rstrip() 74 for line in traceback.format_stack(self._stack_frame, limit=5)]) 75 if raise_error: 76 try: 77 raise RuntimeError( 78 'Object was never used (type {}): {}. If you want to mark it as ' 79 'used call its "mark_used()" method. It was originally created ' 80 'here:\n{}'.format(self._type, self._repr, creation_stack)) 81 finally: 82 self.sate() 83 else: 84 tf_logging.error( 85 '==================================\n' 86 'Object was never used (type {}):\n{}\nIf you want to mark it as ' 87 'used call its "mark_used()" method.\nIt was originally created ' 88 'here:\n{}\n' 89 '==================================' 90 .format(self._type, self._repr, creation_stack)) 91 92 def __del__(self): 93 self._check_sated(raise_error=False) 94 95 96def _new__init__(self, wrapped_value, tf_should_use_helper): 97 # pylint: disable=protected-access 98 self._tf_should_use_helper = tf_should_use_helper 99 self._tf_should_use_wrapped_value = wrapped_value 100 101 102def _new__setattr__(self, key, value): 103 if key in ('_tf_should_use_helper', '_tf_should_use_wrapped_value'): 104 return object.__setattr__(self, key, value) 105 return setattr( 106 object.__getattribute__(self, '_tf_should_use_wrapped_value'), 107 key, value) 108 109 110def _new__getattribute__(self, key): 111 if key not in ('_tf_should_use_helper', '_tf_should_use_wrapped_value'): 112 object.__getattribute__(self, '_tf_should_use_helper').sate() 113 if key in ('_tf_should_use_helper', 'mark_used', '__setatt__'): 114 return object.__getattribute__(self, key) 115 return getattr( 116 object.__getattribute__(self, '_tf_should_use_wrapped_value'), key) 117 118 119def _new_mark_used(self, *args, **kwargs): 120 object.__getattribute__(self, '_tf_should_use_helper').sate() 121 try: 122 mu = object.__getattribute__( 123 object.__getattribute__(self, '_tf_should_use_wrapped_value'), 124 'mark_used') 125 return mu(*args, **kwargs) 126 except AttributeError: 127 pass 128 129 130_WRAPPERS = {} 131 132 133def _get_wrapper(x, tf_should_use_helper): 134 """Create a wrapper for object x, whose class subclasses type(x). 135 136 The wrapper will emit a warning if it is deleted without any of its 137 properties being accessed or methods being called. 138 139 Args: 140 x: The instance to wrap. 141 tf_should_use_helper: The object that tracks usage. 142 143 Returns: 144 An object wrapping `x`, of type `type(x)`. 145 """ 146 type_x = type(x) 147 memoized = _WRAPPERS.get(type_x, None) 148 if memoized: 149 return memoized(x, tf_should_use_helper) 150 151 tx = copy.deepcopy(type_x) 152 # Prefer using __orig_bases__, which preserve generic type arguments. 153 bases = getattr(tx, '__orig_bases__', tx.__bases__) 154 155 # Use types.new_class when available, which is preferred over plain type in 156 # some distributions. 157 if sys.version_info >= (3, 5): 158 def set_body(ns): 159 ns.update(tx.__dict__) 160 return ns 161 162 copy_tx = types.new_class(tx.__name__, bases, exec_body=set_body) 163 else: 164 copy_tx = type(tx.__name__, bases, dict(tx.__dict__)) 165 166 copy_tx.__init__ = _new__init__ 167 copy_tx.__getattribute__ = _new__getattribute__ 168 copy_tx.mark_used = _new_mark_used 169 copy_tx.__setattr__ = _new__setattr__ 170 _WRAPPERS[type_x] = copy_tx 171 172 return copy_tx(x, tf_should_use_helper) 173 174 175def _add_should_use_warning(x, error_in_function=False, warn_in_eager=False): 176 """Wraps object x so that if it is never used, a warning is logged. 177 178 Args: 179 x: Python object. 180 error_in_function: Python bool. If `True`, a `RuntimeError` is raised 181 if the returned value is never used when created during `tf.function` 182 tracing. 183 warn_in_eager: Python bool. If `True` raise warning if in Eager mode as well 184 as graph mode. 185 186 Returns: 187 An instance of `TFShouldUseWarningWrapper` which subclasses `type(x)` 188 and is a very shallow wrapper for `x` which logs access into `x`. 189 """ 190 if x is None or (isinstance(x, list) and not x): 191 return x 192 193 if context.executing_eagerly() and not warn_in_eager: 194 return x 195 196 if ops.inside_function() and not error_in_function: 197 # We don't currently log warnings in tf.function calls, so just skip it. 198 return x 199 200 # Extract the current frame for later use by traceback printing. 201 try: 202 raise ValueError() 203 except ValueError: 204 stack_frame = sys.exc_info()[2].tb_frame.f_back 205 206 tf_should_use_helper = _TFShouldUseHelper( 207 type_=type(x), 208 repr_=repr(x), 209 stack_frame=stack_frame, 210 error_in_function=error_in_function, 211 warn_in_eager=warn_in_eager) 212 213 return _get_wrapper(x, tf_should_use_helper) 214 215 216def should_use_result(fn=None, warn_in_eager=False, error_in_function=False): 217 """Function wrapper that ensures the function's output is used. 218 219 If the output is not used, a `logging.error` is logged. If 220 `error_in_function` is set, then a `RuntimeError` will be raised at the 221 end of function tracing if the output is not used by that point. 222 223 An output is marked as used if any of its attributes are read, modified, or 224 updated. Examples when the output is a `Tensor` include: 225 226 - Using it in any capacity (e.g. `y = t + 0`, `sess.run(t)`) 227 - Accessing a property (e.g. getting `t.name` or `t.op`). 228 - Calling `t.mark_used()`. 229 230 Note, certain behaviors cannot be tracked - for these the object may not 231 be marked as used. Examples include: 232 233 - `t != 0`. In this case, comparison is done on types / ids. 234 - `isinstance(t, tf.Tensor)`. Similar to above. 235 236 Args: 237 fn: The function to wrap. 238 warn_in_eager: Whether to create warnings in Eager as well. 239 error_in_function: Whether to raise an error when creating a tf.function. 240 241 Returns: 242 The wrapped function. 243 """ 244 def decorated(fn): 245 """Decorates the input function.""" 246 def wrapped(*args, **kwargs): 247 return _add_should_use_warning(fn(*args, **kwargs), 248 warn_in_eager=warn_in_eager, 249 error_in_function=error_in_function) 250 fn_doc = fn.__doc__ or '' 251 split_doc = fn_doc.split('\n', 1) 252 if len(split_doc) == 1: 253 updated_doc = fn_doc 254 else: 255 brief, rest = split_doc 256 updated_doc = '\n'.join([brief, textwrap.dedent(rest)]) 257 258 note = ('\n\nNote: The output of this function should be used. If it is ' 259 'not, a warning will be logged or an error may be raised. ' 260 'To mark the output as used, call its .mark_used() method.') 261 return tf_decorator.make_decorator( 262 target=fn, 263 decorator_func=wrapped, 264 decorator_name='should_use_result', 265 decorator_doc=updated_doc + note) 266 267 if fn is not None: 268 return decorated(fn) 269 else: 270 return decorated 271