message: Remove unused cache_recursive_attribute
diff: @@ -49,99 +49,6 @@ def has_weights(obj):
  return has_weight and not isinstance(obj, type)
  -def cache_recursive_attribute(key):
  - """Decorator to cache Layer properties which recursively depend on sub-layers.
  -
  - A number of attributes in Keras Layers take the form:
  -
  - ```
  - @property
  - def thing(self):
  - return self._thing or any(layer.thing for layer in self.layers)
  - ```
  -
  - This means that checking these properties (e.g. dynamic, stateful, etc) must
  - traverse the entire graph of layers to determine whether any descent has
  - changed its state. This decorator adds a mechanism for Layers and trackable
  - data structures to broadcast mutations (including the addition or deletion
  - of layers) and allows the top level layer to safely cache results. In general,
  - if computing an attribute triggers a depth first search it is a good candidate
  - for this caching mechanism.
  -
  - The architecture is optimized for safety and correctness rather than absolute
  - optimality. This manifests in two ways:
  - 1) Parents are never removed. It is possible for layer A to depend on layer
  - B but subsequently remove that dependency. In that case, layer B will
  - continue to broadcast its mutations to layer A until either A or B is
  - deleted. However because the only effect is to invalidate a cache this
  - does not affect correctness. (And robustly removing dependencies is
  - difficult and error prone.)
  -
  - 2) Layers aggressively invalidate their caches when there is any ambiguity
  - of whether or not it is necessary. For instance, consider the following:
  - ```
  - class MyLayer(tf.keras.layers.Layer):
  - def __init__(self):
  - super(MyLayer, self).__init__()
  -
  - sub_layer = tf.keras.layers.Dense(1)
  - self.sub_layers = [
  - sub_layer # This will be picked up, converted to a ListWrapper,
  - # and added to self._layers
  - ]
  -
  - # Include the layer twice.
  - self.sub_layers.append(sub_layer)
  -
  - # Remove one copy, but one copy remains.
  - self.sub_layers.pop()
  - ```
  - In the example layer above, the set of tracked layers actually doesn't
  - change; however to know that in the general case the Layer needs
  - significant machinery to reason about what, if anything, has changed.
  - By invalidating on every mutation we don't need to concern ourselves
  - with the many types of mutations (append, pop, in-place replacement)
  - and their specific semantics.
  -
  - Because mutations to layers are expected to be infrequent, this very
  - conservative approach captures the vast majority of the performance gains from
  - caching recursive properties while still remaining quite lightweight and easy
  - to reason about.
  -
  - `tracking.cached_per_instance` provides a more detailed performance analysis
  - of the WeakKeyDictionary cache pattern.
  -
  - Args:
  - key: A string indicating which field is being cached. While not strictly
  - necessary (since it could be obtained from f.__name__), it forces
  - deliberate behavior when caching an attribute.
  -
  - Returns:
  - A caching decorater specialized to `key`.
  - """
  - cache = weakref.WeakKeyDictionary()
  - def outer(f):
  - """Attribute cache which has been specialized."""
  -
  - @functools.wraps(f)
  - def wrapped(self):
  - """Cache aware version of `f`."""
  -
  - # Sentinels are unique per Layer/Trackable, but can be hashed. (Unlike
  - # some trackable data structures.) Consequently it makes sense to use the
  - # sentinel as a cache key rather than `self`.
  - sentinel = getattr(self, "_attribute_sentinel") # type: AttributeSentinel
  -
  - if not sentinel.get(key) or sentinel not in cache:
  - cache[sentinel] = f(self)
  - sentinel.mark_cached(key)
  - output = cache[sentinel]
  - return output
  -
  - return wrapped
  - return outer
  -
  -
  def invalidate_recursive_cache(key):
  """Convenience decorator to invalidate the cache when setting attributes."""
  def outer(f):
  