Home | History | Annotate | Download | only in util
      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."""
     16 from __future__ import absolute_import
     17 from __future__ import division
     18 from __future__ import print_function
     19 
     20 import functools
     21 import types
     22 
     23 import six  # pylint: disable=unused-import
     24 
     25 from tensorflow.python.eager import context
     26 from 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()).
     32 def _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 
     92 def 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 
    128 def 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