Einen benutzerdefinierten Container erstellen

PythonPythonBeginner
Jetzt üben

This tutorial is from open-source community. Access the source code

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

In diesem Lab lernen Sie etwas über Python - Container und Speicherverwaltung. Sie werden untersuchen, wie Python den Speicher für eingebaute Datenstrukturen verwaltet, und entdecken, wie Sie eine speichereffiziente benutzerdefinierte Containerklasse erstellen können.

Die Ziele dieses Labs sind es, das Verhalten der Speicherzuweisung von Python - Listen und - Wörterbüchern zu untersuchen, eine benutzerdefinierte Containerklasse zu erstellen, um die Speicherauslastung zu optimieren, und die Vorteile der spaltenorientierten Datenspeicherung zu verstehen.

Dies ist ein Guided Lab, das schrittweise Anweisungen bietet, um Ihnen beim Lernen und Üben zu helfen. Befolgen Sie die Anweisungen sorgfältig, um jeden Schritt abzuschließen und praktische Erfahrungen zu sammeln. Historische Daten zeigen, dass dies ein Labor der Stufe Anfänger mit einer Abschlussquote von 93% ist. Es hat eine positive Bewertungsrate von 97% von den Lernenden erhalten.

Verständnis der Speicherzuweisung von Listen

In Python sind Listen eine sehr nützliche Datenstruktur, insbesondere wenn Sie Elemente hinzufügen müssen. Python - Listen sind so konzipiert, dass Anhängeeffizient sind. Anstatt genau die benötigte Speichermenge zuzuweisen, weist Python zusätzlichen Speicher im Voraus für zukünftige Hinzufügungen zu. Diese Strategie minimiert die Anzahl der erforderlichen Speicherumzuweisungen, wenn die Liste wächst.

Lassen Sie uns dieses Konzept besser verstehen, indem wir die Funktion sys.getsizeof() verwenden. Diese Funktion gibt die Größe eines Objekts in Bytes zurück, was uns hilft, zu sehen, wie viel Speicher eine Liste in verschiedenen Stadien verwendet.

  1. Zunächst müssen Sie in Ihrem Terminal eine interaktive Python - Shell öffnen. Dies ist wie ein Spielplatz, auf dem Sie sofort Python - Code ausführen können. Um sie zu öffnen, geben Sie den folgenden Befehl in Ihrem Terminal ein und drücken Sie die Eingabetaste:
python3
  1. Sobald Sie in der interaktiven Python - Shell sind, müssen Sie das Modul sys importieren. Module in Python sind wie Werkzeugkästen, die nützliche Funktionen enthalten. Das Modul sys hat die von uns benötigte Funktion getsizeof(). Nach dem Importieren des Moduls erstellen Sie eine leere Liste namens items. Hier ist der Code dafür:
import sys
items = []
  1. Jetzt überprüfen wir die anfängliche Größe der leeren Liste. Wir verwenden die Funktion sys.getsizeof() mit der Liste items als Argument. Geben Sie den folgenden Code in der interaktiven Python - Shell ein und drücken Sie die Eingabetaste:
sys.getsizeof(items)

Sie sollten einen Wert wie 64 Bytes sehen. Dieser Wert repräsentiert die Overhead - Kosten für eine leere Liste. Die Overhead - Kosten sind die grundlegende Speichermenge, die Python verwendet, um die Liste zu verwalten, auch wenn sie keine Elemente enthält.

  1. Als Nächstes fügen wir der Liste nacheinander Elemente hinzu und beobachten, wie sich die Speicherzuweisung ändert. Wir verwenden die Methode append(), um ein Element zur Liste hinzuzufügen, und überprüfen dann erneut die Größe. Hier ist der Code:
items.append(1)
sys.getsizeof(items)

Sie sollten einen größeren Wert, etwa 96 Bytes, sehen. Diese Größenzunahme zeigt, dass Python mehr Speicher zugewiesen hat, um das neue Element aufzunehmen.

  1. Lassen Sie uns weiterhin mehr Elemente zur Liste hinzufügen und die Größe nach jeder Hinzufügung überprüfen. Hier ist der Code dafür:
items.append(2)
sys.getsizeof(items)  ## Größe bleibt gleich

items.append(3)
sys.getsizeof(items)  ## Größe bleibt weiterhin unverändert

items.append(4)
sys.getsizeof(items)  ## Größe bleibt weiterhin unverändert

items.append(5)
sys.getsizeof(items)  ## Größe springt auf einen größeren Wert

Sie werden feststellen, dass die Größe nicht bei jeder Anhängeoperation zunimmt. Stattdessen springt sie periodisch auf einen höheren Wert. Dies zeigt, dass Python Speicher in Blöcken zuweist, anstatt für jedes neue Element individuell.

Dieses Verhalten ist beabsichtigt. Python weist zunächst mehr Speicher zu, als benötigt wird, um häufige Umzuweisungen zu vermeiden, wenn die Liste wächst. Jedes Mal, wenn die Liste ihre aktuelle Kapazität überschreitet, weist Python einen größeren Speicherblock zu.

Denken Sie daran, dass eine Liste Verweise auf Objekte speichert, nicht die Objekte selbst. Auf einem 64 - Bit - System benötigt jeder Verweis typischerweise 8 Bytes Speicher. Dies ist wichtig zu verstehen, da es daraufhinwirkt, wie viel Speicher eine Liste tatsächlich verwendet, wenn sie verschiedene Objekttypen enthält.

Speicherzuweisung von Wörterbüchern

In Python sind Wörterbücher (Dictionaries) genauso wie Listen eine grundlegende Datenstruktur. Ein wichtiger Aspekt, den man verstehen sollte, ist, wie sie Speicher zuweisen. Die Speicherzuweisung bezieht sich darauf, wie Python im Computer-Speicher Platz reserviert, um die Daten in Ihrem Wörterbuch zu speichern. Ähnlich wie Listen weisen Python-Wörterbücher auch Speicher in Blöcken zu. Lassen Sie uns untersuchen, wie die Speicherzuweisung bei Wörterbüchern funktioniert.

  1. Zunächst müssen wir ein Wörterbuch erstellen, mit dem wir arbeiten können. In derselben Python-Shell (oder öffnen Sie eine neue, wenn Sie die alte geschlossen haben) erstellen wir ein Wörterbuch, das einen Datensatz darstellt. Ein Wörterbuch in Python ist eine Sammlung von Schlüssel-Wert-Paaren, wobei jeder Schlüssel eindeutig ist und zum Zugriff auf den entsprechenden Wert verwendet wird.
import sys  ## Import sys if you're starting a new session
row = {'route': '22', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354}

Hier haben wir das Modul sys importiert, das Zugang zu einigen Variablen bietet, die vom Python-Interpreter verwendet oder verwaltet werden, sowie zu Funktionen, die stark mit dem Interpreter interagieren. Anschließend haben wir ein Wörterbuch namens row mit vier Schlüssel-Wert-Paaren erstellt.

  1. Nun, da wir unser Wörterbuch haben, möchten wir seine anfängliche Größe überprüfen. Die Größe eines Wörterbuchs bezieht sich auf die Menge an Speicher, die es im Computer belegt.
sys.getsizeof(row)

Die Funktion sys.getsizeof() gibt die Größe eines Objekts in Bytes zurück. Wenn Sie diesen Code ausführen, sollten Sie einen Wert um die 240 Bytes sehen. Dies gibt Ihnen eine Vorstellung davon, wie viel Speicher das Wörterbuch anfänglich beansprucht.

  1. Als Nächstes fügen wir dem Wörterbuch neue Schlüssel-Wert-Paare hinzu und beobachten, wie sich die Speicherzuweisung ändert. Das Hinzufügen von Elementen zu einem Wörterbuch ist eine häufige Operation, und es ist wichtig zu verstehen, wie sie sich auf den Speicher auswirkt.
row['a'] = 1
sys.getsizeof(row)  ## Size might remain the same

row['b'] = 2
sys.getsizeof(row)  ## Size may increase

Wenn Sie das erste Schlüssel-Wert-Paar ('a': 1) hinzufügen, kann die Größe des Wörterbuchs gleich bleiben. Dies liegt daran, dass Python bereits einen bestimmten Speicherblock zugewiesen hat und möglicherweise genügend Platz in diesem Block für das neue Element vorhanden ist. Wenn Sie jedoch das zweite Schlüssel-Wert-Paar ('b': 2) hinzufügen, kann die Größe zunehmen. Sie werden feststellen, dass die Größe des Wörterbuchs plötzlich zunimmt, nachdem eine bestimmte Anzahl von Elementen hinzugefügt wurde. Dies liegt daran, dass Wörterbücher wie Listen Speicher in Blöcken zuweisen, um die Leistung zu optimieren. Das Zuweisen von Speicher in Blöcken reduziert die Anzahl der Mal, in denen Python vom System mehr Speicher anfordern muss, was den Prozess des Hinzufügens neuer Elemente beschleunigt.

  1. Versuchen wir, ein Element aus dem Wörterbuch zu entfernen, um zu sehen, ob der Speicherverbrauch sinkt. Das Entfernen von Elementen aus einem Wörterbuch ist ebenfalls eine häufige Operation, und es ist interessant zu sehen, wie es sich auf den Speicher auswirkt.
del row['b']
sys.getsizeof(row)

Interessanterweise reduziert das Entfernen eines Elements normalerweise nicht die Speicherzuweisung. Dies liegt daran, dass Python den zugewiesenen Speicher beibehält, um eine erneute Speicherzuweisung zu vermeiden, wenn erneut Elemente hinzugefügt werden. Die erneute Speicherzuweisung ist in Bezug auf die Leistung eine relativ teure Operation, daher versucht Python, sie so weit wie möglich zu vermeiden.

Überlegungen zur Speichereffizienz:

Wenn Sie mit großen Datensätzen arbeiten, bei denen Sie viele Datensätze erstellen müssen, ist die Verwendung von Wörterbüchern für jeden Datensatz möglicherweise nicht der speichereffizienteste Ansatz. Wörterbücher sind sehr flexibel und einfach zu verwenden, aber sie können eine beträchtliche Menge an Speicher verbrauchen, insbesondere wenn es um eine große Anzahl von Datensätzen geht. Hier sind einige Alternativen, die weniger Speicher verbrauchen:

  • Tupel: Einfache unveränderliche Sequenzen. Ein Tupel ist eine Sammlung von Werten, die nach der Erstellung nicht geändert werden können. Es verbraucht weniger Speicher als ein Wörterbuch, da es keine Schlüssel speichern und die zugehörige Schlüssel-Wert-Zuordnung verwalten muss.
  • Benannte Tupel (Named tuples): Tupel mit Feldnamen. Benannte Tupel sind ähnlich wie normale Tupel, aber sie ermöglichen es Ihnen, die Werte anhand des Namens zuzugreifen, was den Code lesbarer machen kann. Sie verbrauchen auch weniger Speicher als Wörterbücher.
  • Klassen mit __slots__: Klassen, die explizit Attribute definieren, um die Verwendung eines Wörterbuchs für Instanzvariablen zu vermeiden. Wenn Sie __slots__ in einer Klasse verwenden, erstellt Python kein Wörterbuch, um die Instanzvariablen zu speichern, was den Speicherverbrauch reduziert.

Diese Alternativen können den Speicherverbrauch bei der Verarbeitung vieler Datensätze erheblich reduzieren.

Optimierung des Speichers mit spaltenorientierten Daten

Bei der traditionellen Datenspeicherung speichern wir oft jeden Datensatz als separates Wörterbuch (Dictionary), was als zeilenorientierter Ansatz bezeichnet wird. Dieser Ansatz kann jedoch eine beträchtliche Menge an Speicher verbrauchen. Eine Alternative besteht darin, die Daten spaltenweise zu speichern. Im spaltenorientierten Ansatz erstellen wir für jedes Attribut eine separate Liste, und jede Liste enthält alle Werte für dieses spezifische Attribut. Dies kann uns helfen, Speicher zu sparen.

  1. Zunächst müssen Sie in Ihrem Projektverzeichnis eine neue Python - Datei erstellen. Diese Datei wird den Code zum Lesen von Daten in spaltenorientierter Weise enthalten. Benennen Sie die Datei readrides.py. Sie können die folgenden Befehle im Terminal ausführen, um dies zu erreichen:
cd ~/project
touch readrides.py

Der Befehl cd ~/project wechselt das aktuelle Verzeichnis in Ihr Projektverzeichnis, und der Befehl touch readrides.py erstellt eine neue leere Datei namens readrides.py.

  1. Öffnen Sie als Nächstes die Datei readrides.py im WebIDE - Editor. Fügen Sie dann den folgenden Python - Code in die Datei ein. Dieser Code definiert eine Funktion read_rides_as_columns, die Busfahrtdaten aus einer CSV - Datei liest und sie in vier separaten Listen speichert, wobei jede Liste eine Spalte der Daten darstellt.
## readrides.py
import csv
import sys
import tracemalloc

def read_rides_as_columns(filename):
    '''
    Read the bus ride data into 4 lists, representing columns
    '''
    routes = []
    dates = []
    daytypes = []
    numrides = []
    with open(filename) as f:
        rows = csv.reader(f)
        headings = next(rows)     ## Skip headers
        for row in rows:
            routes.append(row[0])
            dates.append(row[1])
            daytypes.append(row[2])
            numrides.append(int(row[3]))
    return dict(routes=routes, dates=dates, daytypes=daytypes, numrides=numrides)

In diesem Code importieren wir zunächst die erforderlichen Module csv, sys und tracemalloc. Das Modul csv wird verwendet, um CSV - Dateien zu lesen, sys kann für systembezogene Operationen verwendet werden (wird in dieser Funktion jedoch nicht verwendet), und tracemalloc wird für die Speicheranalyse verwendet. Innerhalb der Funktion initialisieren wir vier leere Listen, um verschiedene Spalten der Daten zu speichern. Dann öffnen wir die Datei, überspringen die Kopfzeile und iterieren durch jede Zeile in der Datei, indem wir die entsprechenden Werte an die passenden Listen anhängen. Schließlich geben wir ein Wörterbuch zurück, das diese vier Listen enthält.

  1. Analysieren wir nun, warum der spaltenorientierte Ansatz Speicher sparen kann. Wir tun dies in der Python - Shell. Führen Sie den folgenden Code aus:
import readrides
import tracemalloc

## Estimate memory for row-oriented approach
nrows = 577563     ## Number of rows in original file
dict_overhead = 240  ## Approximate dictionary overhead in bytes
row_memory = nrows * dict_overhead
print(f"Estimated memory for row-oriented data: {row_memory} bytes ({row_memory/1024/1024:.2f} MB)")

## Estimate memory for column-oriented approach
pointer_size = 8   ## Size of a pointer in bytes on 64-bit systems
column_memory = nrows * 4 * pointer_size  ## 4 columns with one pointer per entry
print(f"Estimated memory for column-oriented data: {column_memory} bytes ({column_memory/1024/1024:.2f} MB)")

## Estimate savings
savings = row_memory - column_memory
print(f"Estimated memory savings: {savings} bytes ({savings/1024/1024:.2f} MB)")

In diesem Code importieren wir zunächst das gerade erstellte Modul readrides und das Modul tracemalloc. Dann schätzen wir den Speicherverbrauch für den zeilenorientierten Ansatz ab. Wir gehen davon aus, dass jedes Wörterbuch einen Overhead von 240 Bytes hat, und multiplizieren diesen mit der Anzahl der Zeilen in der ursprünglichen Datei, um den gesamten Speicherverbrauch für die zeilenorientierten Daten zu erhalten. Für den spaltenorientierten Ansatz gehen wir davon aus, dass auf einem 64 - Bit - System jeder Zeiger 8 Bytes benötigt. Da wir 4 Spalten haben und pro Eintrag einen Zeiger, berechnen wir den gesamten Speicherverbrauch für die spaltenorientierten Daten. Schließlich berechnen wir die Speichereinsparung, indem wir den Speicherverbrauch des spaltenorientierten Ansatzes vom Speicherverbrauch des zeilenorientierten Ansatzes subtrahieren.

Diese Berechnung zeigt, dass der spaltenorientierte Ansatz im Vergleich zum zeilenorientierten Ansatz mit Wörterbüchern etwa 120 MB Speicher sparen sollte.

  1. Überprüfen wir dies, indem wir den tatsächlichen Speicherverbrauch mit dem Modul tracemalloc messen. Führen Sie den folgenden Code aus:
## Start tracking memory
tracemalloc.start()

## Read the data
columns = readrides.read_rides_as_columns('ctabus.csv')

## Get current and peak memory usage
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage: {current/1024/1024:.2f} MB")
print(f"Peak memory usage: {peak/1024/1024:.2f} MB")

## Stop tracking memory
tracemalloc.stop()

In diesem Code starten wir zunächst die Speicherüberwachung mit tracemalloc.start(). Dann rufen wir die Funktion read_rides_as_columns auf, um die Daten aus der Datei ctabus.csv zu lesen. Danach verwenden wir tracemalloc.get_traced_memory(), um den aktuellen und den maximalen Speicherverbrauch zu erhalten. Schließlich stoppen wir die Speicherüberwachung mit tracemalloc.stop().

Die Ausgabe zeigt Ihnen den tatsächlichen Speicherverbrauch Ihrer spaltenorientierten Datenstruktur. Dieser sollte deutlich geringer sein als unsere theoretische Schätzung für den zeilenorientierten Ansatz.

Die erheblichen Speichereinsparungen resultieren aus der Eliminierung des Overheads von Tausenden von Wörterbuchobjekten. Jedes Wörterbuch in Python hat einen festen Overhead, unabhängig davon, wie viele Elemente es enthält. Durch die Verwendung der spaltenorientierten Speicherung benötigen wir nur wenige Listen anstelle von Tausenden von Wörterbüchern.

Erstellen einer benutzerdefinierten Containerklasse

Bei der Datenverarbeitung ist der spaltenorientierte Ansatz hervorragend geeignet, um Speicher zu sparen. Allerdings kann es Probleme geben, wenn Ihr bestehender Code erwartet, dass die Daten in Form einer Liste von Wörterbüchern (Dictionaries) vorliegen. Um dieses Problem zu lösen, erstellen wir eine benutzerdefinierte Containerklasse. Diese Klasse wird eine zeilenorientierte Schnittstelle bieten, was bedeutet, dass sie für Ihren Code wie eine Liste von Wörterbüchern aussehen und verhalten wird. Intern wird sie jedoch die Daten in spaltenorientierter Form speichern, was uns hilft, Speicher zu sparen.

  1. Öffnen Sie zunächst die Datei readrides.py im WebIDE - Editor. Wir werden dieser Datei eine neue Klasse hinzufügen. Diese Klasse wird die Grundlage für unseren benutzerdefinierten Container bilden.
## Add this to readrides.py
from collections.abc import Sequence

class RideData(Sequence):
    def __init__(self):
        ## Each value is a list with all of the values (a column)
        self.routes = []
        self.dates = []
        self.daytypes = []
        self.numrides = []

    def __len__(self):
        ## All lists assumed to have the same length
        return len(self.routes)

    def __getitem__(self, index):
        return {'route': self.routes[index],
                'date': self.dates[index],
                'daytype': self.daytypes[index],
                'rides': self.numrides[index]}

    def append(self, d):
        self.routes.append(d['route'])
        self.dates.append(d['date'])
        self.daytypes.append(d['daytype'])
        self.numrides.append(d['rides'])

In diesem Code definieren wir eine Klasse namens RideData, die von Sequence erbt. Die Methode __init__ initialisiert vier leere Listen, wobei jede Liste eine Spalte der Daten darstellt. Die Methode __len__ gibt die Länge des Containers zurück, die der Länge der routes - Liste entspricht. Die Methode __getitem__ ermöglicht es uns, einen bestimmten Datensatz anhand des Indexes zuzugreifen und ihn als Wörterbuch zurückzugeben. Die Methode append fügt einen neuen Datensatz zum Container hinzu, indem sie die Werte an jede Spaltenliste anhängt.

  1. Jetzt benötigen wir eine Funktion, um die Busfahrtdaten in unseren benutzerdefinierten Container einzulesen. Fügen Sie die folgende Funktion zur Datei readrides.py hinzu.
## Add this to readrides.py
def read_rides_as_dicts(filename):
    '''
    Read the bus ride data as a list of dicts, but use our custom container
    '''
    records = RideData()
    with open(filename) as f:
        rows = csv.reader(f)
        headings = next(rows)     ## Skip headers
        for row in rows:
            route = row[0]
            date = row[1]
            daytype = row[2]
            rides = int(row[3])
            record = {
                'route': route,
                'date': date,
                'daytype': daytype,
                'rides': rides
            }
            records.append(record)
    return records

Diese Funktion erstellt eine Instanz der Klasse RideData und füllt sie mit Daten aus der CSV - Datei. Sie liest jede Zeile aus der Datei, extrahiert die relevanten Informationen, erstellt für jeden Datensatz ein Wörterbuch und fügt es dann dem RideData - Container hinzu. Das Wichtigste ist, dass sie die gleiche Schnittstelle wie eine Liste von Wörterbüchern aufrechterhält, aber die Daten intern spaltenweise speichert.

  1. Testen wir unseren benutzerdefinierten Container in der Python - Shell. Dies wird uns helfen, zu überprüfen, ob er wie erwartet funktioniert.
import readrides

## Read the data using our custom container
rows = readrides.read_rides_as_dicts('ctabus.csv')

## Check the type of the returned object
type(rows)  ## Should be readrides.RideData

## Check the length
len(rows)   ## Should be 577563

## Access individual records
rows[0]     ## Should return a dictionary for the first record
rows[1]     ## Should return a dictionary for the second record
rows[2]     ## Should return a dictionary for the third record

Unser benutzerdefinierter Container implementiert erfolgreich die Sequence - Schnittstelle, was bedeutet, dass er sich wie eine Liste verhält. Sie können die Funktion len() verwenden, um die Anzahl der Datensätze im Container zu erhalten, und Sie können die Indizierung verwenden, um einzelne Datensätze zuzugreifen. Jeder Datensatz scheint ein Wörterbuch zu sein, obwohl die Daten intern spaltenweise gespeichert sind. Dies ist großartig, da bestehender Code, der eine Liste von Wörterbüchern erwartet, weiterhin mit unserem benutzerdefinierten Container ohne jegliche Modifikationen funktioniert.

  1. Schließlich messen wir den Speicherverbrauch unseres benutzerdefinierten Containers. Dies wird uns zeigen, wie viel Speicher wir im Vergleich zu einer Liste von Wörterbüchern sparen.
import tracemalloc

tracemalloc.start()
rows = readrides.read_rides_as_dicts('ctabus.csv')
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage: {current/1024/1024:.2f} MB")
print(f"Peak memory usage: {peak/1024/1024:.2f} MB")
tracemalloc.stop()

Wenn Sie diesen Code ausführen, sollten Sie feststellen, dass der Speicherverbrauch ähnlich dem des spaltenorientierten Ansatzes ist, der viel geringer ist als der einer Liste von Wörterbüchern. Dies zeigt den Vorteil unseres benutzerdefinierten Containers in Bezug auf die Speichereffizienz.

✨ Lösung prüfen und üben

Verbesserung des benutzerdefinierten Containers für Slicing

Unser benutzerdefinierter Container eignet sich hervorragend für den Zugriff auf einzelne Datensätze. Allerdings gibt es ein Problem, wenn es um Slicing geht. Wenn Sie versuchen, einen Teil unseres Containers auszuschneiden (slicen), ist das Ergebnis nicht das, was Sie normalerweise erwarten würden.

Lassen Sie uns verstehen, warum dies passiert. In Python ist Slicing eine gängige Operation, um einen Teil einer Sequenz zu extrahieren. Aber für unseren benutzerdefinierten Container weiß Python nicht, wie es ein neues RideData - Objekt nur mit den ausgeschnittenen Daten erstellen soll. Stattdessen erstellt es eine Liste, die die Ergebnisse des Aufrufs von __getitem__ für jeden Index im Schnitt enthält.

  1. Testen wir das Slicing in der Python - Shell:
import readrides

rows = readrides.read_rides_as_dicts('ctabus.csv')
r = rows[0:10]  ## Take a slice of the first 10 records
type(r)  ## This will likely be a list, not a RideData object
print(r)  ## This might look like a list of numbers, not dictionaries

In diesem Code importieren wir zunächst das Modul readrides. Dann lesen wir die Daten aus der Datei ctabus.csv in eine Variable rows. Wenn wir versuchen, einen Schnitt der ersten 10 Datensätze zu nehmen und den Typ des Ergebnisses überprüfen, stellen wir fest, dass es eine Liste anstelle eines RideData - Objekts ist. Das Drucken des Ergebnisses könnte etwas Unerwartetes anzeigen, wie eine Liste von Zahlen anstelle von Wörterbüchern.

  1. Modifizieren wir unsere RideData - Klasse, um Slicing richtig zu behandeln. Öffnen Sie readrides.py und aktualisieren Sie die Methode __getitem__:
def __getitem__(self, index):
    if isinstance(index, slice):
        ## Handle slice
        result = RideData()
        result.routes = self.routes[index]
        result.dates = self.dates[index]
        result.daytypes = self.daytypes[index]
        result.numrides = self.numrides[index]
        return result
    else:
        ## Handle single index
        return {'route': self.routes[index],
                'date': self.dates[index],
                'daytype': self.daytypes[index],
                'rides': self.numrides[index]}

In dieser aktualisierten Methode __getitem__ überprüfen wir zunächst, ob der index ein Schnitt ist. Wenn dies der Fall ist, erstellen wir ein neues RideData - Objekt namens result. Dann füllen wir dieses neue Objekt mit Schnitten der ursprünglichen Datenspalten (routes, dates, daytypes und numrides). Dies stellt sicher, dass wir beim Slicing unseres benutzerdefinierten Containers ein weiteres RideData - Objekt anstelle einer Liste erhalten. Wenn der index kein Schnitt ist (d. h., es ist ein einzelner Index), geben wir ein Wörterbuch zurück, das den relevanten Datensatz enthält.

  1. Testen wir unsere verbesserte Slicing - Fähigkeit:
import readrides

rows = readrides.read_rides_as_dicts('ctabus.csv')
r = rows[0:10]  ## Take a slice of the first 10 records
type(r)  ## Should now be readrides.RideData
len(r)   ## Should be 10
r[0]     ## Should be the same as rows[0]
r[1]     ## Should be the same as rows[1]

Nachdem wir die Methode __getitem__ aktualisiert haben, können wir das Slicing erneut testen. Wenn wir einen Schnitt der ersten 10 Datensätze nehmen, sollte der Typ des Ergebnisses jetzt readrides.RideData sein. Die Länge des Schnitts sollte 10 sein, und der Zugriff auf einzelne Elemente im Schnitt sollte die gleichen Ergebnisse liefern wie der Zugriff auf die entsprechenden Elemente im ursprünglichen Container.

  1. Sie können auch mit verschiedenen Schnittmustern testen:
## Get every other record from the first 20
r2 = rows[0:20:2]
len(r2)  ## Should be 10

## Get the last 10 records
r3 = rows[-10:]
len(r3)  ## Should be 10

Hier testen wir verschiedene Schnittmuster. Der erste Schnitt rows[0:20:2] holt jedes zweite Element aus den ersten 20 Datensätzen, und die Länge des resultierenden Schnitts sollte 10 sein. Der zweite Schnitt rows[-10:] holt die letzten 10 Datensätze, und seine Länge sollte ebenfalls 10 sein.

Durch die korrekte Implementierung des Slicings verhält sich unser benutzerdefinierter Container jetzt noch mehr wie eine Standard - Python - Liste, während er gleichzeitig die Speichereffizienz der spaltenorientierten Speicherung beibehält.

Dieser Ansatz, benutzerdefinierte Containerklassen zu erstellen, die integrierte Python - Container imitieren, aber eine andere interne Darstellung haben, ist eine leistungsstarke Technik, um den Speicherverbrauch zu optimieren, ohne die Schnittstelle zu ändern, die Ihr Code den Benutzern bietet.

Zusammenfassung

In diesem Lab haben Sie mehrere wichtige Fähigkeiten erlernt. Zunächst haben Sie das Verhalten der Speicherzuweisung in Python - Listen und - Wörterbüchern (Dictionaries) untersucht und gelernt, den Speicherverbrauch zu optimieren, indem Sie von einer zeilenorientierten zur spaltenorientierten Datenspeicherung wechseln. Zweitens haben Sie eine benutzerdefinierte Containerklasse erstellt, die die ursprüngliche Schnittstelle beibehält, dabei aber weniger Speicher verbraucht, und diese Klasse so verbessert, dass sie Slicing - Operationen richtig behandelt.

Diese Techniken sind von hohem Wert, wenn Sie mit großen Datensätzen arbeiten, da sie den Speicherverbrauch erheblich reduzieren können, ohne die Schnittstelle Ihres Codes zu ändern. Die Fähigkeit, benutzerdefinierte Containerklassen zu erstellen, die integrierte Python - Container imitieren, aber eine andere interne Darstellung haben, ist ein leistungsstarkes Optimierungswerkzeug für Python - Anwendungen. Sie können diese Konzepte auf andere speicherkritische Projekte anwenden, insbesondere auf solche, die große, regelmäßig strukturierte Datensätze betreffen.