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