Moduł array dostarcza wyspecjalizowany typ danych array.array do przechowywania sekwencji jednorodnych liczb. W przeciwieństwie do uniwersalnej listy (list), tablice array.array zapewniają bardziej efektywne wykorzystanie pamięci i zwiększoną wydajność podczas pracy z danymi numerycznymi.
- 📦 Kluczowe zalety
array.array - 🔹 Tabela porównawcza:
array.arrayvslist
📦 Kluczowe zalety array.array
Kluczową różnicą między array.array a list jest kompaktowe przechowywanie danych. Zamiast listy wskaźników do obiektów Pythona, array.array przechowuje wartości jako ciągły blok bajtów, co czyni go idealnym do następujących zadań.
1. Oszczędność pamięci przy pracy z dużymi zbiorami liczb
Podczas przetwarzania milionów elementów numerycznych oszczędność pamięci staje się niezwykle ważna. array.array znacznie zmniejsza narzut pamięciowy.
import array
import sys
def compare_memory_usage(num_elements: int = 1_000_000) -> None:
"""
Porównuje zużycie pamięci między listą a array.array.
Args:
num_elements (int, optional): Liczba elementów do testu.
Domyślnie 1 000 000.
"""
# Utwórz listę z obiektami całkowitymi Pythona
list_numbers = list(range(num_elements))
# Utwórz tablicę, w której liczby są przechowywane jako 4-bajtowe liczby całkowite typu C
array_numbers = array.array('i', range(num_elements))
list_size = sys.getsizeof(list_numbers)
array_size = sys.getsizeof(array_numbers)
print(f"Liczba elementów: {num_elements}")
print(f"Rozmiar listy: {list_size / 1024 / 1024:.2f} MB")
print(f"Rozmiar tablicy: {array_size / 1024 / 1024:.2f} MB")
if array_size > 0:
print(f"Oszczędność pamięci: {list_size / array_size:.2f}x")
# Przykład użycia
if __name__ == "__main__":
compare_memory_usage()
Wynik:
Liczba elementów: 1000000
Rozmiar listy: 7.63 MB
Rozmiar tablicy: 3.82 MB
Oszczędność pamięci: 2.00x
2. Zwiększona wydajność operacji numerycznych
Dzięki ciągłej alokacji pamięci, operacje matematyczne na elementach array.array są wykonywane szybciej, ponieważ procesor może efektywniej wykorzystywać pamięć podręczną (cache).
import array
import timeit
def compare_performance(num_elements: int = 10_000_000) -> None:
"""
Porównuje wydajność sumowania elementów w liście i array.array.
Args:
num_elements (int, optional): Liczba elementów do testu.
Domyślnie 10 000 000.
"""
setup_code = f"""
import array
data = range({num_elements})
list_data = list(data)
array_data = array.array('i', data)
"""
# Zmierz czas dla listy
list_time = timeit.timeit("sum(list_data)", setup=setup_code, number=10)
# Zmierz czas dla tablicy
array_time = timeit.timeit("sum(array_data)", setup=setup_code, number=10)
print(f"Czas sumowania {num_elements} elementów (10 razy):")
print(f"lista: {list_time:.4f} sekund")
print(f"tablica: {array_time:.4f} sekund")
# Przykład użycia
if __name__ == "__main__":
compare_performance()
Wynik:
Czas sumowania 10000000 elementów (10 razy):
lista: 2.1106 sekund
tablica: 1.1549 sekund
3. Bezpośrednia praca z bibliotekami C (ctypes, struct)
array.array jest idealne do przekazywania danych do niskopoziomowych bibliotek napisanych w C, ponieważ jego wewnętrzna struktura jest kompatybilna z tablicami C.
Przykład z ctypes:
import array
from ctypes import c_double, CDLL
def demonstrate_ctypes_usage() -> None:
"""
Demonstruje przekazywanie array.array do funkcji C za pomocą ctypes.
"""
# Tablica z liczbami podwójnej precyzji (typ 'd')
py_array = array.array('d', [1.1, 2.2, 3.3, 4.4])
# Utwórz tablicę kompatybilną z C z py_array
# Funkcja (c_double * len(py_array)) tworzy typ "tablica 4 c_double"
# (*py_array) rozpakowuje tablicę Pythona do argumentów tego konstruktora
c_array = (c_double * len(py_array))(*py_array)
# Tutaj mogłoby być wywołanie funkcji C, na przykład:
# my_c_library = CDLL("./libmath.so")
# my_c_library.sum_doubles(c_array, len(c_array))
print(f"Tablica Pythona: {py_array}")
print(f"Tablica kompatybilna z C (ctypes): {[val for val in c_array]}")
# Przykład użycia
if __name__ == "__main__":
demonstrate_ctypes_usage()
Przykład z struct do pakowania danych:
import array
import struct
def demonstrate_struct_packing(data: list[int]) -> bytes:
"""
Pakuje tablicę liczb całkowitych do postaci binarnej.
Args:
data (list[int]): Lista liczb całkowitych do spakowania.
Returns:
bytes: Binarna reprezentacja danych.
"""
arr = array.array('i', data)
# Utwórz ciąg formatujący, np. '3i' dla 3 liczb całkowitych
format_string = f'{len(arr)}i'
# Spakuj dane do formatu binarnego
binary_data = struct.pack(format_string, *arr)
print(f"Oryginalna tablica: {arr}")
print(f"Dane binarne: {binary_data}")
# Sprawdzenie: rozpakuj z powrotem
unpacked_data = struct.unpack(format_string, binary_data)
print(f"Dane rozpakowane: {unpacked_data}")
return binary_data
# Przykład użycia
if __name__ == "__main__":
demonstrate_struct_packing([10, 20, 30])
4. Wydajna serializacja i deserializacja
Metody .tobytes() i .frombytes() pozwalają szybko konwertować tablicę na bajty i z powrotem, co jest idealne do zapisywania w plikach lub przesyłania przez sieć.
import array
def handle_binary_data() -> None:
"""
Demonstruje serializację i deserializację array.array do bajtów.
"""
# Utwórz oryginalną tablicę
source_array = array.array('i', [1, 2, 3, 4, 5])
print(f"Oryginalna tablica: {source_array}")
# Serializuj tablicę do bajtów
binary_data = source_array.tobytes()
print(f"Dane w bajtach: {binary_data}")
# Deserializuj z bajtów do nowej tablicy
new_array = array.array('i')
new_array.frombytes(binary_data)
print(f"Odtworzona tablica: {new_array}")
# Sprawdź integralność
assert source_array == new_array, "Niezgodność danych!"
print("Potwierdzono integralność danych.")
# Przykład użycia
if __name__ == "__main__":
handle_binary_data()
5. Gwarancja jednorodności typu
array.array ściśle wymusza tylko jeden typ danych, określony przy tworzeniu. Zapobiega to przypadkowemu dodaniu elementów innego typu.
import array
def demonstrate_type_safety() -> None:
"""
Pokazuje, że array.array nie pozwala na dodawanie elementów innego typu.
"""
arr = array.array('i', [100, 200, 300])
print(f"Tablica liczb całkowitych: {arr}")
try:
# Próba dodania elementu typu string
arr.append('hello')
except TypeError as e:
# Oczekiwany wyjątek
print(f"\nPróba dodania 'hello' zgłosiła błąd: {e}")
print("To potwierdza ścisłe typowanie tablicy.")
# Przykład użycia
if __name__ == "__main__":
demonstrate_type_safety()
6. Bezpośredni zapis i odczyt z plików binarnych
Metody .tofile() i .fromfile() upraszczają pracę z plikami binarnymi, unikając pośredniej serializacji.
import array
from pathlib import Path
def work_with_binary_files(file_path_str: str = "data.bin") -> None:
"""
Zapisuje tablicę do pliku binarnego i odczytuje ją z powrotem.
Args:
file_path_str (str, optional): Nazwa pliku do zapisu.
Domyślnie "data.bin".
"""
file_path = Path(file_path_str)
source_array = array.array('f', [1.5, 2.7, 3.14])
try:
# Zapisz do pliku
with file_path.open('wb') as f:
source_array.tofile(f)
print(f"Tablica {source_array} zapisana do pliku '{file_path}'.")
# Odczytaj z pliku
new_array = array.array('f')
with file_path.open('rb') as f:
# Odczytaj 3 elementy typu 'f' (float)
new_array.fromfile(f, len(source_array))
print(f"Tablica {new_array} odczytana z pliku.")
assert source_array == new_array
finally:
# Gwarantowane usunięcie pliku po wykonaniu
if file_path.exists():
file_path.unlink()
print(f"Plik tymczasowy '{file_path}' usunięty.")
# Przykład użycia
if __name__ == "__main__":
work_with_binary_files()
🔹 Tabela porównawcza: array.array vs list
| Cecha | array.array | list |
|---|---|---|
| Typ danych | Jednorodne typy proste (liczby, znaki) | Dowolne obiekty Pythona |
| Pamięć | Niskie zużycie | Wysokie zużycie |
| Wydajność | Wysoka dla operacji numerycznych | Niższa dla operacji numerycznych |
| API | Ograniczony zestaw metod | Bogate i elastyczne API |
| Zgodność z C | Wysoka, bezpośrednie przekazywanie danych | Wymaga konwersji |
| Serializacja binarna | Wbudowane metody (.tobytes, .tofile) | Wymaga struct, pickle itp. |
Wniosek:
🚀 Używaj array.array, gdy pracujesz z dużymi ilościami jednorodnych danych numerycznych oraz gdy kluczowe są dla Ciebie wydajność i efektywne wykorzystanie pamięci.
Do większości codziennych zadań, gdzie wymagana jest elastyczność i przechowywanie różnorodnych danych, lista (list) pozostaje najlepszym wyborem.