diff --git a/src/pptx/util.py b/src/pptx/util.py index fdec79298..0ddceff92 100644 --- a/src/pptx/util.py +++ b/src/pptx/util.py @@ -183,14 +183,12 @@ def __get__(self, obj: Any, type: Any = None) -> _T: # --- when accessed on instance, start by checking instance __dict__ for # --- item with key matching the wrapped function's name - value = obj.__dict__.get(self._name) - if value is None: + if self._name not in obj.__dict__: # --- on first access, the __dict__ item will be absent. Evaluate fget() # --- and store that value in the (otherwise unused) host-object # --- __dict__ value of same name ('fget' nominally) - value = self._fget(obj) - obj.__dict__[self._name] = value - return cast(_T, value) + obj.__dict__[self._name] = self._fget(obj) + return cast(_T, obj.__dict__[self._name]) def __set__(self, obj: Any, value: Any) -> None: """Raises unconditionally, to preserve read-only behavior. diff --git a/tests/test_util.py b/tests/test_util.py index 97e46fa4c..99c6361c7 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -4,7 +4,41 @@ import pytest -from pptx.util import Centipoints, Cm, Emu, Inches, Length, Mm, Pt +from pptx.util import Centipoints, Cm, Emu, Inches, Length, Mm, Pt, lazyproperty + + +class DescribeLazyproperty(object): + def it_only_calls_the_decorated_method_once(self): + class Obj(object): + def __init__(self): + self.call_count = 0 + + @lazyproperty + def fget(self): + self.call_count += 1 + return self.call_count + + obj = Obj() + + assert obj.fget == 1 + assert obj.fget == 1 + assert obj.call_count == 1 + + def it_caches_a_None_return_value_too(self): + class Obj(object): + def __init__(self): + self.call_count = 0 + + @lazyproperty + def fget(self): + self.call_count += 1 + return None + + obj = Obj() + + assert obj.fget is None + assert obj.fget is None + assert obj.call_count == 1 class DescribeLength(object):