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

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

56 statements  

1""" 

2Boilerplate functions used in defining binary operations. 

3""" 

4from __future__ import annotations 

5 

6from functools import wraps 

7import sys 

8from typing import Callable 

9 

10from pandas._libs.lib import item_from_zerodim 

11from pandas._libs.missing import is_matching_na 

12from pandas._typing import F 

13 

14from pandas.core.dtypes.generic import ( 

15 ABCDataFrame, 

16 ABCIndex, 

17 ABCSeries, 

18) 

19 

20 

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

22 """ 

23 Boilerplate for pandas conventions in arithmetic and comparison methods. 

24 

25 Parameters 

26 ---------- 

27 name : str 

28 

29 Returns 

30 ------- 

31 decorator 

32 """ 

33 

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

35 return _unpack_zerodim_and_defer(method, name) 

36 

37 return wrapper 

38 

39 

40def _unpack_zerodim_and_defer(method, name: str): 

41 """ 

42 Boilerplate for pandas conventions in arithmetic and comparison methods. 

43 

44 Ensure method returns NotImplemented when operating against "senior" 

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

46 

47 Parameters 

48 ---------- 

49 method : binary method 

50 name : str 

51 

52 Returns 

53 ------- 

54 method 

55 """ 

56 if sys.version_info < (3, 9): 

57 from pandas.util._str_methods import ( 

58 removeprefix, 

59 removesuffix, 

60 ) 

61 

62 stripped_name = removesuffix(removeprefix(name, "__"), "__") 

63 else: 

64 stripped_name = name.removeprefix("__").removesuffix("__") 

65 is_cmp = stripped_name in {"eq", "ne", "lt", "le", "gt", "ge"} 

66 

67 @wraps(method) 

68 def new_method(self, other): 

69 if is_cmp and isinstance(self, ABCIndex) and isinstance(other, ABCSeries): 

70 # For comparison ops, Index does *not* defer to Series 

71 pass 

72 else: 

73 for cls in [ABCDataFrame, ABCSeries, ABCIndex]: 

74 if isinstance(self, cls): 

75 break 

76 if isinstance(other, cls): 

77 return NotImplemented 

78 

79 other = item_from_zerodim(other) 

80 

81 return method(self, other) 

82 

83 return new_method 

84 

85 

86def get_op_result_name(left, right): 

87 """ 

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

89 should always be either an Index or a Series. 

90 

91 Parameters 

92 ---------- 

93 left : {Series, Index} 

94 right : object 

95 

96 Returns 

97 ------- 

98 name : object 

99 Usually a string 

100 """ 

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

102 name = _maybe_match_name(left, right) 

103 else: 

104 name = left.name 

105 return name 

106 

107 

108def _maybe_match_name(a, b): 

109 """ 

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

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

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

113 they have different names. 

114 

115 Parameters 

116 ---------- 

117 a : object 

118 b : object 

119 

120 Returns 

121 ------- 

122 name : str or None 

123 

124 See Also 

125 -------- 

126 pandas.core.common.consensus_name_attr 

127 """ 

128 a_has = hasattr(a, "name") 

129 b_has = hasattr(b, "name") 

130 if a_has and b_has: 

131 try: 

132 if a.name == b.name: 

133 return a.name 

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

135 # e.g. both are np.nan 

136 return a.name 

137 else: 

138 return None 

139 except TypeError: 

140 # pd.NA 

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

142 return a.name 

143 return None 

144 except ValueError: 

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

146 return None 

147 elif a_has: 

148 return a.name 

149 elif b_has: 

150 return b.name 

151 return None