Module array provides a specialized data type array.array for storing sequences of homogeneous numbers. Unlike the universal list, array.array arrays provide more efficient memory usage and increased performance when working with numerical data.
- 📦 Key advantages of
array.array - 🔹 Comparative table:
array.arrayvslist
📦 Key advantages of array.array
The key difference between array.array and list is compact data storage. Instead of a list of pointers to Python objects, array.array stores values as a contiguous block of bytes, which makes it ideal for the following tasks.
1. Memory savings when working with large sets of numbers
When processing millions of numerical elements, memory savings become critically important. array.array significantly reduces overhead.
import array
import sys
def compare_memory_usage(num_elements: int = 1_000_000) -> None:
"""
Compares memory usage between list and array.array.
Args:
num_elements (int, optional): Number of elements for the test.
Defaults to 1,000,000.
"""
<em># Create a list with integer Python objects</em>
list_numbers = list(range(num_elements))
<em># Create an array where numbers are stored as 4-byte C-type ints</em>
array_numbers = array.array('i', range(num_elements))
list_size = sys.getsizeof(list_numbers)
array_size = sys.getsizeof(array_numbers)
print(f"Number of elements: {num_elements}")
print(f"List size: {list_size / 1024 / 1024:.2f} MB")
print(f"Array size: {array_size / 1024 / 1024:.2f} MB")
if array_size > 0:
print(f"Memory savings: {list_size / array_size:.2f}x")
<em># Example usage</em>
if __name__ == "__main__":
compare_memory_usage()
Output:
Количество элементов: 1000000
Размер list: 7.63 MB
Размер array: 3.82 MB
Экономия памяти: 2.00x
2. Increased performance of numerical operations
Due to contiguous memory allocation, mathematical operations on array.array elements are performed faster, as the processor can make more efficient use of the cache.
import array
import timeit
def compare_performance(num_elements: int = 10_000_000) -> None:
"""
Compares the performance of summing elements in list and array.array.
Args:
num_elements (int, optional): Number of elements for the test.
Defaults to 10,000,000.
"""
setup_code = f"""
import array
data = range({num_elements})
list_data = list(data)
array_data = array.array('i', data)
"""
<em># Measure time for list</em>
list_time = timeit.timeit("sum(list_data)", setup=setup_code, number=10)
<em># Measure time for array</em>
array_time = timeit.timeit("sum(array_data)", setup=setup_code, number=10)
print(f"Time to sum {num_elements} elements (10 times):")
print(f"list: {list_time:.4f} seconds")
print(f"array: {array_time:.4f} seconds")
<em># Example usage</em>
if __name__ == "__main__":
compare_performance()
Output:
Количество элементов: 10000000
Размер list: 2.1106 секунд
Размер array: 1.1549 секунд
3. Direct work with C libraries (ctypes, struct)
array.array is ideal for passing data to low-level libraries written in C, as its internal structure is compatible with C arrays.
Example with ctypes:
import array
from ctypes import c_double, CDLL
def demonstrate_ctypes_usage() -> None:
"""
Demonstrates passing array.array to a C function via ctypes.
"""
<em># Array with double-precision numbers (type 'd')</em>
py_array = array.array('d', [1.1, 2.2, 3.3, 4.4])
<em># Create a C-compatible array from py_array</em>
<em># The function (c_double * len(py_array)) creates a type "array of 4 c_double"</em>
<em># (*py_array) unpacks the python array into the arguments of this constructor</em>
c_array = (c_double * len(py_array))(*py_array)
<em># Here could be a call to a C function, for example:</em>
<em># my_c_library = CDLL("./libmath.so")</em>
<em># my_c_library.sum_doubles(c_array, len(c_array))</em>
print(f"Python array: {py_array}")
print(f"C-compatible array (ctypes): {[val for val in c_array]}")
<em># Example usage</em>
if __name__ == "__main__":
demonstrate_ctypes_usage()
Example with struct for data packing:
import array
import struct
def demonstrate_struct_packing(data: list[int]) -> bytes:
"""
Packs an array of integers into a binary string.
Args:
data (list[int]): List of integers to pack.
Returns:
bytes: Binary representation of the data.
"""
arr = array.array('i', data)
<em># Create a format string like '3i' for 3 integers</em>
format_string = f'{len(arr)}i'
<em># Pack data into binary format</em>
binary_data = struct.pack(format_string, *arr)
print(f"Original array: {arr}")
print(f"Binary data: {binary_data}")
<em># Check: unpack back</em>
unpacked_data = struct.unpack(format_string, binary_data)
print(f"Unpacked data: {unpacked_data}")
return binary_data
<em># Example usage</em>
if __name__ == "__main__":
demonstrate_struct_packing([10, 20, 30])
4. Efficient serialization and deserialization
The .tobytes() and .frombytes() methods allow quickly converting an array to bytes and back, which is ideal for saving to files or transmitting over a network.
import array
def handle_binary_data() -> None:
"""
Demonstrates serialization and deserialization of array.array to bytes.
"""
<em># Create original array</em>
source_array = array.array('i', [1, 2, 3, 4, 5])
print(f"Original array: {source_array}")
<em># Serialize array to bytes</em>
binary_data = source_array.tobytes()
print(f"Data in bytes: {binary_data}")
<em># Deserialize from bytes to new array</em>
new_array = array.array('i')
new_array.frombytes(binary_data)
print(f"Restored array: {new_array}")
<em># Check integrity</em>
assert source_array == new_array, "Data mismatch!"
print("Data integrity confirmed.")
<em># Example usage</em>
if __name__ == "__main__":
handle_binary_data()
5. Guarantee of type homogeneity
array.array strictly enforces only one data type, specified at creation. This prevents accidental addition of elements of a different type.
import array
def demonstrate_type_safety() -> None:
"""
Shows that array.array does not allow adding elements of a different type.
"""
arr = array.array('i', [100, 200, 300])
print(f"Integer array: {arr}")
try:
<em># Attempt to add a string element</em>
arr.append('hello')
except TypeError as e:
<em># Expected exception</em>
print(f"\nAttempt to add 'hello' raised an error: {e}")
print("This confirms strict array typing.")
<em># Example usage</em>
if __name__ == "__main__":
demonstrate_type_safety()
6. Direct writing and reading from binary files
The .tofile() and .fromfile() methods simplify working with binary files, avoiding intermediate serialization.
import array
from pathlib import Path
def work_with_binary_files(file_path_str: str = "data.bin") -> None:
"""
Writes an array to a binary file and reads it back.
Args:
file_path_str (str, optional): File name for saving.
Defaults to "data.bin".
"""
file_path = Path(file_path_str)
source_array = array.array('f', [1.5, 2.7, 3.14])
try:
<em># Write to file</em>
with file_path.open('wb') as f:
source_array.tofile(f)
print(f"Array {source_array} written to file '{file_path}'.")
<em># Read from file</em>
new_array = array.array('f')
with file_path.open('rb') as f:
<em># Read 3 elements of type 'f' (float)</em>
new_array.fromfile(f, len(source_array))
print(f"Array {new_array} read from file.")
assert source_array == new_array
finally:
<em># Guaranteed file deletion after execution</em>
if file_path.exists():
file_path.unlink()
print(f"Temporary file '{file_path}' deleted.")
<em># Example usage</em>
if __name__ == "__main__":
work_with_binary_files()
🔹 Comparative table: array.array vs list
| Characteristic | array.array | list |
|---|---|---|
| Data type | Homogeneous primitives (numbers, characters) | Any Python objects |
| Memory | Low consumption | High consumption |
| Performance | High for numerical operations | Lower for numerical operations |
| API | Limited set of methods | Rich and flexible API |
| C compatibility | High, direct data transfer | Conversions required |
| Binary serialization | Built-in methods (.tobytes, .tofile) | Requires struct, pickle etc. |
Conclusion:
🚀 Use array.array when working with large volumes of homogeneous numerical data, and when performance and efficient memory usage are critical for you.
For most everyday tasks where flexibility and storage of heterogeneous data are required, list remains the best choice.