Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/ops/invalid.py: 33%

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

79 statements  

1""" 

2Templates for invalid operations. 

3""" 

4 

5from __future__ import annotations 

6 

7import datetime 

8import operator 

9from typing import ( 

10 TYPE_CHECKING, 

11 Any, 

12 NoReturn, 

13) 

14 

15import numpy as np 

16 

17from pandas._libs import lib 

18from pandas._libs.missing import NAType 

19from pandas._libs.tslibs import ( 

20 BaseOffset, 

21 Period, 

22) 

23 

24from pandas.core.dtypes.dtypes import ( 

25 ArrowDtype, 

26 CategoricalDtype, 

27 PeriodDtype, 

28) 

29from pandas.core.dtypes.generic import ( 

30 ABCDataFrame, 

31 ABCExtensionArray, 

32 ABCIndex, 

33 ABCNumpyExtensionArray, 

34 ABCSeries, 

35) 

36 

37if TYPE_CHECKING: 

38 from collections.abc import Callable 

39 

40 from pandas._typing import ( 

41 ArrayLike, 

42 Scalar, 

43 npt, 

44 ) 

45 

46 

47_LOGICAL_UFUNCS = frozenset( 

48 {np.logical_and, np.logical_or, np.logical_xor, np.logical_not} 

49) 

50 

51# Timestamp and NaT subclass datetime.date, Timedelta subclasses datetime.timedelta 

52_DATETIMELIKE_SCALARS = ( 

53 datetime.date, 

54 datetime.time, 

55 datetime.timedelta, 

56 np.datetime64, 

57 np.timedelta64, 

58 Period, 

59 BaseOffset, 

60) 

61 

62# a ufunc against one of these stays inside pandas: every _HANDLED_TYPES contains 

63# np.ndarray, NA matches no ABC (see test_logical_ufunc_na_scalar_datetimelike), 

64# and a pandas operand that one array does defer to re-enters this guard 

65_NON_DEFERRING = ( 

66 np.ndarray, 

67 ABCDataFrame, 

68 ABCExtensionArray, 

69 # NumpyExtensionArray's _typ is not one of ABCExtensionArray's 

70 ABCNumpyExtensionArray, 

71 ABCIndex, 

72 ABCSeries, 

73 NAType, 

74) 

75 

76 

77def _defers_to(obj: object) -> bool: 

78 """ 

79 Whether a ufunc operand is one pandas hands the operation off to. 

80 """ 

81 array_ufunc = getattr(type(obj), "__array_ufunc__", None) 

82 return ( 

83 array_ufunc is not None 

84 and array_ufunc is not np.ndarray.__array_ufunc__ 

85 # issubclass, not isinstance: the ABCs read `_typ` off whatever they are 

86 # handed, and a symbolic operand answers that with an expression that 

87 # then has no truth value, see test_logical_ufunc_symbolic_operand 

88 and not issubclass(type(obj), _NON_DEFERRING) 

89 ) 

90 

91 

92def invalid_comparison( 

93 left: ArrayLike, 

94 right: ArrayLike | list[Any] | range | Scalar | NAType, 

95 op: Callable[[Any, Any], bool], 

96) -> npt.NDArray[np.bool_]: 

97 """ 

98 If a comparison has mismatched types and is not necessarily meaningful, 

99 follow python3 conventions by: 

100 

101 - returning all-False for equality 

102 - returning all-True for inequality 

103 - raising TypeError otherwise 

104 

105 Parameters 

106 ---------- 

107 left : array-like 

108 right : scalar, array-like 

109 op : operator.{eq, ne, lt, le, gt} 

110 

111 Raises 

112 ------ 

113 TypeError : on inequality comparisons 

114 """ 

115 if op is operator.eq: 

116 res_values = np.zeros(left.shape, dtype=bool) 

117 elif op is operator.ne: 

118 res_values = np.ones(left.shape, dtype=bool) 

119 else: 

120 typ = type(right).__name__ 

121 raise TypeError(f"Invalid comparison between dtype={left.dtype} and {typ}") 

122 return res_values 

123 

124 

125def _is_datetimelike_array(obj: object) -> bool: 

126 if lib.is_scalar(obj): 

127 return False 

128 dtype = getattr(obj, "dtype", None) 

129 if dtype is None: 

130 return False 

131 return _is_datetimelike_dtype(dtype) 

132 

133 

134def _is_datetimelike_dtype(dtype: object) -> bool: 

135 kind = getattr(dtype, "kind", None) 

136 if not isinstance(kind, str) or kind in "biufc": 

137 # no numeric or bool dtype is datetimelike, and logical_op is hot. A 

138 # third-party dtype may have no kind at all, or one that is not a string, 

139 # and must fall through rather than crash, see test_logical_op_third_party 

140 return False 

141 if isinstance(dtype, CategoricalDtype) and dtype.categories is not None: 

142 # a Categorical hides its categories behind kind "O" 

143 return _is_datetimelike_dtype(dtype.categories.dtype) 

144 if isinstance(dtype, ArrowDtype): 

145 import pyarrow as pa 

146 

147 pa_type = dtype.pyarrow_dtype 

148 if pa.types.is_dictionary(pa_type) or pa.types.is_run_end_encoded(pa_type): 

149 # the arrow spellings of the CategoricalDtype unwrap above 

150 pa_type = pa_type.value_type 

151 # kind is "O" for those two, for time32/time64 and for month_day_nano_interval 

152 return pa.types.is_temporal(pa_type) 

153 # kind covers numpy M8/m8 and DatetimeTZDtype; PeriodDtype has kind "O" and needs 

154 # naming. Plain object dtype is excluded on purpose, see 

155 # test_logical_op_object_dtype_still_truthy 

156 return kind in "mM" or isinstance(dtype, PeriodDtype) 

157 

158 

159def _operand_repr(obj: object) -> str: 

160 dtype = getattr(obj, "dtype", None) 

161 if dtype is None: 

162 return f"object of type {type(obj).__name__}" 

163 return f"dtype '{dtype}'" 

164 

165 

166def disallow_datetimelike_logical_op( 

167 left: ArrayLike, right: Any, op: Callable[[Any, Any], Any] 

168) -> None: 

169 """ 

170 Raise TypeError if either operand of a logical op is a datetimelike array. 

171 

172 Parameters 

173 ---------- 

174 left : array-like 

175 right : array-like or scalar 

176 op : operator.{and_, or_, xor} 

177 Or one of the reversed variants from roperator. 

178 

179 Raises 

180 ------ 

181 TypeError : if either operand is a datetimelike array, including a categorical 

182 of one 

183 """ 

184 # GH#68452 these have no truth value; casting them to bool would make every 

185 # entry, NaT included, True 

186 if _is_datetimelike_array(left) or _is_datetimelike_array(right): 

187 raise TypeError( 

188 f"operation '{op.__name__}' not supported for " 

189 f"{_operand_repr(left)} with {_operand_repr(right)}" 

190 ) 

191 

192 

193def disallow_datetimelike_logical_ufunc( 

194 ufunc: np.ufunc, inputs: tuple[Any, ...] 

195) -> None: 

196 """ 

197 Raise TypeError if a logical ufunc is applied to datetimelike data. 

198 

199 These are not in ``UFUNC_ALIASES`` -- they differ from ``&``/``|``/``^`` on 

200 integer data -- so they never reach :func:`disallow_datetimelike_logical_op` 

201 and need their own guard. GH#68524 

202 

203 Parameters 

204 ---------- 

205 ufunc : numpy.ufunc 

206 inputs : tuple 

207 The ufunc's operands, which may include DataFrames. 

208 

209 Raises 

210 ------ 

211 TypeError : if any operand is datetimelike 

212 """ 

213 if ufunc not in _LOGICAL_UFUNCS: 

214 return 

215 

216 if any(_defers_to(obj) for obj in inputs): 

217 # pandas defers the whole op, so raising for any operand takes it away, 

218 # see test_logical_ufunc_third_party_datetimelike 

219 return 

220 

221 for obj in inputs: 

222 if isinstance(obj, ABCDataFrame): 

223 # a DataFrame has no dtype of its own, and with two inputs 

224 # array_ufunc np.asarray()s it before any column-level guard runs. 

225 # Scanned per block rather than per column: this runs on every 

226 # logical ufunc call 

227 dtype = next( 

228 ( 

229 blk.dtype 

230 for blk in obj._mgr.blocks 

231 if _is_datetimelike_dtype(blk.dtype) 

232 ), 

233 None, 

234 ) 

235 if dtype is None: 

236 continue 

237 descr = f"with dtype {dtype}" 

238 elif _is_datetimelike_array(obj): 

239 descr = f"with dtype {obj.dtype}" 

240 elif isinstance(obj, _DATETIMELIKE_SCALARS): 

241 # GH#68452 left scalar operands to the paths that already rejected 

242 # them for &/|/^; no such path exists on the ufunc side 

243 descr = f"of type {type(obj).__name__}" 

244 else: 

245 continue 

246 raise TypeError(f"Object {descr} cannot perform the numpy op {ufunc.__name__}") 

247 

248 

249def make_invalid_op(name: str) -> Callable[..., NoReturn]: 

250 """ 

251 Return a binary method that always raises a TypeError. 

252 

253 Parameters 

254 ---------- 

255 name : str 

256 

257 Returns 

258 ------- 

259 invalid_op : function 

260 """ 

261 

262 def invalid_op(self: object, other: object = None) -> NoReturn: 

263 typ = type(self).__name__ 

264 raise TypeError(f"cannot perform {name} with this index type: {typ}") 

265 

266 invalid_op.__name__ = name 

267 return invalid_op