Probleme lösen wie ein Python Profi

Aus Micropython Referenz
Zur Navigation springen Zur Suche springen

Original: https://medium.com/data-science-collective/problem-solving-like-a-python-pro-01174a3f7740

Problemlösung wie ein Python-Profi

Ein praktischer Leitfaden zum Schreiben von sauberem, flexiblem und zukunftssicherem Python-Code

von Gwang-Jin
Übersetzt von DeepL.com


Foto von Parabol | The Agile Meeting Tool auf Unsplash

Einführung: Kodieren ist Denken

Ich möchte Ihnen eine Geschichte über zwei Python-Programmierer erzählen: Alice und Bob. Alice hat gerade ein Online-Python-Bootcamp abgeschlossen. Bob hingegen schreibt schon seit Jahren Python - aber beide stoßen oft an dieselbe Wand:

   "Warum funktioniert mein Code... aber so hässlich? So anfällig? So repetitiv?"

In diesem Artikel geht es darum, diese Mauer zu durchbrechen.

Wir werden nicht über ausgefallene Frameworks oder obskure Syntax sprechen.
Wir werden das eigentliche Problem beheben: wie Sie vor und während des Programmierens denken.
Dies sind die mentalen Werkzeuge, die die Skripter von den Systemdesignern und die Codeflüsterer von den Codeflüsterern unterscheiden.

Los geht's.

1) Denken Sie wie ein Profi

Stellen Sie sich vor, Sie durchforsten eine Liste von Blogbeiträgen. Du schreibst dies:

def get_titles():
    titles = []
    with open("blogs.csv") as f:
        for line in f:
            parts = line.strip().split(',')
            titles.append(parts[1])
    return titles

Schön. Bis Sie dasselbe für Produkte.csv tun müssen. Und Benutzer.csv.

Der Junior-Entwickler kopiert und fügt ein. Der mittlere Entwickler schreibt eine weitere ähnliche Funktion. Aber der Profi? Der Profi verallgemeinert:

def get_column(filepath, column_index):
    with open(filepath) as f:
        return [line.strip().split(',')[column_index] for line in f]

Boom. Ein Werkzeug, unendlich viele Anwendungsfälle. Keine Wiederholungen. Nur Klarheit.

"Sobald man etwas zweimal löst, verdient es einen Namen."

Daten statt Code

Hier ist ein kurzes Rätsel. Was ist besser?

if cmd == "init":
    initialize()
elif cmd == "start":
    start_service()
elif cmd == "stop":
    stop_service()

oder...

dispatch = {
    "init": initialize,
    "start": start_service,
    "stop": stop_service,
}
dispatch[cmd]()

Anfänger schreiben Logik. Profis schreiben Abbildungen.

Wenn Ihr Code voller if/ elif ist, fragen Sie: Könnte das nicht einfach ein Wörterbuch der Funktionen sein?

Sie ist nicht nur kürzer. Sie ist auch leistungsfähiger. Sie können Schlüssel sortieren. Sie validieren. Generieren Sie automatisch CLI-Tools aus ihnen. Die Möglichkeiten sind wild, wenn Ihr Verhalten in Daten lebt.

Die Debugging-Mentalität

Als ich anfing, hatte ich eine schlechte Angewohnheit: Ich starrte auf fehlerhaften Code, als ob er sich von selbst reparieren würde.

Profis starren nicht. Profis stoßen.

Sie fügen ein:

assert isinstance(data, dict), "Expected a dict" 
assert "user" in data, "Missing user key"

Sie probieren Teile in Isolation aus:

print(parse_date("2023-13-50")) # Hmm... invalid date?

Sie verkleinern Probleme, bis der Fehler in die Enge getrieben ist wie ein Waschbär in einem Pappkarton.

Fehlersuche ist keine lästige Pflicht. Es ist Detektivarbeit. Seien Sie neugierig, nicht ängstlich.

Think in the REPL (or a Notebook)

Hier ist ein Geheimnis: Der meiste großartige Code begann nicht als Code. Es begann als Spiel - mit Spielzeugdaten.

Wenn Profis etwas schnell verstehen wollen, schreiben sie keine Datei. Sie öffnen eine REPL:

$ python
>>> from datetime import datetime
>>> datetime.strptime("2023-06-19", "%Y-%m-%d")

Kein Warten. Kein erneuter Skriptdurchlauf. Nur Sofortige Rückmeldung.

Keine langen Debugging-Sitzungen nach dem Schreiben des Codes. Denn das Kodierverfahren selbst war interaktives Vorwärts-Debugging. Das ist die Stärke der REPL.

Pro-Tipp: Jupyter-Notebooks oder IPython können für komplexe Experimente noch besser sein. Aber auch mit Python oder bpython / ipythonREPLs können Sie Daten, APIs oder Bugs 10x schneller verstehen.
...
Die REPL-gesteuerte Programmierung wurde erstmals von den Lisp-Sprachen eingeführt und war eines der Geheimnisse, warum die Lisp-Sprachen vor dem AI-Winter so erfolgreich waren.
...

Mini-Herausforderung

Nehmt eines eurer aktuellen Drehbücher und macht Folgendes:

  • Ersetze ein großes if/elif durch ein Diktat von Funktionen.
  • Verpacken Sie wiederholte Logik in eine Allzweckfunktion.
  • Fügen Sie zwei assert-Anweisungen hinzu, um falsche Daten zu verhindern.
  • Probieren Sie eine Funktionsidee zuerst in der REPL aus.

2) Bauen wie ein Python-Profi: Jenseits von Schleifen und Ifs

Sie werden den Unterschied in Kontrolle, Klarheit und Selbstvertrauen spüren.

Sie haben also begonnen, wie ein Profi zu denken: Sie verallgemeinern früh, debuggen wie ein Spürhund und lassen Ihre REPL die geistige Last tragen.

Jetzt ist es Zeit für das nächste Upgrade.

Wir werden über das Schreiben anständiger Funktionen hinausgehen und anfangen, komponierbare, elegante und erweiterbare Systeme zu bauen. Sie wissen schon, die Art von Code, auf die Sie ein Jahr später schauen und denken: "Verdammt. Wer hat das geschrieben? Ach, richtig. Ich."

Dekorateure: Funktionen hinzufügen, Code sauber halten

Nehmen wir an, Sie wollen jeden Funktionsaufruf protokollieren. Ein Anfänger fügt überall print() ein. Ein Profi benutzt einen Dekorator:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with {args}, {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@log_calls
def greet(name):
    return f"Hello, {name}!"

print(greet("Alice"))

Ausgabe:

Calling greet with ('Alice',), {}
Hello, Alice!

Du hast greet() nicht angerührt, aber du hast es verbessert. Dekoratoren sind wie Plugins für das Verhalten. Möchten Sie bei einem Fehler einen neuen Versuch starten? Ergebnisse zwischenspeichern? Timing verfolgen? Alles möglich, wunderbar.
... log_calls() ist die vereinfachte Python-Version von trace in Lisp-Sprachen (Common Lisp). Sie ist besonders nützlich bei der Fehlersuche in rekursiven Funktionen.
...

Kontext-Manager: Setup/Teardown wie ein Boss handhaben

Haben Sie jemals eine Datei geöffnet und vergessen, sie zu schließen?

with open("file.txt") as f:

   data = f.read()

Das ist ein Kontextmanager. Aber wissen Sie was? Sie können Ihr eigenes Buch schreiben:

from contextlib import contextmanager

@contextmanager def db_transaction():

   print("BEGIN")
   try:
       yield
       print("COMMIT")
   except:
       print("ROLLBACK")
       raise

with db_transaction():

   print("Do something risky")

Super sauber. Keine Kesselflicker. Maximale Sicherheit.

Wenn Sie zustimmend nicken und denken "Das fühlt sich fast so an, als würde ich funktionale Programmierung im Verborgenen betreiben" - dann liegen Sie nicht falsch.

Viele der Ideen, die sich heute "pythonisch" anfühlen, haben ihre Wurzeln in Lisp.

Zum Beispiel:

   REPL-gesteuerte Programmierung, heute ein Grundnahrungsmittel für Python und Data Science Workflows, wurde zuerst von der Lisp-Familie populär gemacht. Sie war eine der größten Stärken von Lisp - und eine Geheimwaffe im frühen KI-Boom vor dem ersten KI-Winter.
   Auch Kontextmanagerfunktionen haben ihren Ursprung in Lisp. Common Lisp hatte unwind-protect und andere Konstrukte, lange bevor with blocks in Python zum Standard wurde.

Paradigmen mischen: OOP, FP, prozedural

Python ist nicht auf einen bestimmten Stil festgelegt. Das ist das Schöne daran. Verwenden Sie das beste Werkzeug für den Job.

   Verwenden Sie OOP für wiederverwendbare, erweiterbare Systeme (z.B. Plugin-Manager, Simulationen).
   Verwenden Sie FP für Pipelines und reine Datentransformationen.
   Verwenden Sie procedural für einmalige Skripte, bei denen Übersichtlichkeit gefragt ist.

Hier ist eine kleine Pipeline im FP-Stil:

data = [" Apple ", "banana", "Cherry "] data = map(str.strip, data) data = filter(lambda x: x.lower().startswith("a"), data) print(list(data))

Das ist sauber, testbar und kompakt. In größeren Systemen können Sie die Paradigmen nach Bedarf mischen.

Common Lisp ist eine echte Multi-Paradigma Sprache. Sie unterstützt imperative, funktionale, objektorientierte, regelbasierte und logische Programmierung - alles nativ oder mit minimalem Aufwand.

Aber hier ist ein faszinierendes Muster:

   Wenn eine Sprache alle Paradigmen zur Verfügung stellt, neigen Lisp-Programmierer dazu, sich für die funktionale Programmierung zu entscheiden.

Warum? Weil FP auf natürliche Weise Daten vom Verhalten trennt, den Zustand explizit hält und Überraschungen reduziert. It turns out: Reine Funktionen sind gut für das menschliche Gehirn. Sie sind leicht zu testen, leicht zu verstehen und wunderbar zusammensetzbar.

Deshalb sind auch in Python viele der Techniken, die wir behandelt haben - wie Funktionsverkettung, unveränderliche Datenverarbeitung und Regel-Engines - auf FP-Prinzipien zurückzuführen. Design für Erweiterungen (Prinzip Offen/Geschlossen)

Angenommen, Sie möchten viele Exportformate unterstützen:

Einsteiger-Code:

if format == "csv":

   export_csv()

elif format == "json":

   export_json()

Besser:

class Exporter:

   registry = {}
   @classmethod
   def register(cls, name):
       def wrapper(func):
           cls.registry[name] = func
           return func
       return wrapper
   @classmethod
   def run(cls, name, data):
       return cls.registry[name](data)

Verwendung:

@Exporter.register("csv") def export_csv(data): ...

@Exporter.register("json") def export_json(data): ...

Exporter.run("csv", my_data)

Jetzt kann jeder die Unterstützung für ein neues Format hinzufügen, ohne den Kern zu verändern. Kleine Herausforderung

   Schreibe einen Dekorator, der die Dauer einer Funktion angibt.
   Einen Kontextmanager erstellen, der temporär einen Konfigurationswert ändert.
   Umgestaltung einer Kette von if/elif/else in ein Registrierungssystem.
   Mischen Sie OOP und FP: Bauen Sie eine Klasse mit einer Methode, die eine saubere FP-Pipeline zurückgibt.

3) Denken Sie in Systemen, nicht in Skripten: Die Python-Zauberei-Ebene

Im dritten und letzten Teil wechseln wir vom Programmierer zum Architekten. Von clever zu leise mächtig.

Wir schreiben nicht mehr nur Funktionen oder verpacken Logik in schöne Schichten. Jetzt bauen wir Systeme, die sich anpassen, wachsen und sich weiterentwickeln können - mit minimalen Änderungen.

Dies ist der Punkt, an dem Ihr zukünftiges Ich einen High-Five in die Vergangenheit wirft. Schreibe eine DSL: Verwandle ein Problem in eine Sprache

Was wäre, wenn Sie Ihren Code wie einen Satz schreiben könnten?

Stellen Sie sich einen Datenverarbeitungsablauf vor:

with pipeline() as p:

   p >> read("input.csv") >> clean() >> transform() >> write("output.csv")

Das ist kein Framework. Das ist einfach Operatorüberladung + Dekoratoren + monadisches Design. Minimale Zutaten

  1. --- Decorators ---

def step(func):

   def wrapper(*args, **kwargs):
       def delayed(context_value):
           return func(context_value, *args, **kwargs)
       return delayed
   return wrapper

def source(func):

   def wrapper(*args, **kwargs):
       def delayed(_):
           return func(*args, **kwargs)
       return delayed
   return wrapper
  1. --- Monad ---

class pipeline:

   def __init__(self):
       self._value = None
   def __enter__(self):
       return self
   def __exit__(self, *a):
       pass
   def __rshift__(self, func):
       self._value = func(self._value)
       return self
   @property
   def value(self):
       return self._value

Ihre Schritte (Einfacher Python, Verziert)

@source def read(filename):

   with open(filename) as f: return f.readlines()

@step def clean(lines):

   return [x for x in lines if x.strip()]

@step def transform(lines):

   return [x.upper() for x in lines]

@step def write(lines, filename):

   with open(filename, "w") as f:
       for line in lines: f.write(line)
   return filename

Erstellen Sie eine Eingabedatei als Beispiel:

  1. nano input.txt

hello world

python

Ausführen

with pipeline() as p:

   p >> read("input.txt") >> clean() >> transform() >> write("output.txt")

print("Final output stored in:", p.value)

Jeder Schritt ist eine Funktion. Auswechselbar. Verkettbar. Planbar. Sie haben eine DSL (domänenspezifische Sprache) gebaut - eine kleine, elegante Mini-Sprache für Ihr eigenes Problem.

Und Python ist ein gutes Werkzeug, um eine solche zu schreiben (obwohl Lisp-Sprachen dafür viel besser geeignet sind; hier haben wir das Monaden-Konzept von Haskell übernommen, da Lisp-Makros in Python nicht verfügbar waren).

Aber Sie haben es gerade - wunderschön - in Python gemacht. Regel-Engines: Logik als Daten

Stellen Sie sich vor, dass Sie die Preise für Produkte mit unterschiedlichen Rabattregeln festlegen:

if user.vip:

   price *= 0.8

elif user.first_time:

   price *= 0.9

Was wäre, wenn Ihre Logik stattdessen Daten wäre?

rules = [

   (lambda user: user.vip, lambda price: price * 0.8),
   (lambda user: user.first_time, lambda price: price * 0.9),

]

for cond, action in rules:

   if cond(user):
       price = action(price)

Jetzt ist die Logik komponierbar. Testbar. Lässt sich aus einer Konfigurationsdatei oder einer Datenbank laden. Dies ist die Essenz einer Regelmaschine. Plugin-Systeme: Lassen Sie sich von anderen erweitern

Sie müssen nicht immer jeden Teil des Systems selbst schreiben. Manchmal definiert man nur die Steckdosen, und lässt andere die Stecker schreiben.

class Command:

   registry = {}
   @classmethod
   def register(cls, name):
       def inner(func):
           cls.registry[name] = func
           return func
       return inner
   @classmethod
   def run(cls, name):
       return cls.registry[name]()

@Command.register("hello") def say_hello():

   print("Hello!")

Später:

Command.run("hello")

So bauen Sie erweiterbare Systeme, die skalierbar sind, ohne die Kernlogik zu verändern. Denken Sie in Wachstumskurven

Bevor Sie Code schreiben, fragen Sie:

   Müssen dabei mehr Daten verarbeitet werden?
   Mehr Eingabearten?
   Mehr Funktionen, die von anderen hinzugefügt wurden?

Entwerfen Sie Ihre Schnittstellen so, dass sie offen sind.

Verwenden Sie **kwargs, abstrakte Basisklassen oder Strategiemuster, um Systeme zu bauen, die flexibel sind ohne dass sie neu geschrieben werden müssen.

Diese Denkweise erspart Ihnen künftige Kopfschmerzen und erschließt Ihnen ganze Ökosysteme der Wiederverwendung.

Regelbasierte Programmierung hat ein reiches Erbe in Lisp. Ein brillantes Beispiel ist Kapitel 2 von PAIP (Peter Norvigs Paradigmen der Programmierung künstlicher Intelligenz), in dem Regeln als Daten modelliert und dynamisch angewandt werden - ein Konzept, das wir bereits bei der Entwicklung von Regelmaschinen aufgegriffen haben.

Und wenn es um DSLs (domänenspezifische Sprachen) geht? Das Makrosystem von Lisp macht das trivial elegant. Python hat keine Makros, aber mit Dekoratoren, Operatorüberladung und erstklassigen Funktionen kommt man dem erstaunlich nahe.

Das wahre Geheimnis ist also vielleicht folgendes:

   Je mehr Python man "wie Lisp" schreibt...
   desto mächtiger, ausdrucksstärker und freudiger wird es.

Mini-Herausforderung

Versuchen Sie eine oder mehrere der folgenden Aufgaben:

   Schreiben Sie eine Regelmaschine mit 3 Regeln als (Bedingung, Aktion) Lambda-Paare.
   Erstellen Sie eine Pipeline mit >> Verkettung und Mock-Schritten.
   Verwandeln Sie einen if/else-Blob der Geschäftslogik in eine Datenstruktur.
   Entwerfen Sie ein Plugin-System mit einer Registry und einem registrierten Befehl.

Zusammenfassung: Ihre Python-Superkräfte zur Problemlösung

Lassen Sie uns innehalten und auf die Reise zurückblicken, die wir gerade angetreten haben - eine Reise, die mit umständlichen Schleifen begann und mit eleganten Systemen endete.

Egal, ob Sie noch eher ein Anfänger sind oder auf dem besten Weg zur Meisterschaft, die Prinzipien, die Sie gerade gelernt haben, sind nicht nur clevere Tricks. Sie sind die Grundlage für das Schreiben von Python, das mit Ihrem Verstand skaliert - Code, der mit Ihnen und nicht gegen Sie arbeiten möchte. 1) Denke wie ein Profi

   Verallgemeinere früh. Wenn du es zweimal gelöst hast, braucht es einen Namen.
   Benutze Daten, nicht Logik. Ersetze if/elif-Türme durch Dicts und Config.
   Debuggen Sie wie ein Wissenschaftler. Bestätigen Sie Ihre Annahmen. Verkleinern Sie Ihr Universum.
   Prototypen in der REPL. Warten Sie nicht. Testen Sie Ideen live.

2) Architekt wie ein Profi

   Dekoratoren = mehr Leistung ohne Berührung der Kernlogik.
   Kontextmanager = sauberes, automatisches Auf- und Abrüsten.
   Mix paradigms. Python ist nicht auf einen Stil festgelegt - das sollten Sie auch nicht sein.
   Entwerfen Sie für Erweiterungen. Der Code sollte Veränderungen zulassen, ohne zu zerbröseln.

3) In Systemen denken

   Baue DSLs, damit sich Probleme klar ausdrücken lassen.
   Umwandeln Sie Logik in Daten mit Hilfe von Regelmaschinen und Bedingungs-Aktions-Paaren.
   Erstellen Sie Plugin-Systeme, damit andere Ihre Werkzeuge erweitern können.
   Entwerfen Sie mit Blick auf zukünftige Veränderungen - planen Sie für Wachstum, nicht für Neuschreiben.

Egal, ob Sie Anfänger sind oder sich auf dem Weg zur architektonischen Meisterschaft befinden, das Ziel ist nicht, clever zu sein. Es geht darum, Code zu schreiben, der es ist:

Man löst nicht mehr nur Probleme. Sie entwerfen Maschinen, die Problemfamilien lösen.

Bauen Sie. Geh überarbeiten. Unterrichten Sie.

Denn so werden Profis - und Zauberer - gemacht.

   Gefällt Ihnen diese Art des Denkens? Ich werde bald mehr darüber schreiben:
   - Folgen Sie mir hier auf Medium @gwangjinkim for deep dives on Python, Lisp, system design, and developer thinking, and much more
   - Auf Substack abonnieren: gwangjinkim.substack.com — coming soon with early essays, experiments & newsletters (just getting started).
   Besuchen Sie meinen Ghost-Blog: everyhub.org— with hands-on tech tutorials and tools (most of them I cross-post here in medium, but not all of them).
   Folgen Sie der Seite, die zu Ihrem Stil passt - oder allen drei, wenn Sie einen Platz in der ersten Reihe haben wollen, um zu erfahren, was als Nächstes kommt.

Viel Spaß beim Hacken!

Vielleicht lesen Sie auch gerne über Multiple Dispatch in Python (von Lisp inspiriert): Wie man Multiple Dispatch in Python anwendet Wie man Multiple Dispatch in Python heute implementiert

python.plainenglish.io Warum Kompatibilität ein so wichtiges Merkmal in der Programmierung ist Wie inLego ...

python.plainenglish.io

Ich empfehle dir auf jeden Fall uv, einen unglaublich schnellen Python-Paketmanager (geschrieben in Rust, also superschnell - ersetzt poet