Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/accessor.py: 65%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

94 statements  

1""" 

2 

3accessor.py contains base classes for implementing accessor properties 

4that can be mixed into or pinned onto other pandas classes. 

5 

6""" 

7 

8from __future__ import annotations 

9 

10import functools 

11from typing import ( 

12 TYPE_CHECKING, 

13 final, 

14) 

15import warnings 

16 

17from pandas.errors import Pandas4Warning 

18from pandas.util._decorators import ( 

19 set_module, 

20) 

21from pandas.util._exceptions import find_stack_level 

22 

23if TYPE_CHECKING: 

24 from collections.abc import Callable 

25 

26 from pandas._typing import TypeT 

27 

28 from pandas import Index 

29 from pandas.core.generic import NDFrame 

30 

31 

32class DirNamesMixin: 

33 _accessors: set[str] = set() 

34 _hidden_attrs: frozenset[str] = frozenset() 

35 

36 @final 

37 def _dir_deletions(self) -> set[str]: 

38 """ 

39 Delete unwanted __dir__ for this object. 

40 """ 

41 return self._accessors | self._hidden_attrs 

42 

43 def _dir_additions(self) -> set[str]: 

44 """ 

45 Add additional __dir__ for this object. 

46 """ 

47 return {accessor for accessor in self._accessors if hasattr(self, accessor)} 

48 

49 def __dir__(self) -> list[str]: 

50 """ 

51 Provide method name lookup and completion. 

52 

53 Notes 

54 ----- 

55 Only provide 'public' methods. 

56 """ 

57 rv = set(super().__dir__()) 

58 rv = (rv - self._dir_deletions()) | self._dir_additions() 

59 return sorted(rv) 

60 

61 

62class PandasDelegate: 

63 """ 

64 Abstract base class for delegating methods/properties. 

65 """ 

66 

67 def _delegate_property_get(self, name: str, *args, **kwargs): 

68 raise TypeError(f"You cannot access the property {name}") 

69 

70 def _delegate_property_set(self, name: str, value, *args, **kwargs) -> None: 

71 raise TypeError(f"The property {name} cannot be set") 

72 

73 def _delegate_method(self, name: str, *args, **kwargs): 

74 raise TypeError(f"You cannot call method {name}") 

75 

76 @classmethod 

77 def _add_delegate_accessors( 

78 cls, 

79 delegate, 

80 accessors: list[str], 

81 typ: str, 

82 overwrite: bool = False, 

83 accessor_mapping: Callable[[str], str] = lambda x: x, 

84 raise_on_missing: bool = True, 

85 ) -> None: 

86 """ 

87 Add accessors to cls from the delegate class. 

88 

89 Parameters 

90 ---------- 

91 cls 

92 Class to add the methods/properties to. 

93 delegate 

94 Class to get methods/properties and docstrings. 

95 accessors : list of str 

96 List of accessors to add. 

97 typ : {'property', 'method'} 

98 overwrite : bool, default False 

99 Overwrite the method/property in the target class if it exists. 

100 accessor_mapping: Callable, default lambda x: x 

101 Callable to map the delegate's function to the cls' function. 

102 raise_on_missing: bool, default True 

103 Raise if an accessor does not exist on delegate. 

104 False skips the missing accessor. 

105 """ 

106 

107 def _create_delegator_property(name: str): 

108 def _getter(self): 

109 return self._delegate_property_get(name) 

110 

111 def _setter(self, new_values): 

112 return self._delegate_property_set(name, new_values) 

113 

114 _getter.__name__ = name 

115 _setter.__name__ = name 

116 

117 return property( 

118 fget=_getter, 

119 fset=_setter, 

120 doc=getattr(delegate, accessor_mapping(name)).__doc__, 

121 ) 

122 

123 def _create_delegator_method(name: str): 

124 method = getattr(delegate, accessor_mapping(name)) 

125 

126 @functools.wraps(method) 

127 def f(self, *args, **kwargs): 

128 return self._delegate_method(name, *args, **kwargs) 

129 

130 return f 

131 

132 for name in accessors: 

133 if ( 

134 not raise_on_missing 

135 and getattr(delegate, accessor_mapping(name), None) is None 

136 ): 

137 continue 

138 

139 if typ == "property": 

140 f = _create_delegator_property(name) 

141 else: 

142 f = _create_delegator_method(name) 

143 

144 # don't overwrite existing methods/properties 

145 if overwrite or not hasattr(cls, name): 

146 setattr(cls, name, f) 

147 

148 

149def delegate_names( 

150 delegate, 

151 accessors: list[str], 

152 typ: str, 

153 overwrite: bool = False, 

154 accessor_mapping: Callable[[str], str] = lambda x: x, 

155 raise_on_missing: bool = True, 

156): 

157 """ 

158 Add delegated names to a class using a class decorator. This provides 

159 an alternative usage to directly calling `_add_delegate_accessors` 

160 below a class definition. 

161 

162 Parameters 

163 ---------- 

164 delegate : object 

165 The class to get methods/properties & docstrings. 

166 accessors : Sequence[str] 

167 List of accessor to add. 

168 typ : {'property', 'method'} 

169 overwrite : bool, default False 

170 Overwrite the method/property in the target class if it exists. 

171 accessor_mapping: Callable, default lambda x: x 

172 Callable to map the delegate's function to the cls' function. 

173 raise_on_missing: bool, default True 

174 Raise if an accessor does not exist on delegate. 

175 False skips the missing accessor. 

176 

177 Returns 

178 ------- 

179 callable 

180 A class decorator. 

181 

182 Examples 

183 -------- 

184 @delegate_names(Categorical, ["categories", "ordered"], "property") 

185 class CategoricalAccessor(PandasDelegate): 

186 [...] 

187 """ 

188 

189 def add_delegate_accessors(cls): 

190 cls._add_delegate_accessors( 

191 delegate, 

192 accessors, 

193 typ, 

194 overwrite=overwrite, 

195 accessor_mapping=accessor_mapping, 

196 raise_on_missing=raise_on_missing, 

197 ) 

198 return cls 

199 

200 return add_delegate_accessors 

201 

202 

203class Accessor: 

204 """ 

205 Custom property-like object. 

206 

207 A descriptor for accessors. 

208 

209 Parameters 

210 ---------- 

211 name : str 

212 Namespace that will be accessed under, e.g. ``df.foo``. 

213 accessor : cls 

214 Class with the extension methods. 

215 

216 Notes 

217 ----- 

218 For accessor, The class's __init__ method assumes that one of 

219 ``Series``, ``DataFrame`` or ``Index`` as the 

220 single argument ``data``. 

221 """ 

222 

223 def __init__(self, name: str, accessor) -> None: 

224 self._name = name 

225 self._accessor = accessor 

226 

227 def __get__(self, obj, cls): 

228 if obj is None: 

229 # we're accessing the attribute of the class, i.e., Dataset.geo 

230 return self._accessor 

231 return self._accessor(obj) 

232 

233 

234def __getattr__(name: str): 

235 if name == "CachedAccessor": 

236 # Alias kept for downstream libraries 

237 warnings.warn( 

238 f"{name} is deprecated and will be removed in a future version. " 

239 "Use pandas.core.accessor.Accessor instead.", 

240 Pandas4Warning, 

241 stacklevel=find_stack_level(), 

242 ) 

243 return Accessor 

244 

245 raise AttributeError(f"module 'pandas.core.accessor' has no attribute '{name}'") 

246 

247 

248def _register_accessor( 

249 name: str, cls: type[NDFrame | Index] 

250) -> Callable[[TypeT], TypeT]: 

251 """ 

252 Register a custom accessor on objects. 

253 

254 Parameters 

255 ---------- 

256 name : str 

257 Name under which the accessor should be registered. A warning is issued 

258 if this name conflicts with a preexisting attribute. 

259 

260 Returns 

261 ------- 

262 callable 

263 A class decorator. 

264 

265 See Also 

266 -------- 

267 register_dataframe_accessor : Register a custom accessor on DataFrame objects. 

268 register_series_accessor : Register a custom accessor on Series objects. 

269 register_index_accessor : Register a custom accessor on Index objects. 

270 

271 Notes 

272 ----- 

273 This function allows you to register a custom-defined accessor class 

274 for pandas objects (DataFrame, Series, or Index). 

275 The requirements for the accessor class are as follows: 

276 

277 * Must contain an init method that: 

278 

279 * accepts a single object 

280 

281 * raises an AttributeError if the object does not have correctly 

282 matching inputs for the accessor 

283 

284 * Must contain a method for each access pattern. 

285 

286 * The methods should be able to take any argument signature. 

287 

288 * Accessible using the @property decorator if no additional arguments are 

289 needed. 

290 

291 """ 

292 

293 def decorator(accessor: TypeT) -> TypeT: 

294 if hasattr(cls, name): 

295 warnings.warn( 

296 f"registration of accessor {accessor!r} under name " 

297 f"{name!r} for type {cls!r} is overriding a preexisting " 

298 f"attribute with the same name.", 

299 UserWarning, 

300 stacklevel=find_stack_level(), 

301 ) 

302 setattr(cls, name, Accessor(name, accessor)) 

303 cls._accessors.add(name) 

304 return accessor 

305 

306 return decorator 

307 

308 

309@set_module("pandas.api.extensions") 

310def register_dataframe_accessor(name: str) -> Callable[[TypeT], TypeT]: 

311 """ 

312 Register a custom accessor on DataFrame objects. 

313 

314 Use as a decorator to add a custom namespace accessor to DataFrame, 

315 allowing domain-specific methods to be called via ``df.<name>``. 

316 

317 Parameters 

318 ---------- 

319 name : str 

320 Name under which the accessor should be registered. A warning is issued 

321 if this name conflicts with a preexisting attribute. 

322 

323 Returns 

324 ------- 

325 callable 

326 A class decorator. 

327 

328 See Also 

329 -------- 

330 register_dataframe_accessor : Register a custom accessor on DataFrame objects. 

331 register_series_accessor : Register a custom accessor on Series objects. 

332 register_index_accessor : Register a custom accessor on Index objects. 

333 

334 Notes 

335 ----- 

336 This function allows you to register a custom-defined accessor class for DataFrame. 

337 The requirements for the accessor class are as follows: 

338 

339 * Must contain an init method that: 

340 

341 * accepts a single DataFrame object 

342 

343 * raises an AttributeError if the DataFrame object does not have correctly 

344 matching inputs for the accessor 

345 

346 * Must contain a method for each access pattern. 

347 

348 * The methods should be able to take any argument signature. 

349 

350 * Accessible using the @property decorator if no additional arguments are 

351 needed. 

352 

353 Examples 

354 -------- 

355 An accessor that only accepts integers could 

356 have a class defined like this: 

357 

358 >>> @pd.api.extensions.register_dataframe_accessor("int_accessor") 

359 ... class IntAccessor: 

360 ... def __init__(self, pandas_obj): 

361 ... if not all( 

362 ... pandas_obj[col].dtype == "int64" for col in pandas_obj.columns 

363 ... ): 

364 ... raise AttributeError("All columns must contain integer values only") 

365 ... self._obj = pandas_obj 

366 ... 

367 ... def sum(self): 

368 ... return self._obj.sum() 

369 >>> df = pd.DataFrame([[1, 2], ["x", "y"]]) 

370 >>> df.int_accessor 

371 Traceback (most recent call last): 

372 ... 

373 AttributeError: All columns must contain integer values only. 

374 >>> df = pd.DataFrame([[1, 2], [3, 4]]) 

375 >>> df.int_accessor.sum() 

376 0 4 

377 1 6 

378 dtype: int64 

379 """ 

380 from pandas import DataFrame 

381 

382 return _register_accessor(name, DataFrame) 

383 

384 

385@set_module("pandas.api.extensions") 

386def register_series_accessor(name: str) -> Callable[[TypeT], TypeT]: 

387 """ 

388 Register a custom accessor on Series objects. 

389 

390 Use as a decorator to add a custom namespace accessor to Series, 

391 allowing domain-specific methods to be called via ``ser.<name>``. 

392 

393 Parameters 

394 ---------- 

395 name : str 

396 Name under which the accessor should be registered. A warning is issued 

397 if this name conflicts with a preexisting attribute. 

398 

399 Returns 

400 ------- 

401 callable 

402 A class decorator. 

403 

404 See Also 

405 -------- 

406 register_dataframe_accessor : Register a custom accessor on DataFrame objects. 

407 register_series_accessor : Register a custom accessor on Series objects. 

408 register_index_accessor : Register a custom accessor on Index objects. 

409 

410 Notes 

411 ----- 

412 This function allows you to register a custom-defined accessor class for Series. 

413 The requirements for the accessor class are as follows: 

414 

415 * Must contain an init method that: 

416 

417 * accepts a single Series object 

418 

419 * raises an AttributeError if the Series object does not have correctly 

420 matching inputs for the accessor 

421 

422 * Must contain a method for each access pattern. 

423 

424 * The methods should be able to take any argument signature. 

425 

426 * Accessible using the @property decorator if no additional arguments are 

427 needed. 

428 

429 Examples 

430 -------- 

431 An accessor that only accepts integers could 

432 have a class defined like this: 

433 

434 >>> @pd.api.extensions.register_series_accessor("int_accessor") 

435 ... class IntAccessor: 

436 ... def __init__(self, pandas_obj): 

437 ... if not pandas_obj.dtype == "int64": 

438 ... raise AttributeError("The series must contain integer data only") 

439 ... self._obj = pandas_obj 

440 ... 

441 ... def sum(self): 

442 ... return self._obj.sum() 

443 >>> df = pd.Series([1, 2, "x"]) 

444 >>> df.int_accessor 

445 Traceback (most recent call last): 

446 ... 

447 AttributeError: The series must contain integer data only. 

448 >>> df = pd.Series([1, 2, 3]) 

449 >>> df.int_accessor.sum() 

450 6 

451 """ 

452 from pandas import Series 

453 

454 return _register_accessor(name, Series) 

455 

456 

457@set_module("pandas.api.extensions") 

458def register_index_accessor(name: str) -> Callable[[TypeT], TypeT]: 

459 """ 

460 Register a custom accessor on Index objects. 

461 

462 Use as a decorator to add a custom namespace accessor to Index, 

463 allowing domain-specific methods to be called via ``idx.<name>``. 

464 

465 Parameters 

466 ---------- 

467 name : str 

468 Name under which the accessor should be registered. A warning is issued 

469 if this name conflicts with a preexisting attribute. 

470 

471 Returns 

472 ------- 

473 callable 

474 A class decorator. 

475 

476 See Also 

477 -------- 

478 register_dataframe_accessor : Register a custom accessor on DataFrame objects. 

479 register_series_accessor : Register a custom accessor on Series objects. 

480 register_index_accessor : Register a custom accessor on Index objects. 

481 

482 Notes 

483 ----- 

484 This function allows you to register a custom-defined accessor class for Index. 

485 The requirements for the accessor class are as follows: 

486 

487 * Must contain an init method that: 

488 

489 * accepts a single Index object 

490 

491 * raises an AttributeError if the Index object does not have correctly 

492 matching inputs for the accessor 

493 

494 * Must contain a method for each access pattern. 

495 

496 * The methods should be able to take any argument signature. 

497 

498 * Accessible using the @property decorator if no additional arguments are 

499 needed. 

500 

501 Examples 

502 -------- 

503 An accessor that only accepts integers could 

504 have a class defined like this: 

505 

506 >>> @pd.api.extensions.register_index_accessor("int_accessor") 

507 ... class IntAccessor: 

508 ... def __init__(self, pandas_obj): 

509 ... if not all(isinstance(x, int) for x in pandas_obj): 

510 ... raise AttributeError("The index must only be an integer value") 

511 ... self._obj = pandas_obj 

512 ... 

513 ... def even(self): 

514 ... return [x for x in self._obj if x % 2 == 0] 

515 >>> df = pd.DataFrame.from_dict( 

516 ... {"row1": {"1": 1, "2": "a"}, "row2": {"1": 2, "2": "b"}}, orient="index" 

517 ... ) 

518 >>> df.index.int_accessor 

519 Traceback (most recent call last): 

520 ... 

521 AttributeError: The index must only be an integer value. 

522 >>> df = pd.DataFrame( 

523 ... {"col1": [1, 2, 3, 4], "col2": ["a", "b", "c", "d"]}, index=[1, 2, 5, 8] 

524 ... ) 

525 >>> df.index.int_accessor.even() 

526 [2, 8] 

527 """ 

528 from pandas import Index 

529 

530 return _register_accessor(name, Index)