So beschleunigen Sie langsamen Python-Code, auch wenn Sie Anfänger sind
Bild vom Autor

# Einführung

Python ist eine der anfängerfreundlichsten Sprachen überhaupt. Aber wenn Sie eine Weile damit gearbeitet haben, sind Sie wahrscheinlich auf Schleifen gestoßen, deren Abschluss mehrere Minuten dauert, Datenverarbeitungsjobs, die Ihren gesamten Speicher beanspruchen, und mehr.

Sie müssen kein Experte für Leistungsoptimierung werden, um wesentliche Verbesserungen zu erzielen. Der langsamste Python-Code ist auf eine Handvoll häufiger Probleme zurückzuführen, die sich leicht beheben lassen, wenn Sie wissen, wonach Sie suchen müssen.

In diesem Artikel lernen Sie fünf praktische Techniken kennen, um langsamen Python-Code zu beschleunigen, mit Vorher-Nachher-Beispielen, die den Unterschied verdeutlichen.

Den Code für diesen Artikel finden Sie unter GitHub.

# Voraussetzungen

Bevor wir beginnen, stellen Sie sicher, dass Sie Folgendes haben:

  • Python 3.10 oder höher installiert
  • Vertrautheit mit Funktionen, Schleifen und Hear
  • Eine gewisse Vertrautheit mit dem Zeit Modul aus der Standardbibliothek

Für einige Beispiele benötigen Sie außerdem die folgenden Bibliotheken:

# 1. Messen vor dem Optimieren

Bevor Sie eine einzelne Codezeile ändern, müssen Sie es wissen Wo die Langsamkeit ist tatsächlich so. Die Optimierung des falschen Teils Ihres Codes verschwendet Zeit und kann die State of affairs sogar verschlimmern.

Die Standardbibliothek von Python enthält eine einfache Möglichkeit, jeden Codeblock zeitlich festzulegen: die Zeit Modul. Für eine detailliertere Profilerstellung cProfile zeigt Ihnen genau, welche Funktionen am längsten dauern.

Nehmen wir an, Sie haben ein Skript, das eine Liste von Verkaufsdatensätzen verarbeitet. So finden Sie den langsamen Teil:

import time

def load_records():
    # Simulate loading 100,000 information
    return record(vary(100_000))

def filter_records(information):
    return (r for r in information if r % 2 == 0)

def generate_report(information):
    return sum(information)

# Time every step
begin = time.perf_counter()
information = load_records()
print(f"Load     : {time.perf_counter() - begin:.4f}s")

begin = time.perf_counter()
filtered = filter_records(information)
print(f"Filter   : {time.perf_counter() - begin:.4f}s")

begin = time.perf_counter()
report = generate_report(filtered)
print(f"Report   : {time.perf_counter() - begin:.4f}s")

Ausgabe:

Load     : 0.0034s
Filter   : 0.0060s
Report   : 0.0012s

Jetzt wissen Sie, worauf Sie sich konzentrieren müssen. filter_records() ist der langsamste Schritt, gefolgt von load_records(). Hier zahlt sich additionally jeder Optimierungsaufwand aus. Ohne Messungen hätten Sie möglicherweise Zeit mit der Optimierung verbracht generate_report()was schon schnell ging.

Der time.perf_counter() Funktion ist präziser als time.time() für kurze Messungen. Verwenden Sie es immer dann, wenn Sie die Codeleistung zeitlich festlegen.

Faustregel: Raten Sie nie, wo der Engpass liegt. Erst messen, dann optimieren.

# 2. Verwendung integrierter Funktionen und Standardbibliothekstools

Pythons integrierte Funktionen – sum(), map(), filter(), sorted(), min(), max() – werden in C unter der Haube implementiert. Sie sind deutlich schneller als das Schreiben gleichwertiger Logik in reinen Python-Schleifen.

Vergleichen wir das manuelle Summieren einer Liste mit der Verwendung der integrierten Funktion:

import time

numbers = record(vary(1_000_000))

# Handbook loop
begin = time.perf_counter()
complete = 0
for n in numbers:
    complete += n
print(f"Handbook loop : {time.perf_counter() - begin:.4f}s  →  {complete}")

# Constructed-in sum()
begin = time.perf_counter()
complete = sum(numbers)
print(f"Constructed-in    : {time.perf_counter() - begin:.4f}s  →  {complete}")

Ausgabe:

Handbook loop : 0.1177s  →  499999500000
Constructed-in    : 0.0103s  →  499999500000

Wie Sie sehen, ist die Verwendung integrierter Funktionen quick sechsmal schneller.

Beim Sortieren gilt das gleiche Prinzip. Wenn Sie eine Liste von Wörterbüchern nach einem Schlüssel sortieren müssen, verwenden Sie Python sorted() mit einem key Argument ist sowohl schneller als auch sauberer als das manuelle Sortieren. Hier ist ein weiteres Beispiel:

orders = (
    {"id": "ORD-003", "quantity": 250.0},
    {"id": "ORD-001", "quantity": 89.99},
    {"id": "ORD-002", "quantity": 430.0},
)

# Sluggish: guide comparability logic
def manual_sort(orders):
    for i in vary(len(orders)):
        for j in vary(i + 1, len(orders)):
            if orders(i)("quantity") > orders(j)("quantity"):
                orders(i), orders(j) = orders(j), orders(i)
    return orders

# Quick: built-in sorted()
sorted_orders = sorted(orders, key=lambda o: o("quantity"))
print(sorted_orders)

Ausgabe:

({'id': 'ORD-001', 'quantity': 89.99}, {'id': 'ORD-003', 'quantity': 250.0}, {'id': 'ORD-002', 'quantity': 430.0})

Versuchen Sie als Übung, die oben genannten Ansätze zeitlich festzulegen.

Faustregel: Bevor Sie eine Schleife schreiben, um etwas Gemeinsames zu tun – Summieren, Sortieren, Finden des Maximums – prüfen Sie, ob Python bereits über eine integrierte Schleife verfügt. Das geht quick immer, und es ist quick immer schneller.

# 3. Vermeiden Sie wiederholtes Arbeiten innerhalb von Schleifen

Einer der häufigsten Leistungsfehler besteht darin, kostspielige Arbeiten innerhalb einer Schleife auszuführen, die außerhalb der Schleife erledigt werden könnten. Jede Iteration zahlt die Kosten, auch wenn sich das Ergebnis nie ändert.

Hier ist ein Beispiel: Validieren einer Liste von Produktcodes anhand einer genehmigten Liste.

import time

authorized = ("SKU-001", "SKU-002", "SKU-003", "SKU-004", "SKU-005") * 1000
incoming = (f"SKU-{str(i).zfill(3)}" for i in vary(5000))

# Sluggish: len() and record membership test on each iteration
begin = time.perf_counter()
legitimate = ()
for code in incoming:
    if code in authorized:        # record search is O(n) — sluggish
        legitimate.append(code)
print(f"Checklist test : {time.perf_counter() - begin:.4f}s  →  {len(legitimate)} legitimate")

# Quick: convert authorized to a set as soon as, earlier than the loop
begin = time.perf_counter()
approved_set = set(authorized)    # set lookup is O(1) — quick
legitimate = ()
for code in incoming:
    if code in approved_set:
        legitimate.append(code)
print(f"Set test  : {time.perf_counter() - begin:.4f}s  →  {len(legitimate)} legitimate")

Ausgabe:

Checklist test : 0.3769s  →  5 legitimate
Set test  : 0.0014s  →  5 legitimate

Der zweite Ansatz ist viel schneller und die Lösung bestand lediglich darin, eine Konvertierung außerhalb der Schleife zu verschieben.

Das gleiche Muster gilt für alles, was kostspielig ist und sich zwischen den Iterationen nicht ändert, etwa das Lesen einer Konfigurationsdatei, das Kompilieren eines Regex-Musters oder das Öffnen einer Datenbankverbindung. Führen Sie dies einmal vor der Schleife aus, nicht einmal professional Iteration.

import re

# Sluggish: recompiles the sample on each name
def extract_slow(textual content):
    return re.findall(r'd+', textual content)

# Quick: compile as soon as, reuse
DIGIT_PATTERN = re.compile(r'd+')

def extract_fast(textual content):
    return DIGIT_PATTERN.findall(textual content)

Faustregel: Wenn eine Zeile innerhalb Ihrer Schleife bei jeder Iteration das gleiche Ergebnis liefert, verschieben Sie sie nach außen.

# 4. Auswahl der richtigen Datenstruktur

Python bietet Ihnen mehrere integrierte Datenstrukturen – Hear, Mengen, Wörterbücher, Tupel – und die Auswahl der falschen für die jeweilige Aufgabe kann dazu führen, dass Ihr Code viel langsamer wird, als er sein müsste.

Der wichtigste Unterschied besteht zwischen Hear und Units für die Mitgliedschaftsprüfung mithilfe der in Operator:

  • Die Prüfung, ob ein Component in einer Liste vorhanden ist, dauert umso länger, je größer die Liste ist, da Sie sie einzeln durchsuchen müssen
  • Ein Satz verwendet Hashing, um dieselbe Frage unabhängig von der Größe in konstanter Zeit zu beantworten

Schauen wir uns ein Beispiel an: Finden Sie heraus, welche Kunden-IDs aus einem großen Datensatz bereits eine Bestellung aufgegeben haben.

import time
import random

all_customers = (f"CUST-{i}" for i in vary(100_000))
ordered = (f"CUST-{i}" for i in random.pattern(vary(100_000), 10_000))

# Sluggish: ordered is an inventory
begin = time.perf_counter()
repeat_customers = (c for c in all_customers if c in ordered)
print(f"Checklist : {time.perf_counter() - begin:.4f}s  →  {len(repeat_customers)} discovered")

# Quick: ordered is a set
ordered_set = set(ordered)
begin = time.perf_counter()
repeat_customers = (c for c in all_customers if c in ordered_set)
print(f"Set  : {time.perf_counter() - begin:.4f}s  →  {len(repeat_customers)} discovered")

Ausgabe:

Checklist : 16.7478s  →  10000 discovered
Set  : 0.0095s  →  10000 discovered

Die gleiche Logik gilt für Wörterbücher, wenn Sie eine schnelle Schlüsselsuche benötigen, und für die Sammlungen Module deque Wenn Sie häufig Elemente an beiden Enden einer Sequenz hinzufügen oder entfernen, sind Hear bei etwas langsam.

Hier finden Sie eine kurze Übersicht darüber, wann Sie zu welcher Struktur greifen sollten:

Brauchen Zu verwendende Datenstruktur
Geordnete Reihenfolge, Indexzugriff record
Schnelle Mitgliedschaftsprüfungen set
Schlüsselwertsuche dict
Vorkommnisse zählen collections.Counter
Warteschlangen- oder Deque-Operationen collections.deque

Faustregel: wenn Sie es überprüfen if x in one thing innerhalb einer Schleife und one thing Hat es mehr als ein paar hundert Artikel, sollte es sich wahrscheinlich um ein Set handeln.

# 5. Vektorisierende Operationen an numerischen Daten

Wenn Ihr Code Zahlen verarbeitet – Berechnungen über Datenzeilen, statistische Operationen, Transformationen – ist das Schreiben von Python-Schleifen sinnvoll quick immer die langsamstmögliche Annäherung. Bibliotheken mögen NumPy Und Pandas sind genau dafür konzipiert: Operationen auf ganze Arrays auf einmal anzuwenden, in optimiertem C-Code, ohne dass eine Python-Schleife in Sicht ist.

Das nennt man Vektorisierung. Anstatt Python anzuweisen, jedes Component einzeln zu verarbeiten, übergeben Sie das gesamte Array an eine Funktion, die alles intern mit C-Geschwindigkeit verarbeitet.

import time
import numpy as np
import pandas as pd

costs = (spherical(10 + i * 0.05, 2) for i in vary(500_000))
discount_rate = 0.15

# Sluggish: Python loop
begin = time.perf_counter()
discounted = ()
for value in costs:
    discounted.append(spherical(value * (1 - discount_rate), 2))
print(f"Python loop : {time.perf_counter() - begin:.4f}s")

# Quick: NumPy vectorization
prices_array = np.array(costs)
begin = time.perf_counter()
discounted = np.spherical(prices_array * (1 - discount_rate), 2)
print(f"NumPy        : {time.perf_counter() - begin:.4f}s")

# Quick: pandas vectorization
prices_series = pd.Sequence(costs)
begin = time.perf_counter()
discounted = (prices_series * (1 - discount_rate)).spherical(2)
print(f"Pandas       : {time.perf_counter() - begin:.4f}s")

Ausgabe:

Python loop : 1.0025s
NumPy        : 0.0122s
Pandas       : 0.0032s

NumPy ist für diesen Vorgang quick 100-mal schneller. Der Code ist außerdem kürzer und sauberer. Keine Schleife, nein append()nur ein einziger Ausdruck.

Wenn Sie bereits mit Pandas arbeiten DataFramedas gleiche Prinzip gilt für Spaltenoperationen. Bevorzugen Sie immer Vorgänge auf Spaltenebene gegenüber dem Durchlaufen von Zeilen mit iterrows():

df = pd.DataFrame({"value": costs})

# Sluggish: row-by-row with iterrows
begin = time.perf_counter()
for idx, row in df.iterrows():
    df.at(idx, "discounted") = spherical(row("value") * 0.85, 2)
print(f"iterrows : {time.perf_counter() - begin:.4f}s")

# Quick: vectorized column operation
begin = time.perf_counter()
df("discounted") = (df("value") * 0.85).spherical(2)
print(f"Vectorized : {time.perf_counter() - begin:.4f}s")

Ausgabe:

iterrows : 34.5615s
Vectorized : 0.0051s

Der iterrows() Die Funktion ist eine der häufigsten Leistungsfallen bei Pandas. Wenn Sie es in Ihrem Code sehen und an mehr als ein paar tausend Zeilen arbeiten, lohnt es sich quick immer, es durch eine Spaltenoperation zu ersetzen.

Faustregel: wenn Sie Zahlen durchlaufen oder DataFrame Zeilen, fragen Sie, ob NumPy oder Pandas kann dasselbe tun wie eine vektorisierte Operation.

# Abschluss

Langsamer Python-Code ist normalerweise ein Musterproblem. Messen vor dem Optimieren, sich auf integrierte Funktionen stützen, wiederholte Arbeit in Schleifen vermeiden, die richtige Datenstruktur auswählen und Vektorisierung für numerische Arbeiten verwenden – all das deckt die überwiegende Mehrheit der Leistungsprobleme ab, auf die Sie als Anfänger stoßen werden.

Beginnen Sie jedes Mal mit Tipp eins: Messen. Finden Sie den tatsächlichen Engpass, beheben Sie ihn und messen Sie erneut. Sie werden überrascht sein, wie viel Headroom vorhanden ist, bevor Sie etwas Fortgeschritteneres benötigen.

Die fünf Techniken in diesem Artikel decken die häufigsten Ursachen für langsamen Python-Code ab. Aber manchmal muss man noch weiter gehen:

  • Multiprocessing – wenn Ihre Aufgabe CPU-gebunden ist und Sie einen Multi-Core-Pc haben, Python multiprocessing Das Modul kann die Arbeit auf mehrere Kerne aufteilen
  • Asynchrone E/A – wenn Ihr Code die meiste Zeit damit verbringt, auf Netzwerkanfragen oder Dateilesevorgänge zu warten, asyncio kann viele Aufgaben gleichzeitig erledigen
  • Dask oder Polaren – Bei Datensätzen, die zu groß sind, um in den Speicher zu passen, skalieren diese Bibliotheken über das hinaus, was Pandas verarbeiten können

Es lohnt sich, diese auszuprobieren, sobald Sie die Grundlagen angewendet haben und noch mehr Spielraum benötigen. Viel Spaß beim Codieren!

Bala Priya C ist ein Entwickler und technischer Redakteur aus Indien. Sie arbeitet gerne an der Schnittstelle von Mathematik, Programmierung, Datenwissenschaft und Inhaltserstellung. Zu ihren Interessen- und Fachgebieten gehören DevOps, Datenwissenschaft und Verarbeitung natürlicher Sprache. Sie liebt es zu lesen, zu schreiben, zu programmieren und Kaffee zu trinken! Derzeit arbeitet sie daran, zu lernen und ihr Wissen mit der Entwickler-Neighborhood zu teilen, indem sie Tutorials, Anleitungen, Meinungsbeiträge und mehr verfasst. Bala erstellt außerdem ansprechende Ressourcenübersichten und Programmier-Tutorials.



Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert