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

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

68 statements  

1""" 

2Boilerplate functions used in defining binary operations. 

3""" 

4 

5from __future__ import annotations 

6 

7from functools import wraps 

8from typing import TYPE_CHECKING 

9import warnings 

10 

11import numpy as np 

12 

13from pandas._libs.lib import ( 

14 is_list_like, 

15 item_from_zerodim, 

16) 

17from pandas._libs.missing import is_matching_na 

18from pandas.errors import Pandas4Warning 

19from pandas.util._exceptions import find_stack_level 

20 

21from pandas.core.dtypes.generic import ( 

22 ABCDataFrame, 

23 ABCExtensionArray, 

24 ABCIndex, 

25 ABCNumpyExtensionArray, 

26 ABCSeries, 

27) 

28 

29from pandas.core.construction import ( 

30 ensure_wrapped_if_datetimelike, 

31 sanitize_array, 

32) 

33 

34if TYPE_CHECKING: 

35 from collections.abc import Callable 

36 

37 from pandas._typing import F 

38 

39 

40def has_castable_attr(obj) -> bool: 

41 attrs = ["__array__", "__dlpack__", "__arrow_c_array__", "__arrow_c_stream__"] 

42 return any(hasattr(obj, name) for name in attrs) 

43 

44 

45def raise_if_2d(other) -> None: 

46 """ 

47 Reject a multi-dimensional operand before any dtype-specific conversion, 

48 matching BaseMaskedArray (GH#62682). 

49 """ 

50 # ABCExtensionArray does not match NumpyExtensionArray, whose _typ is 

51 # "npy_extension" -- and that is the EA most likely to be 2-D (GH#62682) 

52 if ( 

53 isinstance(other, (np.ndarray, ABCExtensionArray, ABCNumpyExtensionArray)) 

54 and other.ndim > 1 

55 ): 

56 raise NotImplementedError("can only perform ops with 1-d structures") 

57 

58 

59def maybe_warn_listlike(other) -> None: 

60 """ 

61 Warn when operating against a list-like that is neither a standard container 

62 (``list``, ``np.ndarray``) nor a pandas object nor array-castable. 

63 

64 Such operations (e.g. with ``tuple``, ``range``, ``deque``) are deprecated 

65 (GH#62423) and will treat ``other`` as scalar-like in a future version. 

66 """ 

67 if ( 

68 is_list_like(other) 

69 and not isinstance( 

70 other, 

71 (list, np.ndarray, ABCExtensionArray, ABCIndex, ABCSeries, ABCDataFrame), 

72 ) 

73 and not has_castable_attr(other) 

74 ): 

75 warnings.warn( 

76 f"Operation with {type(other).__name__} is deprecated. " 

77 "In a future version these will be treated as scalar-like. " 

78 "To retain the old behavior, explicitly wrap in a Series " 

79 "instead.", 

80 Pandas4Warning, 

81 stacklevel=find_stack_level(), 

82 ) 

83 

84 

85def unpack_zerodim_and_defer(name: str) -> Callable[[F], F]: 

86 """ 

87 Boilerplate for pandas conventions in arithmetic and comparison methods. 

88 

89 Parameters 

90 ---------- 

91 name : str 

92 

93 Returns 

94 ------- 

95 decorator 

96 """ 

97 

98 def wrapper(method: F) -> F: 

99 return _unpack_zerodim_and_defer(method, name) 

100 

101 return wrapper 

102 

103 

104def _unpack_zerodim_and_defer(method: F, name: str) -> F: 

105 """ 

106 Boilerplate for pandas conventions in arithmetic and comparison methods. 

107 

108 Ensure method returns NotImplemented when operating against "senior" 

109 classes. Ensure zero-dimensional ndarrays are always unpacked. 

110 

111 Parameters 

112 ---------- 

113 method : binary method 

114 name : str 

115 

116 Returns 

117 ------- 

118 method 

119 """ 

120 is_logical = name.strip("_") in ["or", "xor", "and", "ror", "rxor", "rand"] 

121 

122 @wraps(method) 

123 def new_method(self, other): 

124 prio = getattr(other, "__pandas_priority__", None) 

125 if prio is not None: 

126 if prio > self.__pandas_priority__: 

127 # e.g. other is DataFrame while self is Index/Series/EA 

128 return NotImplemented 

129 

130 other = item_from_zerodim(other) 

131 if ( 

132 isinstance(self, ABCExtensionArray) 

133 and isinstance(other, list) 

134 and not is_logical 

135 ): 

136 # See GH#62423 

137 other = sanitize_array(other, None) 

138 other = ensure_wrapped_if_datetimelike(other) 

139 

140 return method(self, other) 

141 

142 # error: Incompatible return value type (got "Callable[[Any, Any], Any]", 

143 # expected "F") 

144 return new_method # type: ignore[return-value] 

145 

146 

147def get_op_result_name(left, right): 

148 """ 

149 Find the appropriate name to pin to an operation result. This result 

150 should always be either an Index or a Series. 

151 

152 Parameters 

153 ---------- 

154 left : {Series, Index} 

155 right : object 

156 

157 Returns 

158 ------- 

159 name : object 

160 Usually a string 

161 """ 

162 if isinstance(right, (ABCSeries, ABCIndex)): 

163 name = _maybe_match_name(left, right) 

164 else: 

165 name = left.name 

166 return name 

167 

168 

169def _maybe_match_name(a, b): 

170 """ 

171 Try to find a name to attach to the result of an operation between 

172 a and b. If only one of these has a `name` attribute, return that 

173 name. Otherwise return a consensus name if they match or None if 

174 they have different names. 

175 

176 Parameters 

177 ---------- 

178 a : object 

179 b : object 

180 

181 Returns 

182 ------- 

183 name : str or None 

184 

185 See Also 

186 -------- 

187 pandas.core.common.consensus_name_attr 

188 """ 

189 a_has = hasattr(a, "name") 

190 b_has = hasattr(b, "name") 

191 if a_has and b_has: 

192 try: 

193 if a.name == b.name: 

194 return a.name 

195 elif is_matching_na(a.name, b.name): 

196 # e.g. both are np.nan 

197 return a.name 

198 else: 

199 return None 

200 except TypeError: 

201 # pd.NA 

202 if is_matching_na(a.name, b.name): 

203 return a.name 

204 return None 

205 except ValueError: 

206 # e.g. np.int64(1) vs (np.int64(1), np.int64(2)) 

207 return None 

208 elif a_has: 

209 return a.name 

210 elif b_has: 

211 return b.name 

212 return None