Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pandas/core/accessor.py : 57%

Hot-keys on this page
r m x p 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"""
7from typing import FrozenSet, Set
8import warnings
10from pandas.util._decorators import Appender
13class DirNamesMixin:
14 _accessors: Set[str] = set()
15 _deprecations: FrozenSet[str] = frozenset()
17 def _dir_deletions(self):
18 """
19 Delete unwanted __dir__ for this object.
20 """
21 return self._accessors | self._deprecations
23 def _dir_additions(self):
24 """
25 Add additional __dir__ for this object.
26 """
27 rv = set()
28 for accessor in self._accessors:
29 try:
30 getattr(self, accessor)
31 rv.add(accessor)
32 except AttributeError:
33 pass
34 return rv
36 def __dir__(self):
37 """
38 Provide method name lookup and completion.
40 Notes
41 -----
42 Only provide 'public' methods.
43 """
44 rv = set(dir(type(self)))
45 rv = (rv - self._dir_deletions()) | self._dir_additions()
46 return sorted(rv)
49class PandasDelegate:
50 """
51 Abstract base class for delegating methods/properties.
52 """
54 def _delegate_property_get(self, name, *args, **kwargs):
55 raise TypeError(f"You cannot access the property {name}")
57 def _delegate_property_set(self, name, value, *args, **kwargs):
58 raise TypeError(f"The property {name} cannot be set")
60 def _delegate_method(self, name, *args, **kwargs):
61 raise TypeError(f"You cannot call method {name}")
63 @classmethod
64 def _add_delegate_accessors(
65 cls, delegate, accessors, typ: str, overwrite: bool = False
66 ):
67 """
68 Add accessors to cls from the delegate class.
70 Parameters
71 ----------
72 cls
73 Class to add the methods/properties to.
74 delegate
75 Class to get methods/properties and doc-strings.
76 accessors : list of str
77 List of accessors to add.
78 typ : {'property', 'method'}
79 overwrite : bool, default False
80 Overwrite the method/property in the target class if it exists.
81 """
83 def _create_delegator_property(name):
84 def _getter(self):
85 return self._delegate_property_get(name)
87 def _setter(self, new_values):
88 return self._delegate_property_set(name, new_values)
90 _getter.__name__ = name
91 _setter.__name__ = name
93 return property(
94 fget=_getter, fset=_setter, doc=getattr(delegate, name).__doc__
95 )
97 def _create_delegator_method(name):
98 def f(self, *args, **kwargs):
99 return self._delegate_method(name, *args, **kwargs)
101 f.__name__ = name
102 f.__doc__ = getattr(delegate, name).__doc__
104 return f
106 for name in accessors:
108 if typ == "property":
109 f = _create_delegator_property(name)
110 else:
111 f = _create_delegator_method(name)
113 # don't overwrite existing methods/properties
114 if overwrite or not hasattr(cls, name):
115 setattr(cls, name, f)
118def delegate_names(delegate, accessors, typ: str, overwrite: bool = False):
119 """
120 Add delegated names to a class using a class decorator. This provides
121 an alternative usage to directly calling `_add_delegate_accessors`
122 below a class definition.
124 Parameters
125 ----------
126 delegate : object
127 The class to get methods/properties & doc-strings.
128 accessors : Sequence[str]
129 List of accessor to add.
130 typ : {'property', 'method'}
131 overwrite : bool, default False
132 Overwrite the method/property in the target class if it exists.
134 Returns
135 -------
136 callable
137 A class decorator.
139 Examples
140 --------
141 @delegate_names(Categorical, ["categories", "ordered"], "property")
142 class CategoricalAccessor(PandasDelegate):
143 [...]
144 """
146 def add_delegate_accessors(cls):
147 cls._add_delegate_accessors(delegate, accessors, typ, overwrite=overwrite)
148 return cls
150 return add_delegate_accessors
153# Ported with modifications from xarray
154# https://github.com/pydata/xarray/blob/master/xarray/core/extensions.py
155# 1. We don't need to catch and re-raise AttributeErrors as RuntimeErrors
156# 2. We use a UserWarning instead of a custom Warning
159class CachedAccessor:
160 """
161 Custom property-like object.
163 A descriptor for caching accessors.
165 Parameters
166 ----------
167 name : str
168 Namespace that will be accessed under, e.g. ``df.foo``.
169 accessor : cls
170 Class with the extension methods.
172 Notes
173 -----
174 For accessor, The class's __init__ method assumes that one of
175 ``Series``, ``DataFrame`` or ``Index`` as the
176 single argument ``data``.
177 """
179 def __init__(self, name: str, accessor) -> None:
180 self._name = name
181 self._accessor = accessor
183 def __get__(self, obj, cls):
184 if obj is None:
185 # we're accessing the attribute of the class, i.e., Dataset.geo
186 return self._accessor
187 accessor_obj = self._accessor(obj)
188 # Replace the property with the accessor object. Inspired by:
189 # http://www.pydanny.com/cached-property.html
190 # We need to use object.__setattr__ because we overwrite __setattr__ on
191 # NDFrame
192 object.__setattr__(obj, self._name, accessor_obj)
193 return accessor_obj
196def _register_accessor(name, cls):
197 def decorator(accessor):
198 if hasattr(cls, name):
199 warnings.warn(
200 f"registration of accessor {repr(accessor)} under name "
201 f"{repr(name)} for type {repr(cls)} is overriding a preexisting"
202 f"attribute with the same name.",
203 UserWarning,
204 stacklevel=2,
205 )
206 setattr(cls, name, CachedAccessor(name, accessor))
207 cls._accessors.add(name)
208 return accessor
210 return decorator
213_doc = """
214Register a custom accessor on %(klass)s objects.
216Parameters
217----------
218name : str
219 Name under which the accessor should be registered. A warning is issued
220 if this name conflicts with a preexisting attribute.
222Returns
223-------
224callable
225 A class decorator.
227See Also
228--------
229%(others)s
231Notes
232-----
233When accessed, your accessor will be initialized with the pandas object
234the user is interacting with. So the signature must be
236.. code-block:: python
238 def __init__(self, pandas_object): # noqa: E999
239 ...
241For consistency with pandas methods, you should raise an ``AttributeError``
242if the data passed to your accessor has an incorrect dtype.
244>>> pd.Series(['a', 'b']).dt
245Traceback (most recent call last):
246...
247AttributeError: Can only use .dt accessor with datetimelike values
249Examples
250--------
252In your library code::
254 import pandas as pd
256 @pd.api.extensions.register_dataframe_accessor("geo")
257 class GeoAccessor:
258 def __init__(self, pandas_obj):
259 self._obj = pandas_obj
261 @property
262 def center(self):
263 # return the geographic center point of this DataFrame
264 lat = self._obj.latitude
265 lon = self._obj.longitude
266 return (float(lon.mean()), float(lat.mean()))
268 def plot(self):
269 # plot this array's data on a map, e.g., using Cartopy
270 pass
272Back in an interactive IPython session:
274 >>> ds = pd.DataFrame({'longitude': np.linspace(0, 10),
275 ... 'latitude': np.linspace(0, 20)})
276 >>> ds.geo.center
277 (5.0, 10.0)
278 >>> ds.geo.plot()
279 # plots data on a map
280"""
283@Appender(
284 _doc
285 % dict(
286 klass="DataFrame", others=("register_series_accessor, register_index_accessor")
287 )
288)
289def register_dataframe_accessor(name):
290 from pandas import DataFrame
292 return _register_accessor(name, DataFrame)
295@Appender(
296 _doc
297 % dict(
298 klass="Series", others=("register_dataframe_accessor, register_index_accessor")
299 )
300)
301def register_series_accessor(name):
302 from pandas import Series
304 return _register_accessor(name, Series)
307@Appender(
308 _doc
309 % dict(
310 klass="Index", others=("register_dataframe_accessor, register_series_accessor")
311 )
312)
313def register_index_accessor(name):
314 from pandas import Index
316 return _register_accessor(name, Index)