Visitar URL original
Fix lazyproperty re-evaluating getters that return None by jipeng6036-del · Pull Request #1626 · python-openxml/python-docx · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions src/docx/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,14 @@ def __get__(self, obj: Any, type: Any = None) -> T:
return self # type: ignore

# --- 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:
# --- item with key matching the wrapped function's name. Presence, not
# --- the value, is the "already computed" signal so a cached None is kept.
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.
Expand Down
39 changes: 38 additions & 1 deletion tests/test_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest

from docx.opc.part import XmlPart
from docx.shared import Cm, ElementProxy, Emu, Inches, Length, Mm, Pt, RGBColor, Twips
from docx.shared import Cm, ElementProxy, Emu, Inches, Length, Mm, Pt, RGBColor, Twips, lazyproperty

from .unitutil.cxml import element
from .unitutil.mock import FixtureRequest, Mock, instance_mock
Expand Down Expand Up @@ -119,3 +119,40 @@ def it_can_provide_a_hex_string_rgb_value(self):
def it_has_a_custom_repr(self):
rgb_color = RGBColor(0x42, 0xF0, 0xBA)
assert repr(rgb_color) == "RGBColor(0x42, 0xf0, 0xba)"


class DescribeLazyproperty:
"""Unit-test suite for `docx.shared.lazyproperty`."""

def it_evaluates_the_getter_only_once_when_the_value_is_none(self):
class Widget:
def __init__(self):
self.call_count = 0

@lazyproperty
def maybe_missing(self):
self.call_count += 1
return None

widget = Widget()

assert widget.maybe_missing is None
assert widget.maybe_missing is None
assert widget.maybe_missing is None
assert widget.call_count == 1

def it_evaluates_the_getter_only_once_when_the_value_is_not_none(self):
class Widget:
def __init__(self):
self.call_count = 0

@lazyproperty
def child(self):
self.call_count += 1
return "ok"

widget = Widget()

assert widget.child == "ok"
assert widget.child == "ok"
assert widget.call_count == 1