2
2
3
3
from collections import UserList , defaultdict , abc
4
4
from copy import deepcopy
5
+ from functools import cached_property
5
6
from matplotlib .pyplot import figure
6
7
import numpy as np
7
8
import pandas as pd
@@ -23,7 +24,6 @@ class SimResult(UserList):
23
24
__slots__ = ['times' , 'data' ] # Optimization
24
25
25
26
def __init__ (self , times : list = None , data : list = None , _copy = True ):
26
- self ._frame = None
27
27
if times is None or data is None :
28
28
self .times = []
29
29
self .data = []
@@ -60,27 +60,24 @@ def iterrows(self):
60
60
"""
61
61
return super ().__iter__ ()
62
62
63
- @property
63
+ @cached_property
64
64
def frame (self ) -> pd .DataFrame :
65
65
"""
66
66
.. versionadded:: 1.5.0
67
67
68
68
pd.DataFrame: A pandas DataFrame representing the SimResult data
69
69
"""
70
- warn_once ('frame will be deprecated after version 1.5 of ProgPy.' , DeprecationWarning , stacklevel = 2 )
71
- if self ._frame is None :
72
- if len (self .data ) > 0 : #
73
- self ._frame = pd .concat ([
74
- pd .DataFrame (dict (dframe ), index = [0 ]) for dframe in self .data
75
- ], ignore_index = True , axis = 0 )
76
- else :
77
- self ._frame = pd .DataFrame ()
78
- if self .times is not None :
79
- self ._frame .insert (0 , "time" , self .times )
80
- self ._frame = self ._frame .set_index ('time' )
81
- return self ._frame
70
+ # warn_once('frame will be deprecated after version 1.5 of ProgPy.', DeprecationWarning, stacklevel=2)
71
+ if len (self .data ) > 0 :
72
+ frame = pd .concat ([
73
+ pd .DataFrame (dict (dframe ), index = [0 ]) for dframe in self .data
74
+ ], ignore_index = True , axis = 0 )
82
75
else :
83
- return self ._frame
76
+ frame = pd .DataFrame ()
77
+ if self .times is not None :
78
+ frame .insert (0 , "time" , self .times )
79
+ frame = frame .set_index ('time' )
80
+ return frame
84
81
85
82
def frame_is_empty (self ) -> bool :
86
83
"""
@@ -89,33 +86,33 @@ def frame_is_empty(self) -> bool:
89
86
Returns:
90
87
bool: If the value has been calculated
91
88
"""
92
- return self ._frame .empty
89
+ return self .frame .empty
93
90
94
91
def __setitem__ (self , key , value ):
95
92
"""
96
93
in addition to the normal functionality, updates the _frame if it exists
97
94
"""
98
95
super ().__setitem__ (key , value )
99
- if self ._frame is not None :
96
+ if 'frame' in self .__dict__ : # Has been calculated
100
97
for col in value :
101
- self ._frame .at [key , col ] = value [col ]
98
+ self .__dict__ [ 'frame' ] .at [key , col ] = value [col ]
102
99
103
100
def __delitem__ (self , key ):
104
101
"""
105
102
in addition to the normal functionality, updates the _frame if it exists
106
103
"""
107
104
super ().__delitem__ (key )
108
- if self . _frame is not None :
109
- self ._frame = self . _frame . drop ([key ])
105
+ if 'frame' in self . __dict__ :
106
+ self .__dict__ [ 'frame' ]. drop ([key ], inplace = True )
110
107
111
108
def insert (self , i : int , item ) -> None :
112
109
"""
113
110
in addition to the normal functionality, updates the _frame if it exists
114
111
"""
115
112
self .insert (i , item )
116
- if self . _frame is not None :
113
+ if 'frame' in self . __dict__ :
117
114
for value in item :
118
- self ._frame .insert (i , column = [value ], value = item [value ])
115
+ self .__dict__ [ 'frame' ] .insert (i , column = [value ], value = item [value ])
119
116
120
117
@property
121
118
def iloc (self ):
@@ -197,8 +194,8 @@ def extend(self, other: "SimResult") -> None:
197
194
self .data .extend (other .data )
198
195
else :
199
196
raise ValueError (f"ValueError: Argument must be of type { self .__class__ } " )
200
- if self . _frame is not None :
201
- self ._frame = None
197
+ if 'frame' in self . __dict__ :
198
+ del self .__dict__ [ 'frame' ]
202
199
203
200
def pop_by_index (self , index : int = - 1 ) -> dict :
204
201
"""Remove and return an element
@@ -210,8 +207,8 @@ def pop_by_index(self, index: int = -1) -> dict:
210
207
dict: Element Removed
211
208
"""
212
209
self .times .pop (index )
213
- if self . _frame is not None :
214
- self ._frame = self . _frame . drop ([self ._frame .index .values [index ]])
210
+ if 'frame' in self . __dict__ :
211
+ self .__dict__ [ 'frame' ]. drop ([self .__dict__ [ 'frame' ] .index .values [index ]], inplace = True )
215
212
return self .data .pop (index )
216
213
217
214
def pop (self , index : int = - 1 ) -> dict :
@@ -248,7 +245,7 @@ def clear(self) -> None:
248
245
"""Clear the SimResult"""
249
246
self .times = []
250
247
self .data = []
251
- self ._frame = None
248
+ del self .__dict__ [ 'frame' ]
252
249
253
250
def time (self , index : int ) -> float :
254
251
"""Get time for data point at index `index`
@@ -364,7 +361,6 @@ def __init__(self, fcn: abc.Callable, times: list = None, states: list = None, _
364
361
data (array(dict)): Data points where data[n] corresponds to times[n]
365
362
"""
366
363
self .fcn = fcn
367
- self .__data = None
368
364
if times is None or states is None :
369
365
self .times = []
370
366
self .states = []
@@ -383,14 +379,14 @@ def is_cached(self) -> bool:
383
379
Returns:
384
380
bool: If the value has been calculated
385
381
"""
386
- return self . __data is not None
382
+ return 'data' in self . __dict__
387
383
388
384
def clear (self ) -> None :
389
385
"""
390
386
Clears the times, states, and data cache for a LazySimResult object
391
387
"""
392
388
self .times = []
393
- self .__data = None
389
+ del self .__dict__ [ 'data' ]
394
390
self .states = []
395
391
396
392
def extend (self , other : "LazySimResult" , _copy = True ) -> None :
@@ -409,10 +405,13 @@ def extend(self, other: "LazySimResult", _copy=True) -> None:
409
405
self .states .extend (deepcopy (other .states ))
410
406
else :
411
407
self .states .extend (other .states )
412
- if self .__data is None or not other .is_cached ():
413
- self .__data = None
414
- else :
415
- self .__data .extend (other .data )
408
+ if 'data' in self .__dict__ : # self is cached
409
+ if not other .is_cached ():
410
+ # Either are not cached
411
+ if 'data' in self .__dict__ :
412
+ del self .__dict__ ['data' ]
413
+ else :
414
+ self .__dict__ ['data' ].extend (other .data )
416
415
elif (isinstance (other , SimResult )):
417
416
raise ValueError (
418
417
f"ValueError: { self .__class__ } cannot be extended by SimResult. First convert to SimResult using to_simresult() method." )
@@ -430,8 +429,8 @@ def pop(self, index: int = -1) -> dict:
430
429
"""
431
430
self .times .pop (index )
432
431
x = self .states .pop (index )
433
- if self .__data is not None :
434
- return self .__data .pop (index )
432
+ if 'data' in self .__dict__ : # is cached
433
+ return self .__dict__ [ 'data' ] .pop (index )
435
434
return self .fcn (x )
436
435
437
436
def remove (self , d : float = None , t : float = None , s = None ) -> None :
@@ -457,16 +456,12 @@ def remove(self, d: float = None, t: float = None, s=None) -> None:
457
456
def to_simresult (self ) -> SimResult :
458
457
return SimResult (self .times , self .data )
459
458
460
- @property
459
+ @cached_property
461
460
def data (self ) -> List [dict ]:
462
461
"""
463
462
Get the data (elements of list). Only calculated on first request
464
463
465
464
Returns:
466
465
array(dict): data
467
466
"""
468
- if self .__data is None :
469
- self .__data = [self .fcn (x ) for x in self .states ]
470
- return self .__data
471
-
472
-
467
+ return [self .fcn (x ) for x in self .states ]
0 commit comments