Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/bitstring/bitstring_array.py: 20%

498 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:15 +0000

1from __future__ import annotations 

2 

3import math 

4import numbers 

5from collections.abc import Sized 

6from bitstring.exceptions import CreationError, InterpretError 

7from typing import Union, List, Iterable, Any, Optional, BinaryIO, overload, TextIO 

8from bitstring.classes import BitArray, Bits, BitsType 

9from bitstring.dtypes import Dtype 

10from bitstring.utils import tokenparser 

11import functools 

12import copy 

13import array 

14import operator 

15import io 

16import sys 

17 

18# The possible types stored in each element of the Array 

19ElementType = Union[float, str, int, bytes, bool, Bits] 

20 

21 

22class Array: 

23 """Return an Array whose elements are initialised according to the fmt string. 

24 The dtype string can be typecode as used in the struct module or any fixed-length bitstring 

25 format. 

26 

27 a = Array('>H', [1, 15, 105]) 

28 b = Array('int5', [-9, 0, 4]) 

29 

30 The Array data is stored compactly as a BitArray object and the Array behaves very like 

31 a list of items of the given format. Both the Array data and fmt properties can be freely 

32 modified after creation. If the data length is not a multiple of the fmt length then the 

33 Array will have 'trailing_bits' which will prevent some methods from appending to the 

34 Array. 

35 

36 Methods: 

37 

38 append() -- Append a single item to the end of the Array. 

39 byteswap() -- Change byte endianness of all items. 

40 count() -- Count the number of occurences of a value. 

41 extend() -- Append new items to the end of the Array from an iterable. 

42 fromfile() -- Append items read from a file object. 

43 insert() -- Insert an item at a given position. 

44 pop() -- Remove and return an item. 

45 pp() -- Pretty print the Array. 

46 reverse() -- Reverse the order of all items. 

47 tobytes() -- Return Array data as bytes object, padding with zero bits at the end if needed. 

48 tofile() -- Write Array data to a file, padding with zero bits at the end if needed. 

49 tolist() -- Return Array items as a list. 

50 

51 Special methods: 

52 

53 Also available are the operators [], ==, !=, +, *, <<, >>, &, |, ^, 

54 plus the mutating operators [], +=, *=, <<=, >>=, &=, |=, ^=. 

55 

56 Properties: 

57 

58 data -- The BitArray binary data of the Array. Can be freely modified. 

59 dtype -- The format string or typecode. Can be freely modified. 

60 itemsize -- The length *in bits* of a single item. Read only. 

61 trailing_bits -- If the data length is not a multiple of the fmt length, this BitArray 

62 gives the leftovers at the end of the data. 

63 

64 

65 """ 

66 

67 def __init__(self, dtype: Union[str, Dtype], initializer: Optional[Union[int, Array, array.array, Iterable, Bits, bytes, bytearray, memoryview, BinaryIO]] = None, 

68 trailing_bits: Optional[BitsType] = None) -> None: 

69 self.data = BitArray() 

70 try: 

71 self.dtype = dtype 

72 except ValueError as e: 

73 raise CreationError(e) 

74 

75 if isinstance(initializer, numbers.Integral): 

76 self.data = BitArray(initializer * self._dtype.length) 

77 elif isinstance(initializer, (Bits, bytes, bytearray, memoryview)): 

78 self.data += initializer 

79 elif isinstance(initializer, io.BufferedReader): 

80 self.fromfile(initializer) 

81 elif initializer is not None: 

82 self.extend(initializer) 

83 

84 if trailing_bits is not None: 

85 self.data += BitArray._create_from_bitstype(trailing_bits) 

86 

87 @property 

88 def itemsize(self) -> int: 

89 return self._dtype.length 

90 

91 @property 

92 def trailing_bits(self) -> BitArray: 

93 trailing_bit_length = len(self.data) % self._dtype.length 

94 return BitArray() if trailing_bit_length == 0 else self.data[-trailing_bit_length:] 

95 

96 # Converting array.array typecodes to our equivalents. 

97 _array_typecodes: dict[str, str] = {'b': 'int8', 

98 'B': 'uint8', 

99 'h': 'intne16', 

100 'H': 'uintne16', 

101 'l': 'intne32', 

102 'L': 'uintne32', 

103 'q': 'intne64', 

104 'Q': 'uintne64', 

105 'e': 'floatne16', 

106 'f': 'floatne32', 

107 'd': 'floatne64'} 

108 

109 @property 

110 def dtype(self) -> str: 

111 return self._fmt 

112 

113 @dtype.setter 

114 def dtype(self, new_dtype: Union[str, Dtype]) -> None: 

115 if isinstance(new_dtype, Dtype): 

116 self._dtype = new_dtype 

117 self._fmt = str(self._dtype) 

118 else: 

119 dtype = Dtype(new_dtype) 

120 if dtype.length == 0: 

121 raise ValueError(f"A fixed length format is needed for an Array, received '{new_dtype}'.") 

122 self._dtype = dtype 

123 self._fmt = new_dtype 

124 

125 def _create_element(self, value: ElementType) -> Bits: 

126 """Create Bits from value according to the token_name and token_length""" 

127 b = Bits() 

128 self._dtype.set(b, value) 

129 if len(b) != self._dtype.length: 

130 raise ValueError(f"The value {value!r} has the wrong length for the format '{self._fmt}'.") 

131 return b 

132 

133 def __len__(self) -> int: 

134 return len(self.data) // self._dtype.length 

135 

136 @overload 

137 def __getitem__(self, key: slice) -> Array: 

138 ... 

139 

140 @overload 

141 def __getitem__(self, key: int) -> ElementType: 

142 ... 

143 

144 def __getitem__(self, key: Union[slice, int]) -> Union[Array, ElementType]: 

145 if isinstance(key, slice): 

146 start, stop, step = key.indices(len(self)) 

147 if step != 1: 

148 d = BitArray() 

149 for s in range(start * self._dtype.length, stop * self._dtype.length, step * self._dtype.length): 

150 d.append(self.data[s: s + self._dtype.length]) 

151 a = Array(self._dtype) 

152 a.data = d 

153 return a 

154 else: 

155 a = Array(self._dtype) 

156 a.data = self.data[start * self._dtype.length: stop * self._dtype.length] 

157 return a 

158 else: 

159 if key < 0: 

160 key += len(self) 

161 if key < 0 or key >= len(self): 

162 raise IndexError(f"Index {key} out of range for Array of length {len(self)}.") 

163 return self._dtype.get(self.data, start=self._dtype.length * key) 

164 

165 @overload 

166 def __setitem__(self, key: slice, value: Iterable[ElementType]) -> None: 

167 ... 

168 

169 @overload 

170 def __setitem__(self, key: int, value: ElementType) -> None: 

171 ... 

172 

173 def __setitem__(self, key: Union[slice, int], value: Union[Iterable[ElementType], ElementType]) -> None: 

174 if isinstance(key, slice): 

175 start, stop, step = key.indices(len(self)) 

176 if not isinstance(value, Iterable): 

177 raise TypeError("Can only assign an iterable to a slice.") 

178 if step == 1: 

179 new_data = BitArray() 

180 for x in value: 

181 new_data += self._create_element(x) 

182 self.data[start * self._dtype.length: stop * self._dtype.length] = new_data 

183 return 

184 items_in_slice = len(range(start, stop, step)) 

185 if not isinstance(value, Sized): 

186 value = list(value) 

187 if len(value) == items_in_slice: 

188 for s, v in zip(range(start, stop, step), value): 

189 self.data.overwrite(self._create_element(v), s * self._dtype.length) 

190 else: 

191 raise ValueError(f"Can't assign {len(value)} values to an extended slice of length {stop - start}.") 

192 else: 

193 if key < 0: 

194 key += len(self) 

195 if key < 0 or key >= len(self): 

196 raise IndexError(f"Index {key} out of range for Array of length {len(self)}.") 

197 start = self._dtype.length * key 

198 self.data.overwrite(self._create_element(value), start) 

199 return 

200 

201 def __delitem__(self, key: Union[slice, int]) -> None: 

202 if isinstance(key, slice): 

203 start, stop, step = key.indices(len(self)) 

204 if step == 1: 

205 self.data.__delitem__(slice(start * self._dtype.length, stop * self._dtype.length)) 

206 return 

207 # We need to delete from the end or the earlier positions will change 

208 r = reversed(range(start, stop, step)) if step > 0 else range(start, stop, step) 

209 for s in r: 

210 self.data.__delitem__(slice(s * self._dtype.length, (s + 1) * self._dtype.length)) 

211 else: 

212 if key < 0: 

213 key += len(self) 

214 if key < 0 or key >= len(self): 

215 raise IndexError 

216 start = self._dtype.length * key 

217 del self.data[start: start + self._dtype.length] 

218 

219 def __repr__(self) -> str: 

220 list_str = f"{self.tolist()}" 

221 trailing_bit_length = len(self.data) % self._dtype.length 

222 final_str = "" if trailing_bit_length == 0 else ", trailing_bits=" + repr( 

223 self.data[-trailing_bit_length:]) 

224 return f"Array('{self._fmt}', {list_str}{final_str})" 

225 

226 def astype(self, dtype: Union[str, Dtype]) -> Array: 

227 """Return Array with elements of new dtype, initialised from current Array.""" 

228 new_array = Array(dtype, self.tolist()) 

229 return new_array 

230 

231 def tolist(self) -> List[ElementType]: 

232 return [self._dtype.get(self.data, start=start) 

233 for start in range(0, len(self.data) - self._dtype.length + 1, self._dtype.length)] 

234 

235 def append(self, x: ElementType) -> None: 

236 if len(self.data) % self._dtype.length != 0: 

237 raise ValueError("Cannot append to Array as its length is not a multiple of the format length.") 

238 self.data += self._create_element(x) 

239 

240 def extend(self, iterable: Union[Array, array.array, Iterable]) -> None: 

241 if len(self.data) % self._dtype.length != 0: 

242 raise ValueError(f"Cannot extend Array as its data length ({len(self.data)} bits) is not a multiple of the format length ({self._dtype.length} bits).") 

243 if isinstance(iterable, Array): 

244 if self._dtype.name != iterable._dtype.name or self._dtype.length != iterable._dtype.length: 

245 raise TypeError( 

246 f"Cannot extend an Array with format '{self._fmt}' from an Array of format '{iterable._fmt}'.") 

247 # No need to iterate over the elements, we can just append the data 

248 self.data.append(iterable.data) 

249 elif isinstance(iterable, array.array): 

250 other_fmt = Array._array_typecodes.get(iterable.typecode, iterable.typecode) 

251 token_name, token_length, _ = tokenparser(other_fmt)[1][0] 

252 if self._dtype.name != token_name or self._dtype.length != token_length: 

253 raise ValueError( 

254 f"Cannot extend an Array with format '{self._fmt}' from an array with typecode '{iterable.typecode}'.") 

255 self.data += iterable.tobytes() 

256 else: 

257 if isinstance(iterable, str): 

258 raise TypeError("Can't extend an Array with a str.") 

259 for item in iterable: 

260 self.data += self._create_element(item) 

261 

262 def insert(self, i: int, x: ElementType) -> None: 

263 """Insert a new element into the Array at position i. 

264 

265 """ 

266 i = min(i, len(self)) # Inserting beyond len of array inserts at the end (copying standard behaviour) 

267 self.data.insert(self._create_element(x), i * self._dtype.length) 

268 

269 def pop(self, i: int = -1) -> ElementType: 

270 """Return and remove an element of the Array. 

271 

272 Default is to return and remove the final element. 

273 

274 """ 

275 if len(self) == 0: 

276 raise IndexError("Can't pop from an empty Array.") 

277 x = self[i] 

278 del self[i] 

279 return x 

280 

281 def byteswap(self) -> None: 

282 """Change the endianness in-place of all items in the Array. 

283 

284 If the Array format is not a whole number of bytes a ValueError will be raised. 

285 

286 """ 

287 if self._dtype.length % 8 != 0: 

288 raise ValueError( 

289 f"byteswap can only be used for whole-byte elements. The '{self._fmt}' format is {self._dtype.length} bits long.") 

290 self.data.byteswap(self.itemsize // 8) 

291 

292 def count(self, value: ElementType) -> int: 

293 """Return count of Array items that equal value. 

294 

295 value -- The quantity to compare each Array element to. Type should be appropriate for the Array format. 

296 

297 For floating point types using a value of float('nan') will count the number of elements that are NaN. 

298 

299 """ 

300 if math.isnan(value): 

301 return sum(math.isnan(i) for i in self) 

302 else: 

303 return sum(i == value for i in self) 

304 

305 def tobytes(self) -> bytes: 

306 """Return the Array data as a bytes object, padding with zero bits if needed. 

307 

308 Up to seven zero bits will be added at the end to byte align. 

309 

310 """ 

311 return self.data.tobytes() 

312 

313 def tofile(self, f: BinaryIO) -> None: 

314 """Write the Array data to a file object, padding with zero bits if needed. 

315 

316 Up to seven zero bits will be added at the end to byte align. 

317 

318 """ 

319 self.data.tofile(f) 

320 

321 def fromfile(self, f: BinaryIO, n: Optional[int] = None) -> None: 

322 trailing_bit_length = len(self.data) % self._dtype.length 

323 if trailing_bit_length != 0: 

324 raise ValueError(f"Cannot extend Array as its data length ({len(self.data)} bits) is not a multiple of the format length ({self._dtype.length} bits).") 

325 

326 new_data = Bits(f) 

327 max_items = len(new_data) // self._dtype.length 

328 items_to_append = max_items if n is None else min(n, max_items) 

329 self.data += new_data[0: items_to_append * self._dtype.length] 

330 if n is not None and items_to_append < n: 

331 raise EOFError(f"Only {items_to_append} were appended, not the {n} items requested.") 

332 

333 def reverse(self) -> None: 

334 trailing_bit_length = len(self.data) % self._dtype.length 

335 if trailing_bit_length != 0: 

336 raise ValueError(f"Cannot reverse the items in the Array as its data length ({len(self.data)} bits) is not a multiple of the format length ({self._dtype.length} bits).") 

337 for start_bit in range(0, len(self.data) // 2, self._dtype.length): 

338 start_swap_bit = len(self.data) - start_bit - self._dtype.length 

339 temp = self.data[start_bit: start_bit + self._dtype.length] 

340 self.data[start_bit: start_bit + self._dtype.length] = self.data[ 

341 start_swap_bit: start_swap_bit + self._dtype.length] 

342 self.data[start_swap_bit: start_swap_bit + self._dtype.length] = temp 

343 

344 def pp(self, fmt: Optional[str] = None, width: int = 120, 

345 show_offset: bool = False, stream: TextIO = sys.stdout) -> None: 

346 """Pretty-print the Array contents. 

347 

348 fmt -- Data format string. Defaults to current Array fmt. 

349 width -- Max width of printed lines in characters. Defaults to 120. A single group will always 

350 be printed per line even if it exceeds the max width. 

351 show_offset -- If True shows the element offset in the first column of each line. 

352 stream -- A TextIO object with a write() method. Defaults to sys.stdout. 

353 

354 """ 

355 sep = ' ' 

356 fmt_is_dtype = False 

357 if fmt is None: 

358 fmt = self.dtype 

359 fmt_is_dtype = True 

360 

361 tokens = tokenparser(fmt)[1] 

362 token_names_and_lengths = [(x[0], x[1]) for x in tokens] 

363 if len(token_names_and_lengths) not in [1, 2]: 

364 raise ValueError( 

365 f"Only one or two tokens can be used in an Array.pp() format - '{fmt}' has {len(token_names_and_lengths)} tokens.") 

366 token_name, token_length = token_names_and_lengths[0] 

367 token_name2, token_length2 = None, None 

368 getter_func2 = None 

369 if len(token_names_and_lengths) == 1: 

370 if token_length is None: 

371 token_length = self.itemsize 

372 fmt += str(token_length) 

373 if len(token_names_and_lengths) == 2: 

374 token_name2, token_length2 = token_names_and_lengths[1] 

375 if token_length is None and token_length2 is None: 

376 token_length = token_length2 = self.itemsize 

377 fmt += str(token_length) 

378 if token_length is None: 

379 token_length = token_length2 

380 if token_length2 is None: 

381 token_length2 = token_length 

382 if token_length != token_length2: 

383 raise ValueError(f"Two different format lengths specified ('{fmt}'). Either specify just one, or two the same length.") 

384 getter_func2 = functools.partial(Bits._name_to_read[token_name2], length=token_length2) 

385 

386 getter_func = functools.partial(Bits._name_to_read[token_name], length=token_length) 

387 

388 # Check that the getter functions will work 

389 temp = BitArray(token_length) 

390 try: 

391 getter_func(temp, 0) 

392 except InterpretError as e: 

393 raise ValueError(f"Pretty print format not valid: {e.msg}") 

394 if token_name2 is not None: 

395 try: 

396 getter_func2(temp, 0) 

397 except InterpretError as e: 

398 raise ValueError(f"Pretty print format not valid: {e.msg}") 

399 

400 trailing_bit_length = len(self.data) % token_length 

401 format_sep = " : " # String to insert on each line between multiple formats 

402 

403 if trailing_bit_length == 0: 

404 data = self.data 

405 else: 

406 data = self.data[0: -trailing_bit_length] 

407 length = len(self.data) // token_length 

408 parameter_name = "dtype" if fmt_is_dtype else "fmt" 

409 stream.write(f"<Array {parameter_name}='{fmt}', length={length}, itemsize={token_length} bits, total data size={(len(self.data) + 7) // 8} bytes>\n[\n") 

410 data._pp(token_name, token_name2, token_length, width, sep, format_sep, show_offset, stream, False, token_length, getter_func, getter_func2) 

411 stream.write("]") 

412 if trailing_bit_length != 0: 

413 stream.write(" + trailing_bits = " + str(self.data[-trailing_bit_length:])) 

414 stream.write("\n") 

415 

416 def equals(self, other: Any) -> bool: 

417 """Return True if format and all Array items are equal.""" 

418 if isinstance(other, Array): 

419 if self._dtype.length != other._dtype.length: 

420 return False 

421 if self._dtype.name != other._dtype.name: 

422 return False 

423 if self.data != other.data: 

424 return False 

425 return True 

426 elif isinstance(other, array.array): 

427 # Assume we are comparing with an array type 

428 if self.trailing_bits: 

429 return False 

430 # array's itemsize is in bytes, not bits. 

431 if self.itemsize != other.itemsize * 8: 

432 return False 

433 if len(self) != len(other): 

434 return False 

435 if self.tolist() != other.tolist(): 

436 return False 

437 return True 

438 return False 

439 

440 def __iter__(self) -> Iterable[ElementType]: 

441 start = 0 

442 for _ in range(len(self)): 

443 yield self._dtype.get(self.data, start=start) 

444 start += self._dtype.length 

445 

446 def __copy__(self) -> Array: 

447 a_copy = Array(self._fmt) 

448 a_copy.data = copy.copy(self.data) 

449 return a_copy 

450 

451 def _apply_op_to_all_elements(self, op, value: Union[int, float, None], is_comparison: bool = False) -> Array: 

452 """Apply op with value to each element of the Array and return a new Array""" 

453 new_array = Array('bool' if is_comparison else self._dtype) 

454 new_data = BitArray() 

455 failures = index = 0 

456 msg = '' 

457 if value is not None: 

458 def partial_op(a): 

459 return op(a, value) 

460 else: 

461 def partial_op(a): 

462 return op(a) 

463 for i in range(len(self)): 

464 v = self._dtype.get(self.data, start=self._dtype.length * i) 

465 try: 

466 new_data.append(new_array._create_element(partial_op(v))) 

467 except (CreationError, ZeroDivisionError, ValueError) as e: 

468 if failures == 0: 

469 msg = str(e) 

470 index = i 

471 failures += 1 

472 if failures != 0: 

473 raise ValueError(f"Applying operator '{op.__name__}' to Array caused {failures} errors. " 

474 f'First error at index {index} was: "{msg}"') 

475 new_array.data = new_data 

476 return new_array 

477 

478 def _apply_op_to_all_elements_inplace(self, op, value: Union[int, float]) -> Array: 

479 """Apply op with value to each element of the Array in place.""" 

480 # This isn't really being done in-place, but it's simpler and faster for now? 

481 new_data = BitArray() 

482 failures = index = 0 

483 msg = '' 

484 for i in range(len(self)): 

485 v = self._dtype.get(self.data, start=self._dtype.length * i) 

486 try: 

487 new_data.append(self._create_element(op(v, value))) 

488 except (CreationError, ZeroDivisionError, ValueError) as e: 

489 if failures == 0: 

490 msg = str(e) 

491 index = i 

492 failures += 1 

493 if failures != 0: 

494 raise ValueError(f"Applying operator '{op.__name__}' to Array caused {failures} errors. " 

495 f'First error at index {index} was: "{msg}"') 

496 self.data = new_data 

497 return self 

498 

499 def _apply_bitwise_op_to_all_elements(self, op, value: BitsType) -> Array: 

500 """Apply op with value to each element of the Array as an unsigned integer and return a new Array""" 

501 a_copy = self[:] 

502 a_copy._apply_bitwise_op_to_all_elements_inplace(op, value) 

503 return a_copy 

504 

505 def _apply_bitwise_op_to_all_elements_inplace(self, op, value: BitsType) -> Array: 

506 """Apply op with value to each element of the Array as an unsigned integer in place.""" 

507 value = BitArray._create_from_bitstype(value) 

508 if len(value) != self._dtype.length: 

509 raise ValueError(f"Bitwise op needs a bitstring of length {self._dtype.length} to match format {self._fmt}.") 

510 for start in range(0, len(self) * self._dtype.length, self._dtype.length): 

511 self.data[start: start + self._dtype.length] = op(self.data[start: start + self._dtype.length], value) 

512 return self 

513 

514 def _apply_op_between_arrays(self, op, other: Array, is_comparison: bool = False) -> Array: 

515 if len(self) != len(other): 

516 msg = f"Cannot operate element-wise on Arrays with different lengths ({len(self)} and {len(other)})." 

517 if op == operator.add or op == operator.iadd: 

518 msg += " Use extend() if you want to concatenate Arrays." 

519 raise ValueError(msg) 

520 if is_comparison: 

521 new_type = Dtype('bool') 

522 else: 

523 new_type = self._promotetype(self._dtype, other._dtype) 

524 new_array = Array(new_type) 

525 new_data = BitArray() 

526 failures = index = 0 

527 msg = '' 

528 for i in range(len(self)): 

529 a = self._dtype.get(self.data, start=self._dtype.length * i) 

530 b = other._dtype.get(other.data, start=other._dtype.length * i) 

531 try: 

532 new_data.append(new_array._create_element(op(a, b))) 

533 except (CreationError, ValueError, ZeroDivisionError) as e: 

534 if failures == 0: 

535 msg = str(e) 

536 index = i 

537 failures += 1 

538 if failures != 0: 

539 raise ValueError(f"Applying operator '{op.__name__}' between Arrays caused {failures} errors. " 

540 f'First error at index {index} was: "{msg}"') 

541 new_array.data = new_data 

542 return new_array 

543 

544 @classmethod 

545 def _promotetype(cls, type1: Dtype, type2: Dtype) -> Dtype: 

546 """When combining types which one wins? 

547 

548 1. We only deal with types representing floats or integers. 

549 2. One of the two types gets returned. We never create a new one. 

550 3. Floating point types always win against integer types. 

551 4. Signed integer types always win against unsigned integer types. 

552 5. Longer types win against shorter types. 

553 6. In a tie the first type wins against the second type. 

554 

555 """ 

556 if type1.is_float + type1.is_integer + type2.is_float + type2.is_integer != 2: 

557 raise ValueError(f"Only integer and floating point types can be combined - not '{type1}' and '{type2}'.") 

558 # If same type choose the widest 

559 if type1.name == type2.name: 

560 return type1 if type1.length > type2.length else type2 

561 # We choose floats above integers, irrespective of the widths 

562 if type1.is_float and type2.is_integer: 

563 return type1 

564 if type1.is_integer and type2.is_float: 

565 return type2 

566 if type1.is_float and type2.is_float: 

567 return type2 if type2.length > type1.length else type1 

568 assert type1.is_integer and type2.is_integer 

569 if type1.is_signed and not type2.is_signed: 

570 return type1 

571 if type2.is_signed and not type1.is_signed: 

572 return type2 

573 return type2 if type2.length > type1.length else type1 

574 

575 # Operators between Arrays or an Array and scalar value 

576 

577 def __add__(self, other: Union[int, float, Array]) -> Array: 

578 """Add int or float to all elements.""" 

579 if isinstance(other, Array): 

580 return self._apply_op_between_arrays(operator.add, other) 

581 return self._apply_op_to_all_elements(operator.add, other) 

582 

583 def __iadd__(self, other: Union[int, float, Array]) -> Array: 

584 if isinstance(other, Array): 

585 return self._apply_op_between_arrays(operator.add, other) 

586 return self._apply_op_to_all_elements_inplace(operator.add, other) 

587 

588 def __isub__(self, other: Union[int, float, Array]) -> Array: 

589 if isinstance(other, Array): 

590 return self._apply_op_between_arrays(operator.sub, other) 

591 return self._apply_op_to_all_elements_inplace(operator.sub, other) 

592 

593 def __sub__(self, other: Union[int, float, Array]) -> Array: 

594 if isinstance(other, Array): 

595 return self._apply_op_between_arrays(operator.sub, other) 

596 return self._apply_op_to_all_elements(operator.sub, other) 

597 

598 def __mul__(self, other: Union[int, float, Array]) -> Array: 

599 if isinstance(other, Array): 

600 return self._apply_op_between_arrays(operator.mul, other) 

601 return self._apply_op_to_all_elements(operator.mul, other) 

602 

603 def __imul__(self, other: Union[int, float, Array]) -> Array: 

604 if isinstance(other, Array): 

605 return self._apply_op_between_arrays(operator.mul, other) 

606 return self._apply_op_to_all_elements_inplace(operator.mul, other) 

607 

608 def __floordiv__(self, other: Union[int, float, Array]) -> Array: 

609 if isinstance(other, Array): 

610 return self._apply_op_between_arrays(operator.floordiv, other) 

611 return self._apply_op_to_all_elements(operator.floordiv, other) 

612 

613 def __ifloordiv__(self, other: Union[int, float, Array]) -> Array: 

614 if isinstance(other, Array): 

615 return self._apply_op_between_arrays(operator.floordiv, other) 

616 return self._apply_op_to_all_elements_inplace(operator.floordiv, other) 

617 

618 def __truediv__(self, other: Union[int, float, Array]) -> Array: 

619 if isinstance(other, Array): 

620 return self._apply_op_between_arrays(operator.truediv, other) 

621 return self._apply_op_to_all_elements(operator.truediv, other) 

622 

623 def __itruediv__(self, other: Union[int, float, Array]) -> Array: 

624 if isinstance(other, Array): 

625 return self._apply_op_between_arrays(operator.truediv, other) 

626 return self._apply_op_to_all_elements_inplace(operator.truediv, other) 

627 

628 def __rshift__(self, other: Union[int, Array]) -> Array: 

629 if isinstance(other, Array): 

630 return self._apply_op_between_arrays(operator.rshift, other) 

631 return self._apply_op_to_all_elements(operator.rshift, other) 

632 

633 def __lshift__(self, other: Union[int, Array]) -> Array: 

634 if isinstance(other, Array): 

635 return self._apply_op_between_arrays(operator.lshift, other) 

636 return self._apply_op_to_all_elements(operator.lshift, other) 

637 

638 def __irshift__(self, other: Union[int, Array]) -> Array: 

639 if isinstance(other, Array): 

640 return self._apply_op_between_arrays(operator.rshift, other) 

641 return self._apply_op_to_all_elements_inplace(operator.rshift, other) 

642 

643 def __ilshift__(self, other: Union[int, Array]) -> Array: 

644 if isinstance(other, Array): 

645 return self._apply_op_between_arrays(operator.lshift, other) 

646 return self._apply_op_to_all_elements_inplace(operator.lshift, other) 

647 

648 def __mod__(self, other: Union[int, Array]) -> Array: 

649 if isinstance(other, Array): 

650 return self._apply_op_between_arrays(operator.mod, other) 

651 return self._apply_op_to_all_elements(operator.mod, other) 

652 

653 def __imod__(self, other: Union[int, Array]) -> Array: 

654 if isinstance(other, Array): 

655 return self._apply_op_between_arrays(operator.mod, other) 

656 return self._apply_op_to_all_elements_inplace(operator.mod, other) 

657 

658 # Bitwise operators 

659 

660 def __and__(self, other: BitsType) -> Array: 

661 return self._apply_bitwise_op_to_all_elements(operator.iand, other) 

662 

663 def __iand__(self, other: BitsType) -> Array: 

664 return self._apply_bitwise_op_to_all_elements_inplace(operator.iand, other) 

665 

666 def __or__(self, other: BitsType) -> Array: 

667 return self._apply_bitwise_op_to_all_elements(operator.ior, other) 

668 

669 def __ior__(self, other: BitsType) -> Array: 

670 return self._apply_bitwise_op_to_all_elements_inplace(operator.ior, other) 

671 

672 def __xor__(self, other: BitsType) -> Array: 

673 return self._apply_bitwise_op_to_all_elements(operator.ixor, other) 

674 

675 def __ixor__(self, other: BitsType) -> Array: 

676 return self._apply_bitwise_op_to_all_elements_inplace(operator.ixor, other) 

677 

678 # Reverse operators between a scalar value and an Array 

679 

680 def __rmul__(self, other: Union[int, float]) -> Array: 

681 return self._apply_op_to_all_elements(operator.mul, other) 

682 

683 def __radd__(self, other: Union[int, float]) -> Array: 

684 return self._apply_op_to_all_elements(operator.add, other) 

685 

686 def __rsub__(self, other: Union[int, float]) -> Array: 

687 # i - A == (-A) + i 

688 neg = self._apply_op_to_all_elements(operator.neg, None) 

689 return neg._apply_op_to_all_elements(operator.add, other) 

690 

691 def __rand__(self, other: BitsType) -> Array: 

692 return self._apply_bitwise_op_to_all_elements(operator.iand, other) 

693 

694 def __ror__(self, other: BitsType) -> Array: 

695 return self._apply_bitwise_op_to_all_elements(operator.ior, other) 

696 

697 def __rxor__(self, other: BitsType) -> Array: 

698 return self._apply_bitwise_op_to_all_elements(operator.ixor, other) 

699 

700 # Comparison operators 

701 

702 def __lt__(self, other: Union[int, float, Array]) -> Array: 

703 if isinstance(other, Array): 

704 return self._apply_op_between_arrays(operator.lt, other, is_comparison=True) 

705 return self._apply_op_to_all_elements(operator.lt, other, is_comparison=True) 

706 

707 def __gt__(self, other: Union[int, float, Array]) -> Array: 

708 if isinstance(other, Array): 

709 return self._apply_op_between_arrays(operator.gt, other, is_comparison=True) 

710 return self._apply_op_to_all_elements(operator.gt, other, is_comparison=True) 

711 

712 def __ge__(self, other: Union[int, float, Array]) -> Array: 

713 if isinstance(other, Array): 

714 return self._apply_op_between_arrays(operator.ge, other, is_comparison=True) 

715 return self._apply_op_to_all_elements(operator.ge, other, is_comparison=True) 

716 

717 def __le__(self, other: Union[int, float, Array]) -> Array: 

718 if isinstance(other, Array): 

719 return self._apply_op_between_arrays(operator.le, other, is_comparison=True) 

720 return self._apply_op_to_all_elements(operator.le, other, is_comparison=True) 

721 

722 def __eq__(self, other: Union[int, float, str, BitsType, Array]) -> Array: 

723 if isinstance(other, Array): 

724 return self._apply_op_between_arrays(operator.eq, other, is_comparison=True) 

725 return self._apply_op_to_all_elements(operator.eq, other, is_comparison=True) 

726 

727 def __ne__(self, other: Union[int, float, str, BitsType, Array]) -> Array: 

728 if isinstance(other, Array): 

729 return self._apply_op_between_arrays(operator.ne, other, is_comparison=True) 

730 return self._apply_op_to_all_elements(operator.ne, other, is_comparison=True) 

731 

732 # Unary operators 

733 

734 def __neg__(self): 

735 return self._apply_op_to_all_elements(operator.neg, None) 

736 

737 def __abs__(self): 

738 return self._apply_op_to_all_elements(operator.abs, None)