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
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
1"""
3accessor.py contains base classes for implementing accessor properties
4that can be mixed into or pinned onto other pandas classes.
6"""
8from __future__ import annotations
10import functools
11from typing import (
12 TYPE_CHECKING,
13 final,
14)
15import warnings
17from pandas.errors import Pandas4Warning
18from pandas.util._decorators import (
19 set_module,
20)
21from pandas.util._exceptions import find_stack_level
23if TYPE_CHECKING:
24 from collections.abc import Callable
26 from pandas._typing import TypeT
28 from pandas import Index
29 from pandas.core.generic import NDFrame
32class DirNamesMixin:
33 _accessors: set[str] = set()
34 _hidden_attrs: frozenset[str] = frozenset()
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
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)}
49 def __dir__(self) -> list[str]:
50 """
51 Provide method name lookup and completion.
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)
62class PandasDelegate:
63 """
64 Abstract base class for delegating methods/properties.
65 """
67 def _delegate_property_get(self, name: str, *args, **kwargs):
68 raise TypeError(f"You cannot access the property {name}")
70 def _delegate_property_set(self, name: str, value, *args, **kwargs) -> None:
71 raise TypeError(f"The property {name} cannot be set")
73 def _delegate_method(self, name: str, *args, **kwargs):
74 raise TypeError(f"You cannot call method {name}")
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.
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 """
107 def _create_delegator_property(name: str):
108 def _getter(self):
109 return self._delegate_property_get(name)
111 def _setter(self, new_values):
112 return self._delegate_property_set(name, new_values)
114 _getter.__name__ = name
115 _setter.__name__ = name
117 return property(
118 fget=_getter,
119 fset=_setter,
120 doc=getattr(delegate, accessor_mapping(name)).__doc__,
121 )
123 def _create_delegator_method(name: str):
124 method = getattr(delegate, accessor_mapping(name))
126 @functools.wraps(method)
127 def f(self, *args, **kwargs):
128 return self._delegate_method(name, *args, **kwargs)
130 return f
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
139 if typ == "property":
140 f = _create_delegator_property(name)
141 else:
142 f = _create_delegator_method(name)
144 # don't overwrite existing methods/properties
145 if overwrite or not hasattr(cls, name):
146 setattr(cls, name, f)
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.
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.
177 Returns
178 -------
179 callable
180 A class decorator.
182 Examples
183 --------
184 @delegate_names(Categorical, ["categories", "ordered"], "property")
185 class CategoricalAccessor(PandasDelegate):
186 [...]
187 """
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
200 return add_delegate_accessors
203class Accessor:
204 """
205 Custom property-like object.
207 A descriptor for accessors.
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.
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 """
223 def __init__(self, name: str, accessor) -> None:
224 self._name = name
225 self._accessor = accessor
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)
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
245 raise AttributeError(f"module 'pandas.core.accessor' has no attribute '{name}'")
248def _register_accessor(
249 name: str, cls: type[NDFrame | Index]
250) -> Callable[[TypeT], TypeT]:
251 """
252 Register a custom accessor on objects.
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.
260 Returns
261 -------
262 callable
263 A class decorator.
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.
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:
277 * Must contain an init method that:
279 * accepts a single object
281 * raises an AttributeError if the object does not have correctly
282 matching inputs for the accessor
284 * Must contain a method for each access pattern.
286 * The methods should be able to take any argument signature.
288 * Accessible using the @property decorator if no additional arguments are
289 needed.
291 """
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
306 return decorator
309@set_module("pandas.api.extensions")
310def register_dataframe_accessor(name: str) -> Callable[[TypeT], TypeT]:
311 """
312 Register a custom accessor on DataFrame objects.
314 Use as a decorator to add a custom namespace accessor to DataFrame,
315 allowing domain-specific methods to be called via ``df.<name>``.
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.
323 Returns
324 -------
325 callable
326 A class decorator.
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.
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:
339 * Must contain an init method that:
341 * accepts a single DataFrame object
343 * raises an AttributeError if the DataFrame object does not have correctly
344 matching inputs for the accessor
346 * Must contain a method for each access pattern.
348 * The methods should be able to take any argument signature.
350 * Accessible using the @property decorator if no additional arguments are
351 needed.
353 Examples
354 --------
355 An accessor that only accepts integers could
356 have a class defined like this:
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
382 return _register_accessor(name, DataFrame)
385@set_module("pandas.api.extensions")
386def register_series_accessor(name: str) -> Callable[[TypeT], TypeT]:
387 """
388 Register a custom accessor on Series objects.
390 Use as a decorator to add a custom namespace accessor to Series,
391 allowing domain-specific methods to be called via ``ser.<name>``.
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.
399 Returns
400 -------
401 callable
402 A class decorator.
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.
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:
415 * Must contain an init method that:
417 * accepts a single Series object
419 * raises an AttributeError if the Series object does not have correctly
420 matching inputs for the accessor
422 * Must contain a method for each access pattern.
424 * The methods should be able to take any argument signature.
426 * Accessible using the @property decorator if no additional arguments are
427 needed.
429 Examples
430 --------
431 An accessor that only accepts integers could
432 have a class defined like this:
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
454 return _register_accessor(name, Series)
457@set_module("pandas.api.extensions")
458def register_index_accessor(name: str) -> Callable[[TypeT], TypeT]:
459 """
460 Register a custom accessor on Index objects.
462 Use as a decorator to add a custom namespace accessor to Index,
463 allowing domain-specific methods to be called via ``idx.<name>``.
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.
471 Returns
472 -------
473 callable
474 A class decorator.
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.
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:
487 * Must contain an init method that:
489 * accepts a single Index object
491 * raises an AttributeError if the Index object does not have correctly
492 matching inputs for the accessor
494 * Must contain a method for each access pattern.
496 * The methods should be able to take any argument signature.
498 * Accessible using the @property decorator if no additional arguments are
499 needed.
501 Examples
502 --------
503 An accessor that only accepts integers could
504 have a class defined like this:
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
530 return _register_accessor(name, Index)