Hören Sie auf, chaotische Python zu schreiben: Ein sauberer Code -Crash -Kurs
Bild von Autor | Ideogramm

Wenn Sie eine Weile in Python codiert haben, haben Sie wahrscheinlich die Grundlagen gemeistert und einige Projekte gebaut. Und jetzt schauen Sie sich Ihren Code an und denken: „Das funktioniert, aber … es ist nicht genau etwas, das ich in einer Codebewertung stolz zeigen würde.“ Wir waren alle dort.

Wenn Sie jedoch weiter codieren, wird das Schreiben sauberer Code genauso wichtig wie das Schreiben von Funktionscode. In diesem Artikel habe ich praktische Techniken zusammengestellt, mit denen Sie von „It Runs, berühren Sie es nicht“ nach „Dies ist tatsächlich aufrechterhalten“.

🔗 Hyperlink zum Code auf GitHub

1. Modelldaten explizit. Gehen Sie nicht um Diktate herum

Wörterbücher sind in Python tremendous flexibel und das ist genau das Drawback. Wenn Sie Uncooked -Wörterbücher während Ihres gesamten Codes übergeben, laden Sie Tippfehler, Schlüsselfehler und Verwirrung darüber ein, welche Daten tatsächlich vorhanden sein sollten.

Anstelle davon:

def process_user(user_dict):
    if user_dict('standing') == 'lively':  # What if 'standing' is lacking?
        send_email(user_dict('electronic mail'))   # What if it is 'mail' in some locations?
        
        # Is it 'title', 'full_name', or 'username'? Who is aware of!
        log_activity(f"Processed {user_dict('title')}")

Dieser Code ist nicht strong, da er davon ausgeht, dass Wörterbuchschlüssel ohne Validierung existieren. Es bietet keinen Schutz gegen Tippfehler oder fehlende Schlüssel, was verursacht wird KeyError Ausnahmen zur Laufzeit. Es gibt auch keine Dokumentation darüber, welche Felder erwartet werden.

Tun Sie das:

from dataclasses import dataclass
from typing import Non-obligatory

@dataclass
class Person:
    id: int
    electronic mail: str
    full_name: str
    standing: str
    last_login: Non-obligatory(datetime) = None

def process_user(person: Person):
    if person.standing == 'lively':
        send_email(person.electronic mail)
        log_activity(f"Processed {person.full_name}")

Python @dataclass Der Dekorateur bietet Ihnen eine saubere, explizite Struktur mit minimaler Kesselplatte. Ihre IDE kann jetzt eine automatische Vervollständigung für Attribute bereitstellen, und Sie erhalten unmittelbare Fehler, wenn die erforderlichen Felder fehlen.

Für eine komplexere Validierung sollten Sie Pydantic betrachten:

from pydantic import BaseModel, EmailStr, validator

class Person(BaseModel):
    id: int
    electronic mail: EmailStr  # Validates electronic mail format
    full_name: str
    standing: str
    
    @validator('standing')
    def status_must_be_valid(cls, v):
        if v not in {'lively', 'inactive', 'pending'}:
            elevate ValueError('Have to be lively, inactive or pending')
        return v

Jetzt überprüft sich Ihre Daten selbst, fängt Fehler frühzeitig auf und dokumentiert die Erwartungen klar.

2. Verwenden Sie Enums für bekannte Entscheidungen

String -Literale sind anfällig für Tippfehler und bieten keine IDE -Autokonomplete. Die Validierung erfolgt nur zur Laufzeit.

Anstelle davon:

def process_order(order, standing):
    if standing == 'pending':
        # course of logic
    elif standing == 'shipped':
        # totally different logic
    elif standing == 'delivered':
        # extra logic
    else:
        elevate ValueError(f"Invalid standing: {standing}")
        
# Later in your code...
process_order(order, 'shiped')  # Typo! However no IDE warning

Tun Sie das:

from enum import Enum, auto

class OrderStatus(Enum):
    PENDING = 'pending'
    SHIPPED = 'shipped'
    DELIVERED = 'delivered'
    
def process_order(order, standing: OrderStatus):
    if standing == OrderStatus.PENDING:
        # course of logic
    elif standing == OrderStatus.SHIPPED:
        # totally different logic
    elif standing == OrderStatus.DELIVERED:
        # extra logic
    
# Later in your code...
process_order(order, OrderStatus.SHIPPED)  # IDE autocomplete helps!

Wenn Sie sich mit einer festen Reihe von Optionen befassen, macht ein Enum Ihren Code robuster und Selbstdokumentation.

Mit Aufzügen:

  • Ihre IDE bietet automatische Vorschläge
  • Tippfehler werden (quick) unmöglich
  • Sie können bei Bedarf alle möglichen Werte durchsetzen

Enum erstellt eine Reihe genannter Konstanten. Der Typ Hinweis standing: OrderStatus dokumentiert den erwarteten Parametertyp. Verwendung OrderStatus.SHIPPED Anstelle eines Saitenliterales ermöglicht die IDE automatisch und fängt Tippfehler zur Entwicklungszeit.

3. Verwenden Sie nur Key phrase-Argumente für Klarheit

Das versatile Argumentsystem von Python ist leistungsfähig, kann jedoch zu Verwirrung führen, wenn Funktionsaufrufe mehrere optionale Parameter aufweisen.

Anstelle davon:

def create_user(title, electronic mail, admin=False, notify=True, momentary=False):
    # Implementation
    
# Later in code...
create_user("John Smith", "john@instance.com", True, False)

Warten Sie, was meinen diese Booleschen wieder?

Bei der Aufforderung mit Positionsargumenten ist unklar, welche Booleschen Werte ohne Überprüfung der Funktionsdefinition darstellen. Gilt für Administrator, Benachrichtigung oder etwas anderes?

Tun Sie das:

def create_user(title, electronic mail, *, admin=False, notify=True, momentary=False):
    # Implementation

# Now you could use key phrases for non-compulsory args
create_user("John Smith", "john@instance.com", admin=True, notify=False)

Die Syntax erzwingt alle Argumente, nachdem sie nach Schlüsselwort angegeben werden. Auf diese Weise ruft Ihre Funktion Selbstdokumentation auf und verhindert das Drawback „Thriller Boolean“, bei dem die Leser nicht sagen können, was wahr oder Falsch bezieht, ohne die Funktionsdefinition zu lesen.

Dieses Muster ist besonders nützlich bei API -Aufrufen und dergleichen, wo Sie die Klarheit auf der Anrufe sicherstellen möchten.

4. Verwenden Sie Pathlib über OS.Path

Pythons OS.Path -Modul ist funktional, aber klobig. Das neuere Pathlib-Modul bietet einen objektorientierten Ansatz, der intuitiver und weniger fehleranfällig ist.

Anstelle davon:

import os

data_dir = os.path.be a part of('knowledge', 'processed')
if not os.path.exists(data_dir):
    os.makedirs(data_dir)

filepath = os.path.be a part of(data_dir, 'output.csv')
with open(filepath, 'w') as f:
    f.write('resultsn')
    
# Test if we now have a JSON file with the identical title
json_path = os.path.splitext(filepath)(0) + '.json'
if os.path.exists(json_path):
    with open(json_path) as f:
        knowledge = json.load(f)

Dies verwendet eine String -Manipulation mit os.path.be a part of() Und os.path.splitext() für Pfadbeschaffung. Pfadoperationen sind über verschiedene Funktionen hinweg verstreut. Der Code ist ausführlich und weniger intuitiv.

Tun Sie das:

from pathlib import Path

data_dir = Path('knowledge') / 'processed'
data_dir.mkdir(mother and father=True, exist_ok=True)

filepath = data_dir / 'output.csv'
filepath.write_text('resultsn')

# Test if we now have a JSON file with the identical title
json_path = filepath.with_suffix('.json')
if json_path.exists():
    knowledge = json.masses(json_path.read_text())

Warum Pathlib besser ist:

  • Pfad beiträgt / ist intuitiverer
  • Methoden wie mkdir()Anwesend exists()Und read_text() sind an das Pfadobjekt angehängt
  • Operationen wie Änderungen von Erweiterungen (mit _suffix) sind semantischer

Pathlib behandelt die Feinheiten der Pfadmanipulation über verschiedene Betriebssysteme hinweg. Dies macht Ihren Code tragbarer und robuster.

5. MIT WARD -Klauseln schnell scheitern

Tief verschachtelte, wenn es sich um Andränge handelt, sind oft schwer zu verstehen und aufrechtzuerhalten. Die Verwendung von frühen Renditen – Schutzklauseln – führt zu mehr lesbarer Code.

Anstelle davon:

def process_payment(order, person):
    if order.is_valid:
        if person.has_payment_method:
            payment_method = person.get_payment_method()
            if payment_method.has_sufficient_funds(order.whole):
                strive:
                    payment_method.cost(order.whole)
                    order.mark_as_paid()
                    send_receipt(person, order)
                    return True
                besides PaymentError as e:
                    log_error(e)
                    return False
            else:
                log_error("Inadequate funds")
                return False
        else:
            log_error("No fee technique")
            return False
    else:
        log_error("Invalid order")
        return False

Tiefe Verschachtelung ist schwer zu folgen. Jeder bedingte Block erfordert die gleichzeitige Verfolgung mehrerer Zweige.

Tun Sie das:

def process_payment(order, person):
    # Guard clauses: test preconditions first
    if not order.is_valid:
        log_error("Invalid order")
        return False
        
    if not person.has_payment_method:
        log_error("No fee technique")
        return False
    
    payment_method = person.get_payment_method()
    if not payment_method.has_sufficient_funds(order.whole):
        log_error("Inadequate funds")
        return False
    
    # Principal logic comes in any case validations
    strive:
        payment_method.cost(order.whole)
        order.mark_as_paid()
        send_receipt(person, order)
        return True
    besides PaymentError as e:
        log_error(e)
        return False

Schutzklauseln verarbeiten Fehlerfälle vorne und reduzieren die Einkleberwerte. Jede Bedingung wird nacheinander überprüft, wodurch der Fluss leichter zu folgen ist. Die Hauptlogik kommt am Ende und ist eindeutig von der Fehlerbehandlung getrennt.

Dieser Ansatz skaliert viel besser, wenn Ihre Logik in Komplexität wächst.

6. Nicht überbeanspruchte Auflistverständnisse

Record -Verständnisse sind eine der elegantesten Merkmale von Python, werden jedoch bei Überladen mit komplexen Bedingungen oder Transformationen unlesbar.

Anstelle davon:

# Laborious to parse at a look
active_premium_emails = (person('electronic mail') for person in users_list 
                         if person('standing') == 'lively' and 
                         person('subscription') == 'premium' and 
                         person('email_verified') and
                         not person('electronic mail') in blacklisted_domains)

In dieser Liste wird das Verständnis zu viel Logik in eine Zeile eingebaut. Es ist schwer zu lesen und zu debuggen. Mehrere Bedingungen werden miteinander verkettet, was es schwierig macht, die Filterkriterien zu verstehen.

Tun Sie das:
Hier sind bessere Alternativen.

Choice 1: Funktion mit einem beschreibenden Namen

Extrahiert die komplexe Bedingung in eine benannte Funktion mit einem beschreibenden Namen. Das Listenverständnis ist jetzt viel klarer und konzentriert sich auf das, was es tut (E -Mails extrahieren), anstatt wie es Filterung ist.

def is_valid_premium_user(person):
    return (person('standing') == 'lively' and
            person('subscription') == 'premium' and
            person('email_verified') and
            not person('electronic mail') in blacklisted_domains)

active_premium_emails = (person('electronic mail') for person in users_list if is_valid_premium_user(person))

Choice 2: herkömmliche Schleife, wenn die Logik komplex ist

Verwendet eine traditionelle Schleife mit frühes Fortsetzung für Klarheit. Jede Bedingung wird separat überprüft, sodass es leicht zu debuggen kann, welcher Zustand möglicherweise fehlschlägt. Die Transformationslogik ist ebenfalls deutlich getrennt.

active_premium_emails = ()
for person in users_list:
    # Complicated filtering logic
    if person('standing') != 'lively':
        proceed
    if person('subscription') != 'premium':
        proceed
    if not person('email_verified'):
        proceed
    if person('electronic mail') in blacklisted_domains:
        proceed
        
    # Complicated transformation logic
    electronic mail = person('electronic mail').decrease().strip()
    active_premium_emails.append(electronic mail)

Auflistenverständnisse sollten Ihren Code lesbarer machen, nicht weniger. Wenn die Logik komplex wird:

  • Komplexe Bedingungen in benannte Funktionen unterteilen
  • Erwägen Sie, eine reguläre Schleife mit frühen Fortsetzung zu verwenden
  • Teilen Sie komplexe Operationen in mehrere Schritte auf

Denken Sie daran, das Ziel ist die Lesbarkeit.

7. Schreiben Sie wiederverwendbare reine Funktionen

Eine Funktion ist eine reine Funktion, wenn sie immer den gleichen Ausgang für die gleichen Eingänge erzeugt. Außerdem hat es keine Nebenwirkungen.

Anstelle davon:

total_price = 0  # World state

def add_item_price(item_name, amount):
    world total_price
    # Lookup value from world stock
    value = stock.get_item_price(item_name)
    # Apply low cost 
    if settings.discount_enabled:
        value *= 0.9
    # Replace world state
    total_price += value * amount
    
# Later in code...
add_item_price('widget', 5)
add_item_price('gadget', 3)
print(f"Complete: ${total_price:.2f}")

Dies verwendet den globalen Staat (total_price) was es schwierig macht.

Die Funktion hat Nebenwirkungen (modifizieren globaler Zustand) und hängt vom externen Zustand (Inventar und Einstellungen) ab. Dies macht es unvorhersehbar und schwer wiederzuverwenden.

Tun Sie das:

def calculate_item_price(merchandise, value, amount, low cost=0):
    """Calculate closing value for a amount of things with non-compulsory low cost.
    
    Args:
        merchandise: Merchandise identifier (for logging)
        value: Base unit value
        amount: Variety of gadgets
        low cost: Low cost as decimal 
        
    Returns:
        Ultimate value after reductions
    """
    discounted_price = value * (1 - low cost)
    return discounted_price * amount

def calculate_order_total(gadgets, low cost=0):
    """Calculate whole value for a set of things.
    
    Args:
        gadgets: Record of (item_name, value, amount) tuples
        low cost: Order-level low cost
        
    Returns:
        Complete value in any case reductions
    """
    return sum(
        calculate_item_price(merchandise, value, amount, low cost)
        for merchandise, value, amount in gadgets
    )

# Later in code...
order_items = (
    ('widget', stock.get_item_price('widget'), 5),
    ('gadget', stock.get_item_price('gadget'), 3),
)

whole = calculate_order_total(order_items, 
                             low cost=0.1 if settings.discount_enabled else 0)
print(f"Complete: ${whole:.2f}")

Die folgende Model verwendet reine Funktionen, die alle Abhängigkeiten als Parameter betrachten.

8. Schreiben Sie Docstrings für öffentliche Funktionen und Klassen

Die Dokumentation ist (und sollte nicht) ein nachträglicher Gedanke sein. Es ist ein zentraler Bestandteil des Code -Code. Gute Docstrings erklären nicht nur, was Funktionen tun, sondern warum sie existieren und wie sie sie richtig einsetzen.

Anstelle davon:

def celsius_to_fahrenheit(celsius):
    """Convert Celsius to Fahrenheit."""
    return celsius * 9/5 + 32

Dies ist eine minimale Dokumentation, die den Funktionsnamen nur wiederholt. Bietet keine Informationen zu Parametern, Rückgabetwerten oder Randfällen.
Tun Sie das:

def celsius_to_fahrenheit(celsius):
	"""
	Convert temperature from Celsius to Fahrenheit.
	The system used is: F = C × (9/5) + 32
	Args:
    	celsius: Temperature in levels Celsius (will be float or int)
	Returns:
    	Temperature transformed to levels Fahrenheit
	Instance:
    	>>> celsius_to_fahrenheit(0)
    	32.0
    	>>> celsius_to_fahrenheit(100)
    	212.0
    	>>> celsius_to_fahrenheit(-40)
    	-40.0
	"""
	return celsius * 9/5 + 32

Ein guter Docstring:

  • Dokumente Parameter und Rückgabewerte
  • Stellt alle Ausnahmen fest, die möglicherweise erhoben werden könnten
  • Bietet Nutzungsbeispiele

Ihre Docstrings dienen als ausführbare Dokumentation, die mit Ihrem Code synchronisiert bleibt.

9. Automatisieren Sie das Leinen und Formatieren

Verlassen Sie sich nicht auf die manuelle Inspektion, um Stilprobleme und gemeinsame Fehlern zu fangen. Automatische Instruments können die mühsame Arbeit der Sicherstellung von Codequalität und -konsistenz erledigen.

Sie können versuchen, diese Linien- und Formatierungswerkzeuge einzurichten:

  1. Schwarz – Codeformatierer
  2. Halskrause – Schneller Stoßunter seinen
  3. Mypy – statischer Typ Checker
  4. ISORT – Veranstalter importieren

Integrieren Sie sie mithilfe von Pre-Commit-Hooks, um vor jedem Commit automatisch Code zu überprüfen und zu formatieren:

  1. Vorkommind installieren: pip set up pre-commit
  2. Erstellen a .pre-commit-config.yaml Datei mit den konfigurierten Instruments
  3. Laufen pre-commit set up zu aktivieren

Dieses Setup gewährleistet einen konsistenten Codestil und fängt ohne manuelle Aufwand frühzeitig Fehler auf.

Sie können überprüfen 7 Instruments zum Schreiben eines besseren Python -Code mehr darüber zu wissen.

10. Vermeiden Sie alles, außer dass

Generische Ausnahmehändler verbergen Fehler und erschweren das Debuggen. Sie fangen alles auf, einschließlich Syntaxfehler, Speicherfehler und Tastaturinterrudeln.

Anstelle davon:

strive:
    user_data = get_user_from_api(user_id)
    process_user_data(user_data)
    save_to_database(user_data)
besides:
    # What failed? We'll by no means know!
    logger.error("One thing went mistaken")

Dies verwendet eine bloße Ausnahme zum Handeln:

  • Programmierfehler (wie Syntaxfehler)
  • Systemfehler (wie MemoryError)
  • Tastaturinterrudeln (Strg+C)
  • Erwartete Fehler (wie Netzwerkzeitüberschreitungen)

Dies erschwert das Debuggen extrem, da alle Fehler gleich behandelt werden.

Tun Sie das:

strive:
    user_data = get_user_from_api(user_id)
    process_user_data(user_data)
    save_to_database(user_data)
besides ConnectionError as e:
    logger.error(f"API connection failed: {e}")
    # Deal with API connection points
besides ValueError as e:
    logger.error(f"Invalid person knowledge obtained: {e}")
    # Deal with validation points
besides DatabaseError as e:
    logger.error(f"Database error: {e}")
    # Deal with database points
besides Exception as e:
    # Final resort for surprising errors
    logger.essential(f"Surprising error processing person {user_id}: {e}", 
                  exc_info=True)
    # Presumably re-raise or deal with generically
    elevate

Fängt spezifische Ausnahmen auf, die zu erwarten sind und angemessen behandelt werden können. Jeder Ausnahmetyp hat seine eigene Fehlermeldung und Handhabungsstrategie.

Die letzte Ausnahme außer Ausnahme fängt unerwartete Fehler auf und protokolliert sie mit vollem Traceback (exc_info=True) und räumen sie wieder, um vermeiden, ernsthafte Probleme nonetheless zu ignorieren.

Wenn Sie aus irgendeinem Grund einen All-Hap-Handler benötigen, verwenden Sie besides Exception as e: eher als bloß besides:und protokollieren Sie immer die vollständigen Ausnahmendetails mit exc_info=True.

Einpacken

Ich hoffe, Sie können zumindest einige dieser Praktiken in Ihrem Code verwenden. Beginnen Sie mit der Implementierung in Ihren Projekten.

Sie werden feststellen, dass Ihr Code wartbarer, nachweisbarer und leichter zu argumentierbarer wird.

Wenn Sie das nächste Mal versucht sind, eine Abkürzung zu nehmen, denken Sie daran: Code wird viel mehrmals gelesen als geschrieben. Joyful Clear Coding?

Bala Priya c ist ein Entwickler und technischer Schriftsteller aus Indien. Sie arbeitet gern an der Schnittstelle zwischen Mathematik, Programmierung, Datenwissenschaft und Inhaltserstellung. Ihre Interessensgebiete und Fachgebiete umfassen DevOps, Information Science und natürliche Sprachverarbeitung. Sie liest gerne, schreibt, codieren und Kaffee! Derzeit arbeitet sie daran, ihr Wissen mit der Entwicklergemeinschaft zu lernen und zu teilen, indem sie Tutorials, Anleitungen, Meinungsstücke und vieles mehr autorisiert. Bala erstellt auch ansprechende Ressourcenübersichten und Codierungs -Tutorials.



Von admin

Schreibe einen Kommentar

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