22
33from collections import UserList , defaultdict , abc
44from copy import deepcopy
5+ from functools import cached_property
56from matplotlib .pyplot import figure
67import numpy as np
78import pandas as pd
@@ -23,7 +24,6 @@ class SimResult(UserList):
2324 __slots__ = ['times' , 'data' ] # Optimization
2425
2526 def __init__ (self , times : list = None , data : list = None , _copy = True ):
26- self ._frame = None
2727 if times is None or data is None :
2828 self .times = []
2929 self .data = []
@@ -60,27 +60,24 @@ def iterrows(self):
6060 """
6161 return super ().__iter__ ()
6262
63- @property
63+ @cached_property
6464 def frame (self ) -> pd .DataFrame :
6565 """
6666 .. versionadded:: 1.5.0
6767
6868 pd.DataFrame: A pandas DataFrame representing the SimResult data
6969 """
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 )
8275 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
8481
8582 def frame_is_empty (self ) -> bool :
8683 """
@@ -89,33 +86,33 @@ def frame_is_empty(self) -> bool:
8986 Returns:
9087 bool: If the value has been calculated
9188 """
92- return self ._frame .empty
89+ return self .frame .empty
9390
9491 def __setitem__ (self , key , value ):
9592 """
9693 in addition to the normal functionality, updates the _frame if it exists
9794 """
9895 super ().__setitem__ (key , value )
99- if self ._frame is not None :
96+ if 'frame' in self .__dict__ : # Has been calculated
10097 for col in value :
101- self ._frame .at [key , col ] = value [col ]
98+ self .__dict__ [ 'frame' ] .at [key , col ] = value [col ]
10299
103100 def __delitem__ (self , key ):
104101 """
105102 in addition to the normal functionality, updates the _frame if it exists
106103 """
107104 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 )
110107
111108 def insert (self , i : int , item ) -> None :
112109 """
113110 in addition to the normal functionality, updates the _frame if it exists
114111 """
115112 self .insert (i , item )
116- if self . _frame is not None :
113+ if 'frame' in self . __dict__ :
117114 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 ])
119116
120117 @property
121118 def iloc (self ):
@@ -197,8 +194,8 @@ def extend(self, other: "SimResult") -> None:
197194 self .data .extend (other .data )
198195 else :
199196 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' ]
202199
203200 def pop_by_index (self , index : int = - 1 ) -> dict :
204201 """Remove and return an element
@@ -210,8 +207,8 @@ def pop_by_index(self, index: int = -1) -> dict:
210207 dict: Element Removed
211208 """
212209 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 )
215212 return self .data .pop (index )
216213
217214 def pop (self , index : int = - 1 ) -> dict :
@@ -248,7 +245,7 @@ def clear(self) -> None:
248245 """Clear the SimResult"""
249246 self .times = []
250247 self .data = []
251- self ._frame = None
248+ del self .__dict__ [ 'frame' ]
252249
253250 def time (self , index : int ) -> float :
254251 """Get time for data point at index `index`
@@ -364,7 +361,6 @@ def __init__(self, fcn: abc.Callable, times: list = None, states: list = None, _
364361 data (array(dict)): Data points where data[n] corresponds to times[n]
365362 """
366363 self .fcn = fcn
367- self .__data = None
368364 if times is None or states is None :
369365 self .times = []
370366 self .states = []
@@ -383,14 +379,14 @@ def is_cached(self) -> bool:
383379 Returns:
384380 bool: If the value has been calculated
385381 """
386- return self . __data is not None
382+ return 'data' in self . __dict__
387383
388384 def clear (self ) -> None :
389385 """
390386 Clears the times, states, and data cache for a LazySimResult object
391387 """
392388 self .times = []
393- self .__data = None
389+ del self .__dict__ [ 'data' ]
394390 self .states = []
395391
396392 def extend (self , other : "LazySimResult" , _copy = True ) -> None :
@@ -409,10 +405,13 @@ def extend(self, other: "LazySimResult", _copy=True) -> None:
409405 self .states .extend (deepcopy (other .states ))
410406 else :
411407 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 )
416415 elif (isinstance (other , SimResult )):
417416 raise ValueError (
418417 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:
430429 """
431430 self .times .pop (index )
432431 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 )
435434 return self .fcn (x )
436435
437436 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:
457456 def to_simresult (self ) -> SimResult :
458457 return SimResult (self .times , self .data )
459458
460- @property
459+ @cached_property
461460 def data (self ) -> List [dict ]:
462461 """
463462 Get the data (elements of list). Only calculated on first request
464463
465464 Returns:
466465 array(dict): data
467466 """
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