Модуль array надає спеціалізований тип даних array.array для зберігання послідовностей однорідних чисел. На відміну від універсального list, масиви array.array забезпечують ефективніше використання пам’яті та підвищену продуктивність при роботі з числовими даними.
- 📦 Ключові переваги
array.array - 🔹 Порівняльна таблиця:
array.arrayvslist
📦 Ключові переваги array.array
Ключова відмінність array.array від list — компактне зберігання даних. Замість списку вказівників на об’єкти Python, array.array зберігає значення як суцільний блок байтів, що робить його ідеальним для таких завдань.
1. Економія пам’яті при роботі з великими наборами чисел
При обробці мільйонів числових елементів економія пам’яті стає критично важливою. array.array значно зменшує накладні витрати.
import array
import sys
def compare_memory_usage(num_elements: int = 1_000_000) -> None:
"""
Порівнює використання пам'яті між list та array.array.
Args:
num_elements (int, optional): Кількість елементів для тесту.
За замовчуванням 1 000 000.
"""
# Створити список з цілочисельними об'єктами Python
list_numbers = list(range(num_elements))
# Створити масив, де числа зберігаються як 4-байтні цілі числа типу C
array_numbers = array.array('i', range(num_elements))
list_size = sys.getsizeof(list_numbers)
array_size = sys.getsizeof(array_numbers)
print(f"Кількість елементів: {num_elements}")
print(f"Розмір list: {list_size / 1024 / 1024:.2f} МБ")
print(f"Розмір array: {array_size / 1024 / 1024:.2f} МБ")
if array_size > 0:
print(f"Економія пам'яті: {list_size / array_size:.2f}x")
# Приклад використання
if __name__ == "__main__":
compare_memory_usage()
Вивід:
Кількість елементів: 1000000
Розмір list: 7.63 МБ
Розмір array: 3.82 МБ
Економія пам'яті: 2.00x
2. Підвищена продуктивність числових операцій
Завдяки суцільному розподілу пам’яті математичні операції над елементами array.array виконуються швидше, оскільки процесор може ефективніше використовувати кеш.
import array
import timeit
def compare_performance(num_elements: int = 10_000_000) -> None:
"""
Порівнює продуктивність підсумовування елементів у list та array.array.
Args:
num_elements (int, optional): Кількість елементів для тесту.
За замовчуванням 10 000 000.
"""
setup_code = f"""
import array
data = range({num_elements})
list_data = list(data)
array_data = array.array('i', data)
"""
# Виміряти час для списку
list_time = timeit.timeit("sum(list_data)", setup=setup_code, number=10)
# Виміряти час для масиву
array_time = timeit.timeit("sum(array_data)", setup=setup_code, number=10)
print(f"Час на підсумовування {num_elements} елементів (10 разів):")
print(f"list: {list_time:.4f} секунд")
print(f"array: {array_time:.4f} секунд")
# Приклад використання
if __name__ == "__main__":
compare_performance()
Вивід:
Час на підсумовування 10000000 елементів (10 разів):
list: 2.1106 секунд
array: 1.1549 секунд
3. Пряма робота з бібліотеками C (ctypes, struct)
array.array ідеально підходить для передачі даних у низькорівневі бібліотеки, написані на C, оскільки його внутрішня структура сумісна з масивами C.
Приклад з ctypes:
import array
from ctypes import c_double, CDLL
def demonstrate_ctypes_usage() -> None:
"""
Демонструє передачу array.array у C-функцію через ctypes.
"""
# Масив із числами подвійної точності (тип 'd')
py_array = array.array('d', [1.1, 2.2, 3.3, 4.4])
# Створити C-сумісний масив з py_array
# Функція (c_double * len(py_array)) створює тип "масив з 4 c_double"
# (*py_array) розпаковує масив python в аргументи цього конструктора
c_array = (c_double * len(py_array))(*py_array)
# Тут міг би бути виклик C-функції, наприклад:
# my_c_library = CDLL("./libmath.so")
# my_c_library.sum_doubles(c_array, len(c_array))
print(f"Масив Python: {py_array}")
print(f"C-сумісний масив (ctypes): {[val for val in c_array]}")
# Приклад використання
if __name__ == "__main__":
demonstrate_ctypes_usage()
Приклад з struct для пакування даних:
import array
import struct
def demonstrate_struct_packing(data: list[int]) -> bytes:
"""
Пакує масив цілих чисел у бінарний рядок.
Args:
data (list[int]): Список цілих чисел для пакування.
Returns:
bytes: Бінарне представлення даних.
"""
arr = array.array('i', data)
# Створити рядок форматування, наприклад '3i' для 3 цілих чисел
format_string = f'{len(arr)}i'
# Запакувати дані в бінарний формат
binary_data = struct.pack(format_string, *arr)
print(f"Оригінальний масив: {arr}")
print(f"Бінарні дані: {binary_data}")
# Перевірка: розпакувати назад
unpacked_data = struct.unpack(format_string, binary_data)
print(f"Розпаковані дані: {unpacked_data}")
return binary_data
# Приклад використання
if __name__ == "__main__":
demonstrate_struct_packing([10, 20, 30])
4. Ефективна серіалізація та десеріалізація
Методи .tobytes() та .frombytes() дозволяють швидко перетворювати масив у байти і назад, що ідеально підходить для збереження у файли або передачі по мережі.
import array
def handle_binary_data() -> None:
"""
Демонструє серіалізацію та десеріалізацію array.array у байти.
"""
# Створити вихідний масив
source_array = array.array('i', [1, 2, 3, 4, 5])
print(f"Вихідний масив: {source_array}")
# Серіалізувати масив у байти
binary_data = source_array.tobytes()
print(f"Дані в байтах: {binary_data}")
# Десеріалізувати з байтів у новий масив
new_array = array.array('i')
new_array.frombytes(binary_data)
print(f"Відновлений масив: {new_array}")
# Перевірити цілісність
assert source_array == new_array, "Невідповідність даних!"
print("Цілісність даних підтверджено.")
# Приклад використання
if __name__ == "__main__":
handle_binary_data()
5. Гарантія однорідності типів
array.array суворо вимагає використання лише одного типу даних, зазначеного при створенні. Це запобігає випадковому додаванню елементів іншого типу.
import array
def demonstrate_type_safety() -> None:
"""
Показує, що array.array не дозволяє додавати елементи іншого типу.
"""
arr = array.array('i', [100, 200, 300])
print(f"Цілочисельний масив: {arr}")
try:
# Спроба додати рядковий елемент
arr.append('hello')
except TypeError as e:
# Очікуваний виняток
print(f"\nСпроба додати 'hello' викликала помилку: {e}")
print("Це підтверджує сувору типізацію масиву.")
# Приклад використання
if __name__ == "__main__":
demonstrate_type_safety()
6. Прямий запис та читання з бінарних файлів
Методи .tofile() та .fromfile() спрощують роботу з бінарними файлами, уникаючи проміжної серіалізації.
import array
from pathlib import Path
def work_with_binary_files(file_path_str: str = "data.bin") -> None:
"""
Записує масив у бінарний файл і читає його назад.
Args:
file_path_str (str, optional): Ім'я файлу для збереження.
За замовчуванням "data.bin".
"""
file_path = Path(file_path_str)
source_array = array.array('f', [1.5, 2.7, 3.14])
try:
# Записати у файл
with file_path.open('wb') as f:
source_array.tofile(f)
print(f"Масив {source_array} записано у файл '{file_path}'.")
# Прочитати з файлу
new_array = array.array('f')
with file_path.open('rb') as f:
# Прочитати 3 елементи типу 'f' (float)
new_array.fromfile(f, len(source_array))
print(f"Масив {new_array} прочитано з файлу.")
assert source_array == new_array
finally:
# Гарантоване видалення файлу після виконання
if file_path.exists():
file_path.unlink()
print(f"Тимчасовий файл '{file_path}' видалено.")
# Приклад використання
if __name__ == "__main__":
work_with_binary_files()
🔹 Порівняльна таблиця: array.array vs list
| Характеристика | array.array | list |
|---|---|---|
| Тип даних | Однорідні примітиви (числа, символи) | Будь-які об’єкти Python |
| Пам’ять | Низьке споживання | Високе споживання |
| Продуктивність | Висока для числових операцій | Нижча для числових операцій |
| API | Обмежений набір методів | Багатий та гнучкий API |
| Сумісність із C | Висока, пряма передача даних | Потрібні перетворення |
| Бінарна серіалізація | Вбудовані методи (.tobytes, .tofile) | Вимагає struct, pickle тощо |
Висновок:
🚀 Використовуйте array.array, коли працюєте з великими обсягами однорідних числових даних, і коли для вас критично важливі продуктивність та ефективне використання пам’яті.
Для більшості повсякденних завдань, де потрібна гнучкість і зберігання різнорідних даних, list залишається найкращим вибором.