1"""

2 Various richly-typed exceptions, that also help us deal with string formatting

3 in python where it's easier.

4

5 By putting the formatting in `__str__`, we also avoid paying the cost for

6 users who silence the exceptions.

7"""

8 from numpy.core.overrides import set_module

9

10 def _unpack_tuple(tup):

11 if len(tup) == 1:

12 return tup[0]

13 else:

14 return tup

15

16

17 def _display_as_base(cls):

18 """

19 A decorator that makes an exception class look like its base.

20

21 We use this to hide subclasses that are implementation details - the user

22 should catch the base type, which is what the traceback will show them.

23

24 Classes decorated with this decorator are subject to removal without a

25 deprecation warning.

26 """

27 assert issubclass(cls, Exception)

28 cls.__name__ = cls.__base__.__name__

29 return cls

30

31

32 class UFuncTypeError(TypeError):

33 """ Base class for all ufunc exceptions """

34 def __init__(self, ufunc):

35 self.ufunc = ufunc

36

37

38@_display_as_base

39 class _UFuncBinaryResolutionError(UFuncTypeError):

40 """ Thrown when a binary resolution fails """

41 def __init__(self, ufunc, dtypes):

42 super().__init__(ufunc)

43 self.dtypes = tuple(dtypes)

44 assert len(self.dtypes) == 2

45

46 def __str__(self):

47 return (

48 "ufunc {!r} cannot use operands with types {!r} and {!r}"

49 ).format(

50 self.ufunc.__name__, *self.dtypes

51 )

52

53

54@_display_as_base

55 class _UFuncNoLoopError(UFuncTypeError):

56 """ Thrown when a ufunc loop cannot be found """

57 def __init__(self, ufunc, dtypes):

58 super().__init__(ufunc)

59 self.dtypes = tuple(dtypes)

60

61 def __str__(self):

62 return (

63 "ufunc {!r} did not contain a loop with signature matching types "

64 "{!r} -> {!r}"

65 ).format(

66 self.ufunc.__name__,

67 _unpack_tuple(self.dtypes[:self.ufunc.nin]),

68 _unpack_tuple(self.dtypes[self.ufunc.nin:])

69 )

70

71

72@_display_as_base

73 class _UFuncCastingError(UFuncTypeError):

74 def __init__(self, ufunc, casting, from_, to):

75 super().__init__(ufunc)

76 self.casting = casting

77 self.from_ = from_

78 self.to = to

79

80

81@_display_as_base

82 class _UFuncInputCastingError(_UFuncCastingError):

83 """ Thrown when a ufunc input cannot be casted """

84 def __init__(self, ufunc, casting, from_, to, i):

85 super().__init__(ufunc, casting, from_, to)

86 self.in_i = i

87

88 def __str__(self):

89 # only show the number if more than one input exists

90 i_str = "{} ".format(self.in_i) if self.ufunc.nin != 1 else ""

91 return (

92 "Cannot cast ufunc {!r} input {}from {!r} to {!r} with casting "

93 "rule {!r}"

94 ).format(

95 self.ufunc.__name__, i_str, self.from_, self.to, self.casting

96 )

97

98

99@_display_as_base

100 class _UFuncOutputCastingError(_UFuncCastingError):

101 """ Thrown when a ufunc output cannot be casted """

102 def __init__(self, ufunc, casting, from_, to, i):

103 super().__init__(ufunc, casting, from_, to)

104 self.out_i = i

105

106 def __str__(self):

107 # only show the number if more than one output exists

108 i_str = "{} ".format(self.out_i) if self.ufunc.nout != 1 else ""

109 return (

110 "Cannot cast ufunc {!r} output {}from {!r} to {!r} with casting "

111 "rule {!r}"

112 ).format(

113 self.ufunc.__name__, i_str, self.from_, self.to, self.casting

114 )

115

116

117# Exception used in shares_memory()

118@set_module('numpy')

119 class TooHardError(RuntimeError):

120 """max_work was exceeded.

121

122 This is raised whenever the maximum number of candidate solutions

123 to consider specified by the ``max_work`` parameter is exceeded.

124 Assigning a finite number to max_work may have caused the operation

125 to fail.

126

127 """

128

129 pass

130

131

132@set_module('numpy')

133 class AxisError(ValueError, IndexError):

134 """Axis supplied was invalid.

135

136 This is raised whenever an ``axis`` parameter is specified that is larger

137 than the number of array dimensions.

138 For compatibility with code written against older numpy versions, which

139 raised a mixture of `ValueError` and `IndexError` for this situation, this

140 exception subclasses both to ensure that ``except ValueError`` and

141 ``except IndexError`` statements continue to catch `AxisError`.

142

143 .. versionadded:: 1.13

144

145 Parameters

146 ----------

147 axis : int or str

148 The out of bounds axis or a custom exception message.

149 If an axis is provided, then `ndim` should be specified as well.

150 ndim : int, optional

151 The number of array dimensions.

152 msg_prefix : str, optional

153 A prefix for the exception message.

154

155 Attributes

156 ----------

157 axis : int, optional

158 The out of bounds axis or ``None`` if a custom exception

159 message was provided. This should be the axis as passed by

160 the user, before any normalization to resolve negative indices.

161

162 .. versionadded:: 1.22

163 ndim : int, optional

164 The number of array dimensions or ``None`` if a custom exception

165 message was provided.

166

167 .. versionadded:: 1.22

168

169

170 Examples

171 --------

172 >>> array_1d = np.arange(10)

173 >>> np.cumsum(array_1d, axis=1)

174 Traceback (most recent call last):

175 ...

176 numpy.AxisError: axis 1 is out of bounds for array of dimension 1

177

178 Negative axes are preserved:

179

180 >>> np.cumsum(array_1d, axis=-2)

181 Traceback (most recent call last):

182 ...

183 numpy.AxisError: axis -2 is out of bounds for array of dimension 1

184

185 The class constructor generally takes the axis and arrays'

186 dimensionality as arguments:

187

188 >>> print(np.AxisError(2, 1, msg_prefix='error'))

189 error: axis 2 is out of bounds for array of dimension 1

190

191 Alternatively, a custom exception message can be passed:

192

193 >>> print(np.AxisError('Custom error message'))

194 Custom error message

195

196 """

197

198 __slots__ = ("axis", "ndim", "_msg")

199

200 def __init__(self, axis, ndim=None, msg_prefix=None):

201 if ndim is msg_prefix is None:

202 # single-argument form: directly set the error message

203 self._msg = axis

204 self.axis = None

205 self.ndim = None

206 else:

207 self._msg = msg_prefix

208 self.axis = axis

209 self.ndim = ndim

210

211 def __str__(self):

212 axis = self.axis

213 ndim = self.ndim

214

215 if axis is ndim is None:

216 return self._msg

217 else:

218 msg = f"axis {axis} is out of bounds for array of dimension {ndim}"

219 if self._msg is not None:

220 msg = f"{self._msg}: {msg}"

221 return msg

222

223

224@_display_as_base

225 class _ArrayMemoryError(MemoryError):

226 """ Thrown when an array cannot be allocated"""

227 def __init__(self, shape, dtype):

228 self.shape = shape

229 self.dtype = dtype

230

231 @property

232 def _total_size(self):

233 num_bytes = self.dtype.itemsize

234 for dim in self.shape:

235 num_bytes *= dim

236 return num_bytes

237

238 @staticmethod

239 def _size_to_string(num_bytes):

240 """ Convert a number of bytes into a binary size string """

241

242 # https://en.wikipedia.org/wiki/Binary_prefix

243 LOG2_STEP = 10

244 STEP = 1024

245 units = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']

246

247 unit_i = max(num_bytes.bit_length() - 1, 1) // LOG2_STEP

248 unit_val = 1 << (unit_i * LOG2_STEP)

249 n_units = num_bytes / unit_val

250 del unit_val

251

252 # ensure we pick a unit that is correct after rounding

253 if round(n_units) == STEP:

254 unit_i += 1

255 n_units /= STEP

256

257 # deal with sizes so large that we don't have units for them

258 if unit_i >= len(units):

259 new_unit_i = len(units) - 1

260 n_units *= 1 << ((unit_i - new_unit_i) * LOG2_STEP)

261 unit_i = new_unit_i

262

263 unit_name = units[unit_i]

264 # format with a sensible number of digits

265 if unit_i == 0:

266 # no decimal point on bytes

267 return '{:.0f} {}'.format(n_units, unit_name)

268 elif round(n_units) < 1000:

269 # 3 significant figures, if none are dropped to the left of the .

270 return '{:#.3g} {}'.format(n_units, unit_name)

271 else:

272 # just give all the digits otherwise

273 return '{:#.0f} {}'.format(n_units, unit_name)

274

275 def __str__(self):

276 size_str = self._size_to_string(self._total_size)

277 return (

278 "Unable to allocate {} for an array with shape {} and data type {}"

279 .format(size_str, self.shape, self.dtype)

280 )