Hvis du arbeider med YAML i Python, bruker du nesten helt sikkert PyYAML. Det er standardbiblioteket,
det har eksistert siden 2006 og leveres med en funksjon kalt yaml.load() som har en kritisk
sikkerhetssårbarhet som har skadet mange team. Løsningen er ett ord — safe_load — men du
må forstå hvorfor, hva du kompromitterer med, og når det nyere ruamel.yaml-biblioteket er det bedre valget.
Denne guiden dekker praktisk YAML-parsing i Python: sikker innlasting, strømmer med flere dokumenter, dumping av Python-objekter tilbake til YAML, konfigfil-mønstre med standardverdier og feilhåndtering. Alle eksempler bruker virkelige scenarier — ingen plassholder-data.
Installasjon
pip install pyyaml
# For ruamel.yaml (covered later)
pip install ruamel.yamlyaml.safe_load() — Den Du Alltid Bør Bruke
Det viktigste å vite om
PyYAML
er at yaml.load() kan kjøre vilkårlig Python-kode innebygd i en YAML-fil.
Dette er ikke en teoretisk risiko — det er en veldokumentert angrepsvektor. Bruk alltid yaml.safe_load():
import yaml
# DANGEROUS — never use this with untrusted input
data = yaml.load(open('config.yaml'), Loader=yaml.FullLoader)
# SAFE — use this for any YAML from external sources
data = yaml.safe_load(open('config.yaml'))
# The attack: a YAML file could contain this, which executes Python
# !!python/object/apply:os.system ["rm -rf /important-dir"]yaml.safe_load() støtter bare standard YAML-typer:
strenger, tall, boolske verdier, null, lister og ordbøker. Det kaster en ConstructorError hvis YAML
inneholder Python-spesifikke tagger som !!python/object. Dette er nøyaktig den oppførselen du ønsker.
yaml.full_load() er tryggere enn den gamle bare yaml.load() men fortsatt mindre restriktiv
enn safe_load(). Start med safe_load() og oppgrader bare hvis du virkelig trenger det.Laste inn en YAML-konfigfil
Her er et realistisk konfigurasjonsinnlastingsmønster for en webapplikasjon. Vi laster inn en YAML-konfigfil og bruker Pythons ordbok-sammenslåing for å fylle inn standardverdier for alt som ikke er angitt:
# config.yaml
database:
host: postgres.internal
port: 5432
name: myapp_prod
pool_size: 10
redis:
host: redis.internal
port: 6379
logging:
level: INFO
format: jsonimport yaml
from pathlib import Path
from typing import Any
DEFAULT_CONFIG = {
'database': {
'host': 'localhost',
'port': 5432,
'name': 'myapp',
'pool_size': 5,
'ssl': False,
},
'redis': {
'host': 'localhost',
'port': 6379,
'db': 0,
},
'logging': {
'level': 'DEBUG',
'format': 'text',
}
}
def deep_merge(base: dict, override: dict) -> dict:
"""Recursively merge override into base, returning a new dict."""
result = base.copy()
for key, value in override.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = deep_merge(result[key], value)
else:
result[key] = value
return result
def load_config(config_path: str | Path) -> dict[str, Any]:
path = Path(config_path)
if not path.exists():
raise FileNotFoundError(f"Config file not found: {path}")
with path.open('r', encoding='utf-8') as f:
raw = yaml.safe_load(f)
if raw is None:
return DEFAULT_CONFIG.copy()
return deep_merge(DEFAULT_CONFIG, raw)
config = load_config('config.yaml')
print(config['database']['host']) # postgres.internal
print(config['database']['ssl']) # False (from defaults)
print(config['redis']['db']) # 0 (from defaults)Dumpe Python-objekter til YAML
yaml.dump() serialiserer Python-ordbøker, lister, strenger, tall, boolske verdier og None til YAML.
Som standard brukes flytstil (inline-krøllparenteser) — sett default_flow_style=False for den
lesbare blokkstilen:
import yaml
from dataclasses import dataclass, asdict
@dataclass
class ServiceConfig:
name: str
replicas: int
image: str
port: int
tags: list[str]
service = ServiceConfig(
name='payment-api',
replicas=3,
image='payment-api:2.4.1',
port=8080,
tags=['payments', 'backend', 'critical']
)
# Convert dataclass to dict first, then dump to YAML
output = yaml.dump(
asdict(service),
default_flow_style=False,
sort_keys=False, # preserve insertion order
allow_unicode=True
)
print(output)
# image: payment-api:2.4.1
# name: payment-api
# port: 8080
# replicas: 3
# tags:
# - payments
# - backend
# - critical
# Write to file
with open('service-config.yaml', 'w', encoding='utf-8') as f:
yaml.dump(asdict(service), f, default_flow_style=False, sort_keys=False)Strømmer med Flere Dokumenter med load_all
YAML støtter flere dokumenter i en enkelt fil, adskilt av ---. Dette er vanlig i
Kubernetes-manifester der én enkelt fil kan inneholde en Deployment, en Service og en ConfigMap.
Bruk yaml.safe_load_all() for å iterere over alle dokumenter:
import yaml
# manifests.yaml contains multiple Kubernetes resources separated by ---
with open('manifests.yaml', 'r') as f:
# safe_load_all returns a generator
documents = list(yaml.safe_load_all(f))
for doc in documents:
if doc is None:
continue
kind = doc.get('kind', 'Unknown')
name = doc.get('metadata', {}).get('name', 'unnamed')
print(f"{kind}: {name}")
# Deployment: payment-api
# Service: payment-api-svc
# ConfigMap: payment-api-configDu kan også skrive flere dokumenter til en strøm med yaml.dump_all():
import yaml
documents = [
{'kind': 'Deployment', 'metadata': {'name': 'api'}, 'spec': {'replicas': 2}},
{'kind': 'Service', 'metadata': {'name': 'api-svc'}, 'spec': {'port': 80}},
]
output = yaml.dump_all(documents, default_flow_style=False)
print(output)
# kind: Deployment
# metadata:
# name: api
# spec:
# replicas: 2
# ---
# kind: Service
# metadata:
# name: api-svc
# spec:
# port: 80ruamel.yaml — Når Du Trenger å Bevare Kommentarer
PyYAML har én vesentlig begrensning: det fjerner kommentarer ved innlasting. Hvis du laster inn en YAML-fil, endrer den og skriver den tilbake, er alle kommentarer borte. For konfigfiler som mennesker vedlikeholder, er tapet av kommentarer et dealbreaker.
ruamel.yaml implementerer en round-trip-parser som bevarer kommentarer, nøkkelrekkefølge og formatering — det støtter som standard YAML 1.2-spesifikasjonen. Det er riktig valg når du programmatisk redigerer YAML som mennesker vil lese etterpå:
from ruamel.yaml import YAML
yaml = YAML()
yaml.preserve_quotes = True
# This config.yaml has important comments we need to keep:
# database:
# host: localhost # change this for production
# port: 5432 # default PostgreSQL port
# pool_size: 5 # increase under heavy load
with open('config.yaml', 'r') as f:
config = yaml.load(f)
# Modify a value
config['database']['host'] = 'postgres.prod.internal'
config['database']['pool_size'] = 20
# Write back — comments and formatting are preserved!
with open('config.yaml', 'w') as f:
yaml.dump(config, f)
# Result:
# database:
# host: postgres.prod.internal # change this for production
# port: 5432 # default PostgreSQL port
# pool_size: 20 # increase under heavy load- Bruk PyYAML når du leser YAML for forbruk — parsing av konfigurasjon til appen din, innlasting av testfixtures, behandling av Kubernetes-manifester programmatisk.
- Bruk ruamel.yaml når du redigerer YAML som mennesker vedlikeholder — oppdatering av konfigfiler på stedet, verktøy som endrer CI-konfigurasjoner, alt der tap av kommentarer ville vært problematisk.
- ruamel.yaml er også YAML 1.2-kompatibelt som standard, noe som betyr at Norge-problemet (
NO→false) ikke påvirker det. PyYAML bruker YAML 1.1 som standard.
Feilhåndtering
YAML-parseringsfeil kaster yaml.YAMLError, som er basisklassen for alle PyYAML-unntak.
Fang alltid det når du laster inn YAML fra upålitelige eller brukerleverte kilder:
import yaml
from pathlib import Path
def load_user_config(path: str) -> dict:
try:
with open(path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
except FileNotFoundError:
raise FileNotFoundError(f"Config file not found: {path}")
except yaml.scanner.ScannerError as e:
# Includes line/column info in the error message
raise ValueError(f"YAML syntax error in {path}:\n{e}")
except yaml.YAMLError as e:
raise ValueError(f"Invalid YAML in {path}: {e}")
if data is None:
return {}
if not isinstance(data, dict):
raise TypeError(f"Expected a YAML mapping at top level, got {type(data).__name__}")
return datayaml.safe_load() garanterer bare
gyldig YAML-syntaks — det validerer ikke dataformens form. En konfigfil med database:
øverst er fin; en konfigfil med en liste øverst er også gyldig YAML. Legg til type- og strukturkontroller
etter innlasting, eller bruk et skjemavalideringsbibliotek som
Pydantic for å parse den innlastede ordboken
til en typet modell.Oppsummering
PyYAML dekker det store flertallet av YAML-arbeid i Python: bruk alltid yaml.safe_load()
(ikke yaml.load()), bruk yaml.safe_load_all() for strømmer med flere dokumenter,
og bruk yaml.dump() med default_flow_style=False for lesbar utdata.
Når du trenger å bevare kommentarer eller få YAML 1.2-semantikk, bytt til ruamel.yaml — det er en enkel
oppgradering for lesing og en liten API-endring for skriving. For syntaksfeil før koden din kjører,
YAML-validatoren forteller deg nøyaktig hvilken linje og kolonne som er ødelagt.