[PATCH 06/21] Add a new utils module

Mathieu Bridon bochecha at fedoraproject.org
Wed May 6 11:53:02 UTC 2015


From: Mathieu Bridon <bochecha at daitauha.fr>

This is going to contain random bits and pieces of utilities.

Currently, it only contains a cached_property class, which is going to
be useful to create properties which are only computed the first time
they are called, but have their return value cached for future calls.
---
 src/pyrpkg/utils.py |  43 ++++++++++++++++++
 test/test_utils.py  | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 166 insertions(+)
 create mode 100644 src/pyrpkg/utils.py
 create mode 100644 test/test_utils.py

diff --git a/src/pyrpkg/utils.py b/src/pyrpkg/utils.py
new file mode 100644
index 0000000..2d16ce0
--- /dev/null
+++ b/src/pyrpkg/utils.py
@@ -0,0 +1,43 @@
+# Copyright (c) 2015 - Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
+# the full text of the license.
+
+
+"""Miscellaneous utilities
+
+This module contains a bunch of utilities used elsewhere in pyrpkg.
+"""
+
+
+class cached_property(property):
+    """A property caching its return value
+
+    This is pretty much the same as a normal Python property, except that the
+    decorated function is called only once. Its return value is then saved,
+    subsequent calls will return it without executing the function any more.
+
+    Example:
+        >>> class Foo(object):
+        ...     @cached_property
+        ...     def bar(self):
+        ...         print("Executing Foo.bar...")
+        ...         return 42
+        ...
+        >>> f = Foo()
+        >>> f.bar
+        Executing Foo.bar...
+        42
+        >>> f.bar
+        42
+    """
+    def __get__(self, inst, type=None):
+        try:
+            return getattr(inst, '_%s' % self.fget.__name__)
+        except AttributeError:
+            v = super(cached_property, self).__get__(inst, type)
+            setattr(inst, '_%s' % self.fget.__name__, v)
+            return v
diff --git a/test/test_utils.py b/test/test_utils.py
new file mode 100644
index 0000000..047dde5
--- /dev/null
+++ b/test/test_utils.py
@@ -0,0 +1,123 @@
+import os
+import sys
+import unittest
+
+old_path = list(sys.path)
+src_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../src')
+sys.path.insert(0, src_path)
+from pyrpkg.utils import cached_property
+sys.path = old_path
+
+
+class CachedPropertyTestCase(unittest.TestCase):
+    def test_computed_only_once(self):
+        class Foo(object):
+            @cached_property
+            def foo(self):
+                runs.append("run once")
+                return 42
+
+        runs = []
+
+        f = Foo()
+        self.assertEqual(len(runs), 0)
+        self.assertEqual(f.foo, 42)
+        self.assertEqual(len(runs), 1)
+        self.assertEqual(f.foo, 42)
+        self.assertEqual(len(runs), 1)
+
+    def test_not_shared_between_properties(self):
+        class Foo(object):
+            @cached_property
+            def foo(self):
+                foo_runs.append("run once")
+                return 42
+
+            @cached_property
+            def bar(self):
+                bar_runs.append("run once")
+                return 43
+
+        foo_runs = []
+        bar_runs = []
+
+        f = Foo()
+        self.assertEqual(len(foo_runs), 0)
+        self.assertEqual(f.foo, 42)
+        self.assertEqual(len(foo_runs), 1)
+        self.assertEqual(f.foo, 42)
+        self.assertEqual(len(foo_runs), 1)
+
+        self.assertEqual(len(bar_runs), 0)
+        self.assertEqual(f.bar, 43)
+        self.assertEqual(len(bar_runs), 1)
+        self.assertEqual(f.bar, 43)
+        self.assertEqual(len(bar_runs), 1)
+
+    def test_not_shared_between_instances(self):
+        class Foo(object):
+            @cached_property
+            def foo(self):
+                foo_runs.append("run once")
+                return 42
+
+        class Bar(object):
+            @cached_property
+            def foo(self):
+                bar_runs.append("run once")
+                return 43
+
+        foo_runs = []
+        bar_runs = []
+
+        f = Foo()
+        self.assertEqual(len(foo_runs), 0)
+        self.assertEqual(f.foo, 42)
+        self.assertEqual(len(foo_runs), 1)
+        self.assertEqual(f.foo, 42)
+        self.assertEqual(len(foo_runs), 1)
+
+        b = Bar()
+        self.assertEqual(len(bar_runs), 0)
+        self.assertEqual(b.foo, 43)
+        self.assertEqual(len(bar_runs), 1)
+        self.assertEqual(b.foo, 43)
+        self.assertEqual(len(bar_runs), 1)
+
+    def test_not_shared_when_inheriting(self):
+        class Foo(object):
+            @cached_property
+            def foo(self):
+                foo_runs.append("run once")
+                return 42
+
+        class Bar(Foo):
+            @cached_property
+            def foo(self):
+                bar_runs.append("run once")
+                return 43
+
+        foo_runs = []
+        bar_runs = []
+
+        b = Bar()
+        self.assertEqual(len(bar_runs), 0)
+        self.assertEqual(b.foo, 43)
+        self.assertEqual(len(bar_runs), 1)
+        self.assertEqual(b.foo, 43)
+        self.assertEqual(len(bar_runs), 1)
+
+        f = Foo()
+        self.assertEqual(len(foo_runs), 0)
+        self.assertEqual(f.foo, 42)
+        self.assertEqual(len(foo_runs), 1)
+        self.assertEqual(f.foo, 42)
+        self.assertEqual(len(foo_runs), 1)
+
+        bar_runs = []
+        b = Bar()
+        self.assertEqual(len(bar_runs), 0)
+        self.assertEqual(b.foo, 43)
+        self.assertEqual(len(bar_runs), 1)
+        self.assertEqual(b.foo, 43)
+        self.assertEqual(len(bar_runs), 1)
-- 
2.1.0



More information about the rel-eng mailing list