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