_exceptions.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. """
  2. Various richly-typed exceptions, that also help us deal with string formatting
  3. in python where it's easier.
  4. By putting the formatting in `__str__`, we also avoid paying the cost for
  5. users who silence the exceptions.
  6. """
  7. from numpy.core.overrides import set_module
  8. def _unpack_tuple(tup):
  9. if len(tup) == 1:
  10. return tup[0]
  11. else:
  12. return tup
  13. def _display_as_base(cls):
  14. """
  15. A decorator that makes an exception class look like its base.
  16. We use this to hide subclasses that are implementation details - the user
  17. should catch the base type, which is what the traceback will show them.
  18. Classes decorated with this decorator are subject to removal without a
  19. deprecation warning.
  20. """
  21. assert issubclass(cls, Exception)
  22. cls.__name__ = cls.__base__.__name__
  23. return cls
  24. class UFuncTypeError(TypeError):
  25. """ Base class for all ufunc exceptions """
  26. def __init__(self, ufunc):
  27. self.ufunc = ufunc
  28. @_display_as_base
  29. class _UFuncBinaryResolutionError(UFuncTypeError):
  30. """ Thrown when a binary resolution fails """
  31. def __init__(self, ufunc, dtypes):
  32. super().__init__(ufunc)
  33. self.dtypes = tuple(dtypes)
  34. assert len(self.dtypes) == 2
  35. def __str__(self):
  36. return (
  37. "ufunc {!r} cannot use operands with types {!r} and {!r}"
  38. ).format(
  39. self.ufunc.__name__, *self.dtypes
  40. )
  41. @_display_as_base
  42. class _UFuncNoLoopError(UFuncTypeError):
  43. """ Thrown when a ufunc loop cannot be found """
  44. def __init__(self, ufunc, dtypes):
  45. super().__init__(ufunc)
  46. self.dtypes = tuple(dtypes)
  47. def __str__(self):
  48. return (
  49. "ufunc {!r} did not contain a loop with signature matching types "
  50. "{!r} -> {!r}"
  51. ).format(
  52. self.ufunc.__name__,
  53. _unpack_tuple(self.dtypes[:self.ufunc.nin]),
  54. _unpack_tuple(self.dtypes[self.ufunc.nin:])
  55. )
  56. @_display_as_base
  57. class _UFuncCastingError(UFuncTypeError):
  58. def __init__(self, ufunc, casting, from_, to):
  59. super().__init__(ufunc)
  60. self.casting = casting
  61. self.from_ = from_
  62. self.to = to
  63. @_display_as_base
  64. class _UFuncInputCastingError(_UFuncCastingError):
  65. """ Thrown when a ufunc input cannot be casted """
  66. def __init__(self, ufunc, casting, from_, to, i):
  67. super().__init__(ufunc, casting, from_, to)
  68. self.in_i = i
  69. def __str__(self):
  70. # only show the number if more than one input exists
  71. i_str = "{} ".format(self.in_i) if self.ufunc.nin != 1 else ""
  72. return (
  73. "Cannot cast ufunc {!r} input {}from {!r} to {!r} with casting "
  74. "rule {!r}"
  75. ).format(
  76. self.ufunc.__name__, i_str, self.from_, self.to, self.casting
  77. )
  78. @_display_as_base
  79. class _UFuncOutputCastingError(_UFuncCastingError):
  80. """ Thrown when a ufunc output cannot be casted """
  81. def __init__(self, ufunc, casting, from_, to, i):
  82. super().__init__(ufunc, casting, from_, to)
  83. self.out_i = i
  84. def __str__(self):
  85. # only show the number if more than one output exists
  86. i_str = "{} ".format(self.out_i) if self.ufunc.nout != 1 else ""
  87. return (
  88. "Cannot cast ufunc {!r} output {}from {!r} to {!r} with casting "
  89. "rule {!r}"
  90. ).format(
  91. self.ufunc.__name__, i_str, self.from_, self.to, self.casting
  92. )
  93. # Exception used in shares_memory()
  94. @set_module('numpy')
  95. class TooHardError(RuntimeError):
  96. """max_work was exceeded.
  97. This is raised whenever the maximum number of candidate solutions
  98. to consider specified by the ``max_work`` parameter is exceeded.
  99. Assigning a finite number to max_work may have caused the operation
  100. to fail.
  101. """
  102. pass
  103. @set_module('numpy')
  104. class AxisError(ValueError, IndexError):
  105. """Axis supplied was invalid.
  106. This is raised whenever an ``axis`` parameter is specified that is larger
  107. than the number of array dimensions.
  108. For compatibility with code written against older numpy versions, which
  109. raised a mixture of `ValueError` and `IndexError` for this situation, this
  110. exception subclasses both to ensure that ``except ValueError`` and
  111. ``except IndexError`` statements continue to catch `AxisError`.
  112. .. versionadded:: 1.13
  113. Parameters
  114. ----------
  115. axis : int or str
  116. The out of bounds axis or a custom exception message.
  117. If an axis is provided, then `ndim` should be specified as well.
  118. ndim : int, optional
  119. The number of array dimensions.
  120. msg_prefix : str, optional
  121. A prefix for the exception message.
  122. Attributes
  123. ----------
  124. axis : int, optional
  125. The out of bounds axis or ``None`` if a custom exception
  126. message was provided. This should be the axis as passed by
  127. the user, before any normalization to resolve negative indices.
  128. .. versionadded:: 1.22
  129. ndim : int, optional
  130. The number of array dimensions or ``None`` if a custom exception
  131. message was provided.
  132. .. versionadded:: 1.22
  133. Examples
  134. --------
  135. >>> array_1d = np.arange(10)
  136. >>> np.cumsum(array_1d, axis=1)
  137. Traceback (most recent call last):
  138. ...
  139. numpy.AxisError: axis 1 is out of bounds for array of dimension 1
  140. Negative axes are preserved:
  141. >>> np.cumsum(array_1d, axis=-2)
  142. Traceback (most recent call last):
  143. ...
  144. numpy.AxisError: axis -2 is out of bounds for array of dimension 1
  145. The class constructor generally takes the axis and arrays'
  146. dimensionality as arguments:
  147. >>> print(np.AxisError(2, 1, msg_prefix='error'))
  148. error: axis 2 is out of bounds for array of dimension 1
  149. Alternatively, a custom exception message can be passed:
  150. >>> print(np.AxisError('Custom error message'))
  151. Custom error message
  152. """
  153. __slots__ = ("axis", "ndim", "_msg")
  154. def __init__(self, axis, ndim=None, msg_prefix=None):
  155. if ndim is msg_prefix is None:
  156. # single-argument form: directly set the error message
  157. self._msg = axis
  158. self.axis = None
  159. self.ndim = None
  160. else:
  161. self._msg = msg_prefix
  162. self.axis = axis
  163. self.ndim = ndim
  164. def __str__(self):
  165. axis = self.axis
  166. ndim = self.ndim
  167. if axis is ndim is None:
  168. return self._msg
  169. else:
  170. msg = f"axis {axis} is out of bounds for array of dimension {ndim}"
  171. if self._msg is not None:
  172. msg = f"{self._msg}: {msg}"
  173. return msg
  174. @_display_as_base
  175. class _ArrayMemoryError(MemoryError):
  176. """ Thrown when an array cannot be allocated"""
  177. def __init__(self, shape, dtype):
  178. self.shape = shape
  179. self.dtype = dtype
  180. @property
  181. def _total_size(self):
  182. num_bytes = self.dtype.itemsize
  183. for dim in self.shape:
  184. num_bytes *= dim
  185. return num_bytes
  186. @staticmethod
  187. def _size_to_string(num_bytes):
  188. """ Convert a number of bytes into a binary size string """
  189. # https://en.wikipedia.org/wiki/Binary_prefix
  190. LOG2_STEP = 10
  191. STEP = 1024
  192. units = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']
  193. unit_i = max(num_bytes.bit_length() - 1, 1) // LOG2_STEP
  194. unit_val = 1 << (unit_i * LOG2_STEP)
  195. n_units = num_bytes / unit_val
  196. del unit_val
  197. # ensure we pick a unit that is correct after rounding
  198. if round(n_units) == STEP:
  199. unit_i += 1
  200. n_units /= STEP
  201. # deal with sizes so large that we don't have units for them
  202. if unit_i >= len(units):
  203. new_unit_i = len(units) - 1
  204. n_units *= 1 << ((unit_i - new_unit_i) * LOG2_STEP)
  205. unit_i = new_unit_i
  206. unit_name = units[unit_i]
  207. # format with a sensible number of digits
  208. if unit_i == 0:
  209. # no decimal point on bytes
  210. return '{:.0f} {}'.format(n_units, unit_name)
  211. elif round(n_units) < 1000:
  212. # 3 significant figures, if none are dropped to the left of the .
  213. return '{:#.3g} {}'.format(n_units, unit_name)
  214. else:
  215. # just give all the digits otherwise
  216. return '{:#.0f} {}'.format(n_units, unit_name)
  217. def __str__(self):
  218. size_str = self._size_to_string(self._total_size)
  219. return (
  220. "Unable to allocate {} for an array with shape {} and data type {}"
  221. .format(size_str, self.shape, self.dtype)
  222. )