Gjør Python-skript smartere med regex: 5 praktiske re-eksempler


Hvis du jobber med strenger i Python-skriptene dine og skriver utydelig logikk for å behandle dem, bør du se nærmere på regex i Python. Det lar deg beskrive mønstre i stedet for å skrive prosedyreløsninger. La oss se på noen ekte eksempler hvor Pythons re-modul gjør skriptene smartere.

Valider lumske brukerinnspill

Når du validerer brukerinput uten regex, har du sannsynligvis skrevet kode som føltes feil. La oss si at du vil validere et brukernavn med disse reglene:

  • Bare bokstaver, tall og understreker
  • Må starte med en bokstav
  • Lengde mellom 3 og 16 tegn

Dette er et vanlig krav. La oss se hva du trenger å gjøre uten å bruke Pythons re-modul:

def is_valid_username(username):
    if len(username) < 3 or len(username) > 16:
        return False

    if not username[0].isalpha():
        return False

    for char in username:
        if not (char.isalnum() or char == "_"):
            return False

    return True

Dette fungerer, men det er omstendelig, lett å gjøre feil, og hver ny regel betyr å legge til mer logikk. Det er nettopp her re-modulen skinner. Du beskriver reglene deklarativt ved å bruke re.compile() og bruker det mønsteret overalt:

import re

USERNAME_PATTERN = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]{2,15}$")

def is_valid_username(username):
    return bool(USERNAME_PATTERN.fullmatch(username))

La oss forstå hva som skjer.

  • ^: Dette indikerer starten på strengen
  • [a-zA-Z]: Må begynne med en bokstav
  • [a-zA-Z0-9_]{2,15}: Tillatte tegn og gjenværende lengde
  • $: Dette indikerer slutten på strengen

Når du begynner å validere inndata med re, blir det vanskelig å gå tilbake. Du uttrykker reglene direkte, og Python gjør det tunge arbeidet.

Ekstraher data fra rotete tekst

På et tidspunkt støter hvert Python-skript på rotete tekst. Loggfiler, pakkefangster, e-poster, skrapet HTML, blant annet. Utfordringen er å få strukturen ut uten å skrive skjør, linje-for-linje parsekode.

Tenk deg at du behandler en applikasjonsloggfil og ønsker å hente ut tidsstempler og feilmeldinger. En typisk logglinje kan se slik ut:

[2025-01-02 14:33:21] ERROR: Connection timed out after 30 seconds

Du kan prøve det slik:

def parse_log_line(line):
    if not line.startswith("["):
        return None

    parts = line.split("]")
    timestamp = parts[0][1:]

    if "ERROR:" not in parts[1]:
        return None

    message = parts[1].split("ERROR:")[1].strip()
    return timestamp, message

Dette fungerer for det eksakte formatet du testet med. Men det er sårbart. Ekstra mellomrom, manglende parenteser eller litt annerledes ordlyd kan bryte logikken. Dessuten er hensikten med koden skjult under strenggymnastikk. Med re-modulen kan du beskrive strukturen til linjen i stedet for å hakke den manuelt opp.

import re

LOG_PATTERN = re.compile(
    r"\[(?P<timestamp>[\d\-: ]+)\]\s+ERROR:\s+(?P<message>.+)"
)

def parse_log_line(line):
    match = LOG_PATTERN.search(line)
    if not match:
        return None

    return match.group("timestamp"), match.group("message")

Her forteller vi Python nøyaktig hva vi leter etter: et tidsstempel omsluttet av parenteser, etterfulgt av ordet «ERROR», etterfulgt av resten av meldingen.

Rens og normaliser tekst på én linje

Tekstrensing er en av de oppgavene som ser enkle ut til du faktisk gjør dem. Du må ofte håndtere ekstra mellomrom, ujevne skilletegn, tilfeldig tegnsetting eller formateringsmerkeligheter som gjør videre behandling irriterende.

La oss si at du behandler tekst sendt inn av brukere og ønsker å normalisere den før lagring eller sammenligning. Dine regler er:

  • Fjern ledende og etterfølgende mellomrom
  • Erstatt flere mellomrom, faner eller linjeskift med ett enkelt mellomrom
  • Fjern ikke-alfanumeriske tegn (bortsett fra mellomrom)
  • Konverter alt til små bokstaver

Uten å bruke re, kan en typisk tilnærming se slik ut:

def clean_text(text):
    text = text.strip()
    text = text.replace("\n", " ").replace("\t", " ")
    
    while "  " in text:
        text = text.replace("  ", " ")
    
    cleaned = []
    for char in text:
        if char.isalnum() or char == " ":
            cleaned.append(char)

    return "".join(cleaned).lower()

Dette er ikke forferdelig, men det er støyete. Intensjonen er spredt over løkker og gjentatte operasjoner. Og hvis du vil justere reglene, må du tilbake og redigere flere linjer.

Med Pythons re.sub() kan du beskrive mønstre av rot i stedet for å håndtere dem én sak om gangen.

import re

def clean_text(text):
    text = re.sub(r"\s+", " ", text) # normalize whitespace
    text = re.sub(r"[^a-zA-Z0-9 ]", "", text) # remove punctuation
    return text.strip().lower()

Det er samme logikk, uttrykt mye tydeligere.

Intelligent finn og erstatt

Hver Python-programmerer kjenner str.replace(). Det er enkelt, raskt og helt fint, helt til du trenger kontekst. I det øyeblikket erstatningen din avhenger av hvor noe dukker opp, hva som omgir det, eller en del av det du matchet, går str.replace() tom for kraft.

Tenk deg at du jobber med logger eller eksporterte data som inneholder sensitiv informasjon, og at du ønsker å maskere e-postadresser før du lagrer eller deler filen.

User john.doe@example.com logged in from 10.0.0.5

Du vil ha dette:

User ***@example.com logged in from 10.0.0.5

Ved å bruke grunnleggende strengmetoder, blir dette fort klønete:

def mask_email(text):
    words = text.split()
    masked = []

    for word in words:
        if "@" in word:
            username, domain = word.split("@", 1)
            masked.append("***@" + domain)
        else:
            masked.append(word)

    return " ".join(masked)

Dette forutsetter at e-poster er mellomromsseparert, håndterer ikke skilletegn godt, og blander sammen parsing med erstatningslogikk. Med re.sub() kan du matche e-postadresser direkte og erstatte bare den delen du er opptatt av.

import re

EMAIL_PATTERN = re.compile(r"([\w.-]+)@([\w.-]+\.\w+)")

def mask_emails(text):
    return EMAIL_PATTERN.sub(r"***@\2", text)

Her gjør Pythons re-modul det tunge arbeidet. Mønsteret finner e-postlignende strenger hvor som helst i teksten, fangstgruppene isolerer brukernavnet og domenet, og erstatningen gjenbruker deler av treffet via \2. Dette er finn-og-erstatt med forståelse.

Analyser semi-strukturert data

Selv i en nisjesituasjon når noen data ikke er klart strukturert og helt i fri form, redder Pythons re-modul stille dagen. Anta at du får strenger som dette:

name=John age=32 role=admin active=true

Målet ditt er å gjøre det om til et ordbokobjekt. Formatet er konsistent, men det er ingen garantier for mellomrom, rekkefølge eller hvilke nøkler som dukker opp. Du vet bare at det er en sekvens av nøkkel-verdi-par. Som et første forsøk kan du prøve split():

def parse_kv_string(text):
    result = {}
    parts = text.split()

    for part in parts:
        if "=" not in part:
            continue
        key, value = part.split("=", 1)
        result[key] = value

    return result

Dette fungerer helt til verdiene blir mer komplekse, avstanden endres, eller du ønsker validering. Nok en gang er logikken for å finne dataene og behandle dem tett koblet.

Med Pythons re.findall() kan du beskrive strukturen direkte og la motoren gjøre skanningen.

import re

KV_PATTERN = re.compile(r"(\w+)=([^\s]+)")

def parse_kv_string(text):
    return dict(KV_PATTERN.findall(text))

Det enkelt mønsteret uttrykker hele formatet: en ordlignende nøkkel, etterfulgt av likhetstegnet (=), etterfulgt av en verdi uten mellomrom. Pythons re-modul henter alle parene på én gang.


Hvis du er en Python-programmerer som har unngått regex, start i det små. Bruk re.fullmatch() for validering. Prøv re.sub() neste gang du rengjør tekst. Når mønstrene klikker, vil du oppdage at du skriver kortere skript som gjør mer.

Relaterte artikler