Skip to content

Commit

Permalink
delay attribute computation
Browse files Browse the repository at this point in the history
Using a non-data descriptor (https://docs.python.org/3/howto/descriptor.html#descriptor-protocol)
The attribute is not writeable because the Cosmology dataclass is frozen.

Signed-off-by: nstarman <nstarman@users.noreply.github.com>
  • Loading branch information
nstarman committed Jul 10, 2024
1 parent 00f2bba commit 04bc2fd
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 217 deletions.
57 changes: 56 additions & 1 deletion astropy/cosmology/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from collections.abc import Callable
from dataclasses import Field
from numbers import Number
from typing import TYPE_CHECKING, Any, TypeVar
from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload

import numpy as np

Expand All @@ -20,8 +20,11 @@

if TYPE_CHECKING:
from astropy.cosmology import Parameter
from astropy.cosmology.core import Cosmology


_F = TypeVar("_F", bound=Callable[..., Any])
R = TypeVar("R")


def vectorize_redshift_method(func=None, nin=1):
Expand Down Expand Up @@ -140,3 +143,55 @@ def _depr_kws(func: _F, /, kws: tuple[str, ...], since: str) -> _F:
wrapper = _depr_kws_wrap(func, kws, since)
functools.update_wrapper(wrapper, func)
return wrapper


class CachedInDictPropertyDescriptor(Generic[R]):
"""Descriptor for a property that is cached in the instance's dictionary.
Note that this is a non-data descriptor, not NOT a data descriptor, so
after the property is accessed and cached with the same key as the
property, the instance's dictionary will have the property value
directly, and the descriptor will not be called again, until the
property is deleted from the instance's dictionary. See
https://docs.python.org/3/howto/descriptor.html#descriptor-protocol.
"""

# __slots__ = ("fget", "name") # TODO: when __doc__ is supported by __slots__

def __init__(self, fget: Callable[[Cosmology], R]) -> None:
self.fget = fget
self.__doc__ = fget.__doc__

def __set_name__(self, cosmo_cls: type[Cosmology], name: str) -> None:
self.name: str = name

@overload
def __get__(
self, cosmo: None, cosmo_cls: Any
) -> CachedInDictPropertyDescriptor[R]: ...

@overload
def __get__(self, cosmo: Cosmology, cosmo_cls: Any) -> R: ...

def __get__(
self, cosmo: Cosmology | None, cosmo_cls: type[Cosmology] | None
) -> R | CachedInDictPropertyDescriptor[R]:
# Accessed from the class, return the descriptor itself
if cosmo is None:
return self

# If the property is not in the instance's dictionary, calculate and store it.
if self.name not in cosmo.__dict__:
cosmo.__dict__[self.name] = self.fget(cosmo)

# Return the property value from the instance's dictionary
# This is only called once, thereafter the property value is accessed directly
# from the instance's dictionary.
return cosmo.__dict__[self.name]


def cached_on_dict_property(
fget: Callable[[Cosmology], R],
) -> CachedInDictPropertyDescriptor[R]:
"""Descriptor for a property that is cached in the instance's dictionary."""
return CachedInDictPropertyDescriptor(fget)
Loading

0 comments on commit 04bc2fd

Please sign in to comment.