Przejdź do treści
> 💻 🧠 Kod 1001 > 🧪📓 Bez kategorii > 🧑‍💻 Użycie array.array w Pythonie: kiedy i dlaczego warto stosować

🧑‍💻 Użycie array.array w Pythonie: kiedy i dlaczego warto stosować


📦 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

Cechaarray.arraylist
Typ danychJednorodne typy proste (liczby, znaki)Dowolne obiekty Pythona
PamięćNiskie zużycieWysokie zużycie
WydajnośćWysoka dla operacji numerycznychNiższa dla operacji numerycznych
APIOgraniczony zestaw metodBogate i elastyczne API
Zgodność z CWysoka, bezpośrednie przekazywanie danychWymaga konwersji
Serializacja binarnaWbudowane 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.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Wymagane pola są oznaczone *