Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
14 changes: 8 additions & 6 deletions src/docx/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,17 @@ 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. Checked by key
# --- presence rather than by comparing the stored value to None, since a
# --- getter that legitimately computes None on first access would
# --- otherwise look exactly like one that hasn't been evaluated yet, and
# --- get re-evaluated on every later access instead of cached.
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
50 changes: 49 additions & 1 deletion tests/test_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,18 @@
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 +130,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` objects."""

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

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

widget = Widget()

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

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

@lazyproperty
def value(self):
self.call_count += 1
return "some result"

widget = Widget()

assert widget.value == "some result"
assert widget.value == "some result"
assert widget.call_count == 1