Conditions | 24 |
Total Lines | 164 |
Code Lines | 88 |
Lines | 0 |
Ratio | 0 % |
Changes | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like solph._energy_system.EnergySystem.__init__() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.
There are several approaches to avoid long parameter lists:
1 | # -*- coding: utf-8 -*- |
||
86 | def __init__( |
||
87 | self, |
||
88 | timeindex=None, |
||
89 | timeincrement=None, |
||
90 | infer_last_interval=None, |
||
91 | periods=None, |
||
92 | tsa_parameters=None, |
||
93 | use_remaining_value=False, |
||
94 | groupings=None, |
||
95 | ): |
||
96 | # Doing imports at runtime is generally frowned upon, but should work |
||
97 | # for now. See the TODO in :func:`constraint_grouping |
||
98 | # <oemof.solph.groupings.constraint_grouping>` for more information. |
||
99 | from oemof.solph import GROUPINGS |
||
100 | |||
101 | if groupings is None: |
||
102 | groupings = [] |
||
103 | groupings = GROUPINGS + groupings |
||
104 | |||
105 | if not ( |
||
106 | isinstance(timeindex, pd.DatetimeIndex) |
||
107 | or isinstance(timeindex, type(None)) |
||
108 | ): |
||
109 | msg = ( |
||
110 | "Parameter 'timeindex' has to be of type " |
||
111 | "pandas.DatetimeIndex or NoneType and not of type {0}" |
||
112 | ) |
||
113 | raise TypeError(msg.format(type(timeindex))) |
||
114 | |||
115 | if infer_last_interval is None and timeindex is not None: |
||
116 | msg = ( |
||
117 | "The default behaviour will change in future versions.\n" |
||
118 | "At the moment the last interval of an equidistant time " |
||
119 | "index is added implicitly by default. Set " |
||
120 | "'infer_last_interval' explicitly 'True' or 'False' to avoid " |
||
121 | "this warning. In future versions 'False' will be the default" |
||
122 | "behaviour" |
||
123 | ) |
||
124 | warnings.warn(msg, FutureWarning) |
||
125 | infer_last_interval = True |
||
126 | |||
127 | if infer_last_interval is True and timeindex is not None: |
||
128 | # Add one time interval to the timeindex by adding one time point. |
||
129 | if timeindex.freq is None: |
||
130 | msg = ( |
||
131 | "You cannot infer the last interval if the 'freq' " |
||
132 | "attribute of your DatetimeIndex is None. Set " |
||
133 | " 'infer_last_interval=False' or specify a DatetimeIndex " |
||
134 | "with a valid frequency." |
||
135 | ) |
||
136 | raise AttributeError(msg) |
||
137 | |||
138 | timeindex = timeindex.union( |
||
139 | pd.date_range( |
||
140 | timeindex[-1] + timeindex.freq, |
||
141 | periods=1, |
||
142 | freq=timeindex.freq, |
||
143 | ) |
||
144 | ) |
||
145 | |||
146 | # catch wrong combinations and infer timeincrement from timeindex. |
||
147 | if timeincrement is not None and timeindex is not None: |
||
148 | if periods is None: |
||
149 | msg = ( |
||
150 | "Specifying the timeincrement and the timeindex parameter " |
||
151 | "at the same time is not allowed since these might be " |
||
152 | "conflicting to each other." |
||
153 | ) |
||
154 | raise AttributeError(msg) |
||
155 | else: |
||
156 | msg = ( |
||
157 | "Ensure that your timeindex and timeincrement are " |
||
158 | "consistent." |
||
159 | ) |
||
160 | warnings.warn(msg, debugging.ExperimentalFeatureWarning) |
||
161 | |||
162 | elif timeindex is not None and timeincrement is None: |
||
163 | if tsa_parameters is not None: |
||
164 | pass |
||
165 | else: |
||
166 | df = pd.DataFrame(timeindex) |
||
167 | timedelta = df.diff() |
||
168 | timeincrement = timedelta / np.timedelta64(1, "h") |
||
169 | |||
170 | # we want a series (squeeze) |
||
171 | # without the first item (no delta defined for first entry) |
||
172 | # but starting with index 0 (reset) |
||
173 | timeincrement = timeincrement.squeeze()[1:].reset_index( |
||
174 | drop=True |
||
175 | ) |
||
176 | |||
177 | if timeincrement is not None and (pd.Series(timeincrement) <= 0).any(): |
||
178 | msg = ( |
||
179 | "The time increment is inconsistent. Negative values and zero " |
||
180 | "are not allowed.\nThis is caused by a inconsistent " |
||
181 | "timeincrement parameter or an incorrect timeindex." |
||
182 | ) |
||
183 | raise TypeError(msg) |
||
184 | if tsa_parameters is not None: |
||
185 | msg = ( |
||
186 | "CAUTION! You specified the 'tsa_parameters' attribute for " |
||
187 | "your energy system.\n This will lead to setting up " |
||
188 | "energysystem with aggregated timeseries. " |
||
189 | "Storages and flows will be adapted accordingly.\n" |
||
190 | "Please be aware that the feature is experimental as of " |
||
191 | "now. If you find anything suspicious or any bugs, " |
||
192 | "please report them." |
||
193 | ) |
||
194 | warnings.warn(msg, debugging.SuspiciousUsageWarning) |
||
195 | |||
196 | if isinstance(tsa_parameters, dict): |
||
197 | # Set up tsa_parameters for single period: |
||
198 | tsa_parameters = [tsa_parameters] |
||
199 | |||
200 | # Construct occurrences of typical periods |
||
201 | if periods is not None: |
||
202 | for p in range(len(periods)): |
||
203 | tsa_parameters[p]["occurrences"] = collections.Counter( |
||
204 | tsa_parameters[p]["order"] |
||
205 | ) |
||
206 | else: |
||
207 | tsa_parameters[0]["occurrences"] = collections.Counter( |
||
208 | tsa_parameters[0]["order"] |
||
209 | ) |
||
210 | |||
211 | # If segmentation is used, timesteps is set to number of |
||
212 | # segmentations per period. |
||
213 | # Otherwise, default timesteps_per_period is used. |
||
214 | for params in tsa_parameters: |
||
215 | if "segments" in params: |
||
216 | params["timesteps"] = int( |
||
217 | len(params["segments"]) / len(params["occurrences"]) |
||
218 | ) |
||
219 | else: |
||
220 | params["timesteps"] = params["timesteps_per_period"] |
||
221 | self.tsa_parameters = tsa_parameters |
||
222 | |||
223 | timeincrement = self._init_timeincrement( |
||
224 | timeincrement, timeindex, periods, tsa_parameters |
||
225 | ) |
||
226 | super().__init__( |
||
227 | groupings=groupings, |
||
228 | timeindex=timeindex, |
||
229 | timeincrement=timeincrement, |
||
230 | ) |
||
231 | |||
232 | self.periods = periods |
||
233 | if self.periods is not None: |
||
234 | msg = ( |
||
235 | "CAUTION! You specified the 'periods' attribute for your " |
||
236 | "energy system.\n This will lead to creating " |
||
237 | "a multi-period optimization modeling which can be " |
||
238 | "used e.g. for long-term investment modeling.\n" |
||
239 | "Please be aware that the feature is experimental as of " |
||
240 | "now. If you find anything suspicious or any bugs, " |
||
241 | "please report them." |
||
242 | ) |
||
243 | warnings.warn(msg, debugging.ExperimentalFeatureWarning) |
||
244 | self._extract_periods_years() |
||
245 | self._extract_periods_matrix() |
||
246 | self._extract_end_year_of_optimization() |
||
247 | self.use_remaining_value = use_remaining_value |
||
248 | else: |
||
249 | self.end_year_of_optimization = 1 |
||
250 | |||
357 |