_exceptions.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  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 .._utils 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 _UFuncNoLoopError(UFuncTypeError):
  30. """ Thrown when a ufunc loop cannot be found """
  31. def __init__(self, ufunc, dtypes):
  32. super().__init__(ufunc)
  33. self.dtypes = tuple(dtypes)
  34. def __str__(self):
  35. return (
  36. "ufunc {!r} did not contain a loop with signature matching types "
  37. "{!r} -> {!r}"
  38. ).format(
  39. self.ufunc.__name__,
  40. _unpack_tuple(self.dtypes[:self.ufunc.nin]),
  41. _unpack_tuple(self.dtypes[self.ufunc.nin:])
  42. )
  43. @_display_as_base
  44. class _UFuncBinaryResolutionError(_UFuncNoLoopError):
  45. """ Thrown when a binary resolution fails """
  46. def __init__(self, ufunc, dtypes):
  47. super().__init__(ufunc, dtypes)
  48. assert len(self.dtypes) == 2
  49. def __str__(self):
  50. return (
  51. "ufunc {!r} cannot use operands with types {!r} and {!r}"
  52. ).format(
  53. self.ufunc.__name__, *self.dtypes
  54. )
  55. @_display_as_base
  56. class _UFuncCastingError(UFuncTypeError):
  57. def __init__(self, ufunc, casting, from_, to):
  58. super().__init__(ufunc)
  59. self.casting = casting
  60. self.from_ = from_
  61. self.to = to
  62. @_display_as_base
  63. class _UFuncInputCastingError(_UFuncCastingError):
  64. """ Thrown when a ufunc input cannot be casted """
  65. def __init__(self, ufunc, casting, from_, to, i):
  66. super().__init__(ufunc, casting, from_, to)
  67. self.in_i = i
  68. def __str__(self):
  69. # only show the number if more than one input exists
  70. i_str = "{} ".format(self.in_i) if self.ufunc.nin != 1 else ""
  71. return (
  72. "Cannot cast ufunc {!r} input {}from {!r} to {!r} with casting "
  73. "rule {!r}"
  74. ).format(
  75. self.ufunc.__name__, i_str, self.from_, self.to, self.casting
  76. )
  77. @_display_as_base
  78. class _UFuncOutputCastingError(_UFuncCastingError):
  79. """ Thrown when a ufunc output cannot be casted """
  80. def __init__(self, ufunc, casting, from_, to, i):
  81. super().__init__(ufunc, casting, from_, to)
  82. self.out_i = i
  83. def __str__(self):
  84. # only show the number if more than one output exists
  85. i_str = "{} ".format(self.out_i) if self.ufunc.nout != 1 else ""
  86. return (
  87. "Cannot cast ufunc {!r} output {}from {!r} to {!r} with casting "
  88. "rule {!r}"
  89. ).format(
  90. self.ufunc.__name__, i_str, self.from_, self.to, self.casting
  91. )
  92. @_display_as_base
  93. class _ArrayMemoryError(MemoryError):
  94. """ Thrown when an array cannot be allocated"""
  95. def __init__(self, shape, dtype):
  96. self.shape = shape
  97. self.dtype = dtype
  98. @property
  99. def _total_size(self):
  100. num_bytes = self.dtype.itemsize
  101. for dim in self.shape:
  102. num_bytes *= dim
  103. return num_bytes
  104. @staticmethod
  105. def _size_to_string(num_bytes):
  106. """ Convert a number of bytes into a binary size string """
  107. # https://en.wikipedia.org/wiki/Binary_prefix
  108. LOG2_STEP = 10
  109. STEP = 1024
  110. units = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']
  111. unit_i = max(num_bytes.bit_length() - 1, 1) // LOG2_STEP
  112. unit_val = 1 << (unit_i * LOG2_STEP)
  113. n_units = num_bytes / unit_val
  114. del unit_val
  115. # ensure we pick a unit that is correct after rounding
  116. if round(n_units) == STEP:
  117. unit_i += 1
  118. n_units /= STEP
  119. # deal with sizes so large that we don't have units for them
  120. if unit_i >= len(units):
  121. new_unit_i = len(units) - 1
  122. n_units *= 1 << ((unit_i - new_unit_i) * LOG2_STEP)
  123. unit_i = new_unit_i
  124. unit_name = units[unit_i]
  125. # format with a sensible number of digits
  126. if unit_i == 0:
  127. # no decimal point on bytes
  128. return '{:.0f} {}'.format(n_units, unit_name)
  129. elif round(n_units) < 1000:
  130. # 3 significant figures, if none are dropped to the left of the .
  131. return '{:#.3g} {}'.format(n_units, unit_name)
  132. else:
  133. # just give all the digits otherwise
  134. return '{:#.0f} {}'.format(n_units, unit_name)
  135. def __str__(self):
  136. size_str = self._size_to_string(self._total_size)
  137. return (
  138. "Unable to allocate {} for an array with shape {} and data type {}"
  139. .format(size_str, self.shape, self.dtype)
  140. )