Python lists are flexible, readable, and appropriate for most application code. However, they are not always the best representation for hundreds of thousands of numbers that all share the same machine type. The standard-library Python array module stores homogeneous numeric values in a compact contiguous block. Each item uses a fixed binary representation selected by a type code, which can reduce memory use and simplify integration with files, buffers, and lower-level APIs.
The module is intentionally small. It does not replace NumPy for scientific computing, multidimensional data, broadcasting, or vectorized mathematics. Its strength is providing a dependency-free numeric container with familiar sequence operations and direct binary conversion.
This guide complements our articles about bisect, mmap, shelve, filecmp, and NumPy.
When an array is useful
Use array.array when every element is compatible with the same C numeric type and you need a mutable sequence that is denser than a normal list. Typical examples include sensor samples, audio values, counters, indexes, coordinates, binary records, and chunks received from a network service.
A list stores references to Python objects. An array stores the values directly. The exact memory advantage depends on the type code, platform, allocator, and list contents, so benchmark the real workload rather than assuming a fixed saving.
Create an array
from array import array
values = array("i", [10, 20, 30, 40])
print(values)
print(values[0])The first argument is the type code. Here, i means a signed C integer. Its exact byte width can be platform dependent, so inspect itemsize when a binary format requires a known representation.
Important type codes
Common integer codes include b and B for one-byte signed and unsigned values, h and H for short integers, i and I for integers, and q and Q for 64-bit values where supported. Floating-point data normally uses f or d.
Do not choose a code only because its name looks appropriate. Verify sign, range, and itemsize. Appending an out-of-range integer raises OverflowError, which protects the program from silent truncation.
Add and remove elements
data = array("H")
data.append(100)
data.extend([200, 300, 400])
data.insert(1, 150)
last = data.pop()
data.remove(200)The API resembles a list. It supports append, extend, insert, pop, remove, reverse, count, and index. Every inserted element must be valid for the selected type.
Indexing and slicing
samples = array("h", [2, 4, 6, 8, 10])
print(samples[1:4])
samples[1:3] = array("h", [40, 60])A slice produces another array with the same type code. Slice assignment also expects a compatible array. That restriction prevents accidental mixing of binary representations.
Measure storage
values = array("d", [1.5, 2.5, 3.5])
print(values.itemsize)
print(len(values) * values.itemsize)The multiplication gives the bytes occupied by the elements, excluding the small container overhead. Comparing this number with sys.getsizeof() on a list requires care because a list’s reported size does not include every referenced numeric object.
Convert to and from lists
regular_list = values.tolist()
more = array("d")
more.fromlist([4.5, 5.5])tolist() is useful when another API expects normal Python objects. fromlist() validates incoming elements before adding them. When handling untrusted application input, perform explicit conversion and range checks before passing values to the array.
Convert to bytes
numbers = array("I", [1, 2, 3])
block = numbers.tobytes()
copy = array("I")
copy.frombytes(block)tobytes() exports the native in-memory representation. frombytes() requires a byte count divisible by itemsize. Reject malformed lengths early instead of letting an invalid network packet or file fragment reach later processing.
Byte order and portability
The bytes produced by an array normally use the host machine’s native byte order. A file created on a little-endian machine may be interpreted differently on a big-endian machine.
import sys
if sys.byteorder != "little":
numbers.byteswap()byteswap() reverses the byte order inside each element. Long-lived formats and network protocols should explicitly define their byte order. Record the type width as well; a vague statement such as “native integer” is not a portable format specification.
Read and write binary files
data = array("f", [0.5, 1.5, 2.5])
with open("samples.bin", "wb") as file:
data.tofile(file)
loaded = array("f")
with open("samples.bin", "rb") as file:
loaded.fromfile(file, 3)fromfile() receives an item count, not a byte count. If the file ends early, it raises EOFError, but some items may already have been appended. Applications should validate the expected file length and discard partial state when completeness is mandatory.
Use the buffer protocol
Arrays implement Python’s buffer protocol, allowing a memoryview to access their storage without copying.
values = array("i", [10, 20, 30])
view = memoryview(values)
print(view.format, view.itemsize)
view[0] = 99The assignment updates the original array. While a view exports its buffer, operations that resize the array may fail. Release or delete views before appending, inserting, or otherwise changing the storage size.
Interoperate without extra copies
Many native extensions, compression functions, hashing APIs, sockets, and system calls accept buffer-compatible objects. Passing an array directly can avoid an intermediate conversion to bytes.
Buffer compatibility does not guarantee semantic compatibility. The receiving API must agree on item width, alignment, signedness, byte order, and total length. Document those properties at every integration boundary.
Validate numeric ranges
small = array("B")
small.append(255)
# small.append(256) # OverflowErrorValidate user-controlled values before insertion. For integer arrays, check lower and upper bounds. For floating-point arrays, decide how the application handles NaN, infinity, negative zero, and precision loss.
Array versus list
Choose a list for heterogeneous objects, nested application structures, or code where flexibility and readability matter more than memory density. Choose an array for homogeneous numeric values, binary interoperability, or a compact mutable buffer.
An array does not automatically make Python loops faster. A loop that sums or transforms elements still runs Python-level operations unless another native API processes the buffer.
Array versus NumPy
NumPy supports multidimensional arrays, many data types, broadcasting, linear algebra, vectorized functions, and an extensive scientific ecosystem. The standard array module is smaller, available without installation, and appropriate for straightforward one-dimensional storage.
For numerical analysis, images, matrices, or large transformations, NumPy is usually the better tool. For a command-line utility, compact protocol field, or small binary reader, array may be all that is needed.
Mutation and concurrency
Arrays are mutable and do not provide high-level synchronization. Multiple threads or processes modifying shared data require locks or a carefully designed protocol. A memoryview does not make a multi-step update atomic.
Common mistakes
- Selecting a type code without checking
itemsize. - Writing native-endian data into a format that requires fixed endianness.
- Confusing items with bytes.
- Appending numbers outside the valid range.
- Resizing while exported memory views are alive.
- Expecting NumPy-style vectorized operations.
- Trusting an external binary block without checking size and format.
Practical checklist
- Document type code, signedness, width, and byte order.
- Validate numeric ranges before insertion.
- Use context managers for files.
- Check that byte lengths are multiples of
itemsize. - Release views before resizing.
- Benchmark memory and speed with realistic data.
- Move to NumPy when vectorized computation becomes important.
Conclusion
The Python array module provides compact homogeneous numeric sequences with a familiar mutable interface. It can convert directly to bytes, read and write binary files, and expose storage through the buffer protocol.
Safe use depends on selecting the correct type, validating ranges, and defining a portable binary representation. See the official array documentation and the buffer protocol documentation for complete details.







