Numpy is a python library for efficiently dealing with arrays. Under the hood, it can leverage C and Fortran to achieve those efficient array operations.
 It’s important to note that if you want to use numpy for a single element, use np.array([1]) as opposed to np.array(1) or np.uint8(1). Operations on np.uint8 or a scalar np.array (such as np.array(1)) aren’t guaranteed to return a numpy data type or to behave properly:

In [35]: type(np.array(10000) * 1000000000000000000000000000000) Out[35]: int

In [36]: a = np.array(10000) In [37]: a *= 1000000000000000000000000000000  TypeError Traceback (most recent call last) in () > 1 a *= 1000000000000000000000000000000 TypeError: ufunc 'multiply' output (typecode 'O') could not be coerced to provided output parameter (typecode 'l') according to the casting rule ''same_kind''

Although it’s also important to remember that, under normal operation, you need to deal with overflow. Depending on your application, overflow can be a desirable thing. Here’s an example of overflow:
In [1]: import numpy as np In [3]: a = np.array([255], dtype=np.uint8) In [4]: a Out[4]: array([255], dtype=uint8) In [5]: a+1 Out[5]: array([0], dtype=uint8)
Instead of 255+1 becoming 256, it became 0, because 255 is the maximum value a uint8 can hold, so when 1 was added to it, all the bits were flipped from 1s to 0s, i.e. from 11111111 to 00000000. uint8: “u” means unsigned, as in no negative numbers; int means integers, as in no decimal places; 8 means 8 bits, as in 8 digits, each of which is either a 0 or a 1.