[autotest] Clean up cached property implementation

Use the non-data descriptor behavior of looking up instance attributes
before class attributes to cache the value.  This save an extra dict
and the danger of overwriting the instances _property_cache if it is
already using it, and a few extra attribute lookups when accessed.

This is a pretty standard cached property template.

BUG=None
TEST=Run unittests

Change-Id: I16cabcfa942baca673da80ccb92b297a64e9aca0
Reviewed-on: https://chromium-review.googlesource.com/503548
Commit-Ready: Allen Li <ayatane@chromium.org>
Tested-by: Allen Li <ayatane@chromium.org>
Reviewed-by: Xixuan Wu <xixuan@chromium.org>
diff --git a/client/common_lib/decorators.py b/client/common_lib/decorators.py
index b7db793..e09feb5 100644
--- a/client/common_lib/decorators.py
+++ b/client/common_lib/decorators.py
@@ -59,16 +59,17 @@
     return wrap
 
 
-class _Property(object):
-    def __init__(self, func):
-        self._func = func
+class _CachedProperty(object):
 
-    def __get__(self, obj, type=None):
-        if not hasattr(obj, '_property_cache'):
-            obj._property_cache = {}
-        if self._func not in obj._property_cache:
-            obj._property_cache[self._func] = self._func(obj)
-        return obj._property_cache[self._func]
+
+    def __init__(self, func, name=None):
+        self._func = func
+        self._name = name if name is not None else func.__name__
+
+    def __get__(self, instance, owner):
+        value = self._func(instance)
+        setattr(instance, self._name, value)
+        return value
 
 
 def cached_property(func):
@@ -80,7 +81,7 @@
     @param func: The function to calculate the property value.
     @returns: An object that abides by the descriptor protocol.
     """
-    return _Property(func)
+    return _CachedProperty(func)
 
 
 def test_module_available(module, raise_error=False):