Skip to content

Commit e6d0c35

Browse files
committed
cached_property replaces CachedReadOnlyProperty
This is a Python 3.8 feature.
1 parent 6f03560 commit e6d0c35

File tree

3 files changed

+75
-78
lines changed

3 files changed

+75
-78
lines changed

stagpy/_helpers.py

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
"""Various helper functions and classes."""
22

33
from __future__ import annotations
4+
5+
import typing
46
from inspect import getdoc
5-
from typing import TYPE_CHECKING, Generic, TypeVar
67

78
import matplotlib.pyplot as plt
89

910
from . import conf
1011

11-
if TYPE_CHECKING:
12-
from typing import Optional, Any, Callable, NoReturn
12+
if typing.TYPE_CHECKING:
13+
from typing import Optional, Any
1314
from matplotlib.figure import Figure
1415
from numpy import ndarray
1516

@@ -103,42 +104,3 @@ def find_in_sorted_arr(value: Any, array: ndarray, after: bool = False) -> int:
103104
if not after and array[ielt] != value and ielt > 0:
104105
ielt -= 1
105106
return ielt
106-
107-
108-
T = TypeVar('T')
109-
V = TypeVar('V')
110-
111-
112-
class CachedReadOnlyProperty(Generic[T, V]):
113-
"""Descriptor implementation of read-only cached properties.
114-
115-
Properties are cached as ``_cropped_{name}`` instance attribute.
116-
117-
This is preferable to using a combination of ``@property`` and
118-
``@functools.lru_cache`` since the cache is bound to instances and
119-
therefore get GCd with the instance when the latter is no longer in use
120-
instead of staying in the cache which would use the instance itself as its
121-
key.
122-
123-
This also has an advantage over ``@cached_property`` (Python>3.8): the
124-
property is read-only instead of being writeable.
125-
"""
126-
127-
def __init__(self, thunk: Callable[[T], V]):
128-
self._thunk = thunk
129-
self._name = thunk.__name__
130-
self._cache_name = f'_cropped_{self._name}'
131-
self.__doc__ = thunk.__doc__
132-
133-
def __get__(self, instance: T, _: Any) -> V:
134-
try:
135-
return getattr(instance, self._cache_name)
136-
except AttributeError:
137-
pass
138-
cached_value = self._thunk(instance)
139-
setattr(instance, self._cache_name, cached_value)
140-
return cached_value
141-
142-
def __set__(self, instance: T, _: Any) -> NoReturn:
143-
raise AttributeError(
144-
f'Cannot set {self._name} property of {instance!r}')

stagpy/_step.py

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88

99
from __future__ import annotations
1010
from collections import abc
11+
from functools import cached_property
1112
from itertools import chain
1213
import typing
1314

1415
import numpy as np
1516

1617
from . import error, phyvars, stagyyparsers
17-
from ._helpers import CachedReadOnlyProperty as crop
1818
from .datatypes import Field, Rprof, Varr
1919

2020
if typing.TYPE_CHECKING:
@@ -48,31 +48,42 @@ def _scale_radius_mo(self, radius: ndarray) -> ndarray:
4848
radius + self._header['mo_lambda'])
4949
return radius
5050

51-
@crop
51+
@cached_property
5252
def nttot(self) -> int:
5353
"""Number of grid point along the x/theta direction."""
5454
return self._shape['ntot'][0]
5555

56-
@crop
56+
@cached_property
5757
def nptot(self) -> int:
5858
"""Number of grid point along the y/phi direction."""
5959
return self._shape['ntot'][1]
6060

61-
@crop
61+
@cached_property
6262
def nrtot(self) -> int:
6363
"""Number of grid point along the z/r direction."""
6464
return self._shape['ntot'][2]
6565

66-
@crop
66+
@cached_property
6767
def nbtot(self) -> int:
6868
"""Number of blocks."""
6969
return self._shape['ntot'][3]
7070

71-
nxtot = nttot
72-
nytot = nptot
73-
nztot = nrtot
71+
@property
72+
def nxtot(self) -> int:
73+
"""Same as nttot."""
74+
return self.nttot
75+
76+
@property
77+
def nytot(self) -> int:
78+
"""Same as nptot."""
79+
return self.nptot
80+
81+
@property
82+
def nztot(self) -> int:
83+
"""Same as nrtot."""
84+
return self.nrtot
7485

75-
@crop
86+
@cached_property
7687
def r_walls(self) -> ndarray:
7788
"""Position of FV walls along the z/r direction."""
7889
rgeom = self._header.get("rgeom")
@@ -83,7 +94,7 @@ def r_walls(self) -> ndarray:
8394
walls = np.append(walls, self._step.rprofs.bounds[1])
8495
return self._scale_radius_mo(walls)
8596

86-
@crop
97+
@cached_property
8798
def r_centers(self) -> ndarray:
8899
"""Position of FV centers along the z/r direction."""
89100
rgeom = self._header.get("rgeom")
@@ -93,7 +104,7 @@ def r_centers(self) -> ndarray:
93104
walls = self._step.rprofs.centers
94105
return self._scale_radius_mo(walls)
95106

96-
@crop
107+
@cached_property
97108
def t_walls(self) -> ndarray:
98109
"""Position of FV walls along x/theta."""
99110
if self.threed or self.twod_xz:
@@ -113,12 +124,12 @@ def t_walls(self) -> ndarray:
113124
d_t = (self.p_walls[1] - self.p_walls[0]) / 2
114125
return np.array([center - d_t, center + d_t])
115126

116-
@crop
127+
@cached_property
117128
def t_centers(self) -> ndarray:
118129
"""Position of FV centers along x/theta."""
119130
return (self.t_walls[:-1] + self.t_walls[1:]) / 2
120131

121-
@crop
132+
@cached_property
122133
def p_walls(self) -> ndarray:
123134
"""Position of FV walls along y/phi."""
124135
if self.threed or self.twod_yz:
@@ -136,17 +147,40 @@ def p_walls(self) -> ndarray:
136147
d_p = (self.t_walls[1] - self.t_walls[0]) / 2
137148
return np.array([-d_p, d_p])
138149

139-
@crop
150+
@cached_property
140151
def p_centers(self) -> ndarray:
141152
"""Position of FV centers along y/phi."""
142153
return (self.p_walls[:-1] + self.p_walls[1:]) / 2
143154

144-
z_walls = r_walls
145-
z_centers = r_centers
146-
x_walls = t_walls
147-
x_centers = t_centers
148-
y_walls = p_walls
149-
y_centers = p_centers
155+
@property
156+
def z_walls(self) -> ndarray:
157+
"""Same as r_walls."""
158+
return self.r_walls
159+
160+
@property
161+
def z_centers(self) -> ndarray:
162+
"""Same as r_centers."""
163+
return self.r_centers
164+
165+
@property
166+
def x_walls(self) -> ndarray:
167+
"""Same as t_walls."""
168+
return self.t_walls
169+
170+
@property
171+
def x_centers(self) -> ndarray:
172+
"""Same as t_centers."""
173+
return self.t_centers
174+
175+
@property
176+
def y_walls(self) -> ndarray:
177+
"""Same as p_walls."""
178+
return self.p_walls
179+
180+
@property
181+
def y_centers(self) -> ndarray:
182+
"""Same as p_centers."""
183+
return self.p_centers
150184

151185
def _init_shape(self) -> None:
152186
"""Determine shape of geometry."""
@@ -160,7 +194,7 @@ def _init_shape(self) -> None:
160194
self._shape['axi'] = self.cartesian and self.twod_xz and \
161195
shape == 'axisymmetric'
162196

163-
@crop
197+
@cached_property
164198
def rcmb(self) -> float:
165199
"""Radius of CMB, 0 in cartesian geometry."""
166200
return max(self._header["rcmb"], 0)
@@ -272,7 +306,7 @@ def __getitem__(self, name: str) -> Field:
272306
self._set(fld_name, fld)
273307
return self._data[name]
274308

275-
@crop
309+
@cached_property
276310
def _present_fields(self) -> List[str]:
277311
return [fld for fld in chain(self._vars, self._extra)
278312
if fld in self]
@@ -341,7 +375,7 @@ def __delitem__(self, name: str) -> None:
341375
if name in self._data:
342376
del self._data[name]
343377

344-
@crop
378+
@cached_property
345379
def _header(self) -> Optional[Dict[str, Any]]:
346380
if self.step.isnap is None:
347381
return None
@@ -354,7 +388,7 @@ def _header(self) -> Optional[Dict[str, Any]]:
354388
header = stagyyparsers.read_geom_h5(xmf, self.step.isnap)[0]
355389
return header if header else None
356390

357-
@crop
391+
@cached_property
358392
def geom(self) -> _Geometry:
359393
"""Geometry information.
360394
@@ -425,7 +459,7 @@ def __init__(self, step: Step):
425459
self.step = step
426460
self._cached_extra: Dict[str, Rprof] = {}
427461

428-
@crop
462+
@cached_property
429463
def _data(self) -> Optional[DataFrame]:
430464
step = self.step
431465
return step.sdat._rprof_and_times[0].get(step.istep)
@@ -464,12 +498,12 @@ def stepstr(self) -> str:
464498
"""String representation of the parent :class:`Step`."""
465499
return str(self.step.istep)
466500

467-
@crop
501+
@cached_property
468502
def centers(self) -> ndarray:
469503
"""Radial position of cell centers."""
470504
return self._rprofs['r'].values + self.bounds[0]
471505

472-
@crop
506+
@cached_property
473507
def walls(self) -> ndarray:
474508
"""Radial position of cell walls."""
475509
rbot, rtop = self.bounds
@@ -484,7 +518,7 @@ def walls(self) -> ndarray:
484518
walls = np.append(walls, rtop)
485519
return walls
486520

487-
@crop
521+
@cached_property
488522
def bounds(self) -> Tuple[float, float]:
489523
"""Radial or vertical position of boundaries.
490524

stagpy/stagyydata.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
"""
99

1010
from __future__ import annotations
11+
1112
from collections import abc
1213
from dataclasses import dataclass, field
14+
from functools import cached_property
1315
from itertools import zip_longest
1416
from pathlib import Path
1517
import re
@@ -18,7 +20,6 @@
1820
import numpy as np
1921

2022
from . import conf, error, parfile, phyvars, stagyyparsers, _helpers, _step
21-
from ._helpers import CachedReadOnlyProperty as crop
2223
from ._step import Step
2324
from .datatypes import Rprof, Tseries, Vart
2425

@@ -71,7 +72,7 @@ class _Scales:
7172
def __init__(self, sdat: StagyyData):
7273
self._sdat = sdat
7374

74-
@crop
75+
@cached_property
7576
def length(self) -> float:
7677
"""Length in m."""
7778
thick = self._sdat.par['geometry']['d_dimensional']
@@ -159,7 +160,7 @@ class _Refstate:
159160
def __init__(self, sdat: StagyyData):
160161
self._sdat = sdat
161162

162-
@crop
163+
@cached_property
163164
def _data(self) -> Tuple[List[List[DataFrame]], List[DataFrame]]:
164165
"""Read reference state profile."""
165166
reffile = self._sdat.filename('refstat.dat')
@@ -225,7 +226,7 @@ def __init__(self, sdat: StagyyData):
225226
self.sdat = sdat
226227
self._cached_extra: Dict[str, Tseries] = {}
227228

228-
@crop
229+
@cached_property
229230
def _data(self) -> Optional[DataFrame]:
230231
timefile = self.sdat.filename('TimeSeries.h5')
231232
data = stagyyparsers.time_series_h5(
@@ -639,7 +640,7 @@ def rprofs_averaged(self) -> _RprofsAveraged:
639640
self._rprofs_averaged = _RprofsAveraged(self)
640641
return self._rprofs_averaged
641642

642-
@crop
643+
@cached_property
643644
def stepstr(self) -> str:
644645
"""String representation of the requested set of steps."""
645646
items = []
@@ -772,7 +773,7 @@ def parpath(self) -> Path:
772773
"""Path of par file."""
773774
return self._parpath
774775

775-
@crop
776+
@cached_property
776777
def hdf5(self) -> Optional[Path]:
777778
"""Path of output hdf5 folder if relevant, None otherwise."""
778779
h5_folder = self.path / self.par['ioin']['hdf5_output_folder']
@@ -787,7 +788,7 @@ def par(self) -> Namelist:
787788
"""
788789
return self._par
789790

790-
@crop
791+
@cached_property
791792
def _rprof_and_times(
792793
self
793794
) -> Tuple[Dict[int, DataFrame], Optional[DataFrame]]:
@@ -806,7 +807,7 @@ def rtimes(self) -> DataFrame:
806807
"""Radial profiles times."""
807808
return self._rprof_and_times[1]
808809

809-
@crop
810+
@cached_property
810811
def _files(self) -> Set[Path]:
811812
"""Set of found binary files output by StagYY."""
812813
out_stem = Path(self.par['ioin']['output_file_stem'] + '_')

0 commit comments

Comments
 (0)