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

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

84 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""" 

7from __future__ import annotations 

8 

9from typing import ( 

10 Callable, 

11 final, 

12) 

13import warnings 

14 

15from pandas.util._decorators import doc 

16from pandas.util._exceptions import find_stack_level 

17 

18 

19class DirNamesMixin: 

20 _accessors: set[str] = set() 

21 _hidden_attrs: frozenset[str] = frozenset() 

22 

23 @final 

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

25 """ 

26 Delete unwanted __dir__ for this object. 

27 """ 

28 return self._accessors | self._hidden_attrs 

29 

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

31 """ 

32 Add additional __dir__ for this object. 

33 """ 

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

35 

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

37 """ 

38 Provide method name lookup and completion. 

39 

40 Notes 

41 ----- 

42 Only provide 'public' methods. 

43 """ 

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

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

46 return sorted(rv) 

47 

48 

49class PandasDelegate: 

50 """ 

51 Abstract base class for delegating methods/properties. 

52 """ 

53 

54 def _delegate_property_get(self, name, *args, **kwargs): 

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

56 

57 def _delegate_property_set(self, name, value, *args, **kwargs): 

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

59 

60 def _delegate_method(self, name, *args, **kwargs): 

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

62 

63 @classmethod 

64 def _add_delegate_accessors( 

65 cls, 

66 delegate, 

67 accessors: list[str], 

68 typ: str, 

69 overwrite: bool = False, 

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

71 raise_on_missing: bool = True, 

72 ) -> None: 

73 """ 

74 Add accessors to cls from the delegate class. 

75 

76 Parameters 

77 ---------- 

78 cls 

79 Class to add the methods/properties to. 

80 delegate 

81 Class to get methods/properties and doc-strings. 

82 accessors : list of str 

83 List of accessors to add. 

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

85 overwrite : bool, default False 

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

87 accessor_mapping: Callable, default lambda x: x 

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

89 raise_on_missing: bool, default True 

90 Raise if an accessor does not exist on delegate. 

91 False skips the missing accessor. 

92 """ 

93 

94 def _create_delegator_property(name): 

95 def _getter(self): 

96 return self._delegate_property_get(name) 

97 

98 def _setter(self, new_values): 

99 return self._delegate_property_set(name, new_values) 

100 

101 _getter.__name__ = name 

102 _setter.__name__ = name 

103 

104 return property( 

105 fget=_getter, 

106 fset=_setter, 

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

108 ) 

109 

110 def _create_delegator_method(name): 

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

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

113 

114 f.__name__ = name 

115 f.__doc__ = getattr(delegate, accessor_mapping(name)).__doc__ 

116 

117 return f 

118 

119 for name in accessors: 

120 if ( 

121 not raise_on_missing 

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

123 ): 

124 continue 

125 

126 if typ == "property": 

127 f = _create_delegator_property(name) 

128 else: 

129 f = _create_delegator_method(name) 

130 

131 # don't overwrite existing methods/properties 

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

133 setattr(cls, name, f) 

134 

135 

136def delegate_names( 

137 delegate, 

138 accessors: list[str], 

139 typ: str, 

140 overwrite: bool = False, 

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

142 raise_on_missing: bool = True, 

143): 

144 """ 

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

146 an alternative usage to directly calling `_add_delegate_accessors` 

147 below a class definition. 

148 

149 Parameters 

150 ---------- 

151 delegate : object 

152 The class to get methods/properties & doc-strings. 

153 accessors : Sequence[str] 

154 List of accessor to add. 

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

156 overwrite : bool, default False 

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

158 accessor_mapping: Callable, default lambda x: x 

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

160 raise_on_missing: bool, default True 

161 Raise if an accessor does not exist on delegate. 

162 False skips the missing accessor. 

163 

164 Returns 

165 ------- 

166 callable 

167 A class decorator. 

168 

169 Examples 

170 -------- 

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

172 class CategoricalAccessor(PandasDelegate): 

173 [...] 

174 """ 

175 

176 def add_delegate_accessors(cls): 

177 cls._add_delegate_accessors( 

178 delegate, 

179 accessors, 

180 typ, 

181 overwrite=overwrite, 

182 accessor_mapping=accessor_mapping, 

183 raise_on_missing=raise_on_missing, 

184 ) 

185 return cls 

186 

187 return add_delegate_accessors 

188 

189 

190# Ported with modifications from xarray 

191# https://github.com/pydata/xarray/blob/master/xarray/core/extensions.py 

192# 1. We don't need to catch and re-raise AttributeErrors as RuntimeErrors 

193# 2. We use a UserWarning instead of a custom Warning 

194 

195 

196class CachedAccessor: 

197 """ 

198 Custom property-like object. 

199 

200 A descriptor for caching accessors. 

201 

202 Parameters 

203 ---------- 

204 name : str 

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

206 accessor : cls 

207 Class with the extension methods. 

208 

209 Notes 

210 ----- 

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

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

213 single argument ``data``. 

214 """ 

215 

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

217 self._name = name 

218 self._accessor = accessor 

219 

220 def __get__(self, obj, cls): 

221 if obj is None: 

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

223 return self._accessor 

224 accessor_obj = self._accessor(obj) 

225 # Replace the property with the accessor object. Inspired by: 

226 # https://www.pydanny.com/cached-property.html 

227 # We need to use object.__setattr__ because we overwrite __setattr__ on 

228 # NDFrame 

229 object.__setattr__(obj, self._name, accessor_obj) 

230 return accessor_obj 

231 

232 

233@doc(klass="", others="") 

234def _register_accessor(name, cls): 

235 """ 

236 Register a custom accessor on {klass} objects. 

237 

238 Parameters 

239 ---------- 

240 name : str 

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

242 if this name conflicts with a preexisting attribute. 

243 

244 Returns 

245 ------- 

246 callable 

247 A class decorator. 

248 

249 See Also 

250 -------- 

251 register_dataframe_accessor : Register a custom accessor on DataFrame objects. 

252 register_series_accessor : Register a custom accessor on Series objects. 

253 register_index_accessor : Register a custom accessor on Index objects. 

254 

255 Notes 

256 ----- 

257 When accessed, your accessor will be initialized with the pandas object 

258 the user is interacting with. So the signature must be 

259 

260 .. code-block:: python 

261 

262 def __init__(self, pandas_object): # noqa: E999 

263 ... 

264 

265 For consistency with pandas methods, you should raise an ``AttributeError`` 

266 if the data passed to your accessor has an incorrect dtype. 

267 

268 >>> pd.Series(['a', 'b']).dt 

269 Traceback (most recent call last): 

270 ... 

271 AttributeError: Can only use .dt accessor with datetimelike values 

272 

273 Examples 

274 -------- 

275 In your library code:: 

276 

277 import pandas as pd 

278 

279 @pd.api.extensions.register_dataframe_accessor("geo") 

280 class GeoAccessor: 

281 def __init__(self, pandas_obj): 

282 self._obj = pandas_obj 

283 

284 @property 

285 def center(self): 

286 # return the geographic center point of this DataFrame 

287 lat = self._obj.latitude 

288 lon = self._obj.longitude 

289 return (float(lon.mean()), float(lat.mean())) 

290 

291 def plot(self): 

292 # plot this array's data on a map, e.g., using Cartopy 

293 pass 

294 

295 Back in an interactive IPython session: 

296 

297 .. code-block:: ipython 

298 

299 In [1]: ds = pd.DataFrame({{"longitude": np.linspace(0, 10), 

300 ...: "latitude": np.linspace(0, 20)}}) 

301 In [2]: ds.geo.center 

302 Out[2]: (5.0, 10.0) 

303 In [3]: ds.geo.plot() # plots data on a map 

304 """ 

305 

306 def decorator(accessor): 

307 if hasattr(cls, name): 

308 warnings.warn( 

309 f"registration of accessor {repr(accessor)} under name " 

310 f"{repr(name)} for type {repr(cls)} is overriding a preexisting " 

311 f"attribute with the same name.", 

312 UserWarning, 

313 stacklevel=find_stack_level(), 

314 ) 

315 setattr(cls, name, CachedAccessor(name, accessor)) 

316 cls._accessors.add(name) 

317 return accessor 

318 

319 return decorator 

320 

321 

322@doc(_register_accessor, klass="DataFrame") 

323def register_dataframe_accessor(name): 

324 from pandas import DataFrame 

325 

326 return _register_accessor(name, DataFrame) 

327 

328 

329@doc(_register_accessor, klass="Series") 

330def register_series_accessor(name): 

331 from pandas import Series 

332 

333 return _register_accessor(name, Series) 

334 

335 

336@doc(_register_accessor, klass="Index") 

337def register_index_accessor(name): 

338 from pandas import Index 

339 

340 return _register_accessor(name, Index)