mrkeyoor.com_
Wed 05 Aug 05:04 UTC
PyPIUtilsupdated 05 Aug 2026

pyyaml

PyYAML is the standard YAML parser and emitter for Python: yaml.safe_load turns YAML text into dicts, lists, and scalars, yaml.dump goes the other way. It is a dependency of half the Python ecosystem (Ansible, Docker Compose tooling, CI config readers, Kubernetes clients) and at roughly 300 million downloads a week it is one of the most installed packages on PyPI. It implements the YAML 1.1 spec, with optional C-accelerated parsing through LibYAML bindings.

Verdict

For reading YAML that other tools produce, PyYAML is the boring correct answer and safe_load is the only load you should ever type. For editing YAML in place or for strict 1.2 semantics it is the wrong tool, and ruamel.yaml exists precisely because of that.

API stability4/5The load/dump API has been essentially frozen for a decade; the one real break was 5.1 making bare yaml.load warn and 6.0 requiring an explicit Loader, both security-motivated.
Docs2/5The official documentation is a wiki page last restructured years ago; it covers the API but reads like 2009, and critical guidance like the safe_load rule is easy to miss. Most people learn PyYAML from Stack Overflow.
Maintenance3/5Maintained by the YAML community with recent pushes and a 6.0.3 release, but movement is slow: 350+ open issues and PRs, years between releases, and no progress toward YAML 1.2.
Ecosystem5/5Roughly 300M weekly downloads and a dependency of Ansible-class tooling; YAML-adjacent Python code overwhelmingly assumes PyYAML behavior.

Use it if

  • You need to read YAML config files, Kubernetes manifests, CI definitions, or anything else the infrastructure world writes in YAML
  • You want the zero-surprise choice: every tutorial, linter, and tool assumes PyYAML semantics, and it is probably already in your dependency tree
  • You parse large YAML files and can install the LibYAML C bindings (yaml.CSafeLoader) for a substantial speedup over the pure-Python parser
  • You only need load and dump; for that job the API has been unchanged and reliable for years
Skip it if

Setup reality

pip install pyyaml just works because binary wheels ship for CPython on Linux, macOS, and Windows. The catch is the C accelerator: the wheels include LibYAML bindings when available, but yaml.load defaults to the pure-Python loader anyway, so you must explicitly pass CSafeLoader (with a try/except ImportError fallback) to get the speed you installed. Building from source without wheels needs a compiler and libyaml-dev headers. Historical trivia that still bites: version 6.0 broke installs for months in 2023 because of a Cython 3 build interaction on old pinned versions.

Patterns

Parse YAML from a fileload-yaml-safely

import yaml

with open('config.yml') as f:
    config = yaml.safe_load(f)

Always safe_load. Plain yaml.load with a full loader can execute arbitrary object construction from untrusted input.

Write a dict out as YAMLdump-yaml

import yaml

text = yaml.safe_dump(
    data,
    sort_keys=False,
    default_flow_style=False,
    allow_unicode=True,
)

sort_keys defaults to True and will alphabetize your keys; set it False to keep insertion order.

Read a multi-document streammulti-document

import yaml

with open('manifests.yml') as f:
    for doc in yaml.safe_load_all(f):
        if doc is not None:
            handle(doc)

Kubernetes-style files separated by --- need load_all; empty documents come back as None, so filter them.

Use the LibYAML C parser when availablefast-c-loader

import yaml

try:
    from yaml import CSafeLoader as SafeLoader
except ImportError:
    from yaml import SafeLoader

data = yaml.load(stream, Loader=SafeLoader)

safe_load never uses the C parser on its own; you must pass CSafeLoader explicitly, with a fallback for wheels built without LibYAML.

Serialize a custom classdump-custom-class

import yaml

def point_representer(dumper, p):
    return dumper.represent_mapping('!point', {'x': p.x, 'y': p.y})

yaml.add_representer(Point, point_representer, Dumper=yaml.SafeDumper)
yaml.safe_dump(Point(1, 2))

Register against SafeDumper if you dump with safe_dump; registering on the default Dumper alone silently does nothing for safe_dump.

Parse a custom tag safelyload-custom-tag

import yaml

def env_constructor(loader, node):
    import os
    return os.environ.get(loader.construct_scalar(node), '')

yaml.SafeLoader.add_constructor('!env', env_constructor)
data = yaml.safe_load('token: !env API_TOKEN')

Adding constructors to SafeLoader keeps safe_load safe while supporting the one tag you actually need.

Avoid the yes/no/on/off boolean trapnorway-problem

import yaml

yaml.safe_load('country: NO')   # {'country': False}  (YAML 1.1)
yaml.safe_load("country: 'NO'") # {'country': 'NO'}
yaml.safe_load('debug: on')     # {'debug': True}

This is YAML 1.1 behavior, not a bug; quote any value that could look like a boolean, and quote version numbers like 3.10 too or they load as floats.

Load from and dump to stringsstring-to-yaml-string

import yaml

data = yaml.safe_load('a: 1\nb: [2, 3]')
text = yaml.safe_dump(data)  # returns a str when no stream given

dump writes to the stream argument if given and returns None in that case; only the no-stream form returns the string.

Dump multi-line strings as literal blocksblock-style-strings

import yaml

def str_presenter(dumper, s):
    style = '|' if '\n' in s else None
    return dumper.represent_scalar('tag:yaml.org,2002:str', s, style=style)

yaml.add_representer(str, str_presenter, Dumper=yaml.SafeDumper)

Without this, multi-line strings dump as quoted scalars full of \n escapes instead of readable | blocks.

Know what happens with duplicate keysduplicate-keys

import yaml

yaml.safe_load('a: 1\na: 2')  # {'a': 2}, no error

PyYAML silently keeps the last duplicate key; if duplicates should be errors, validate separately or use a stricter parser.

Reuse blocks with anchors and merge keysanchors-aliases

import yaml

doc = '''\ndefaults: &d\n  retries: 3\n  timeout: 10\nprod:\n  <<: *d\n  timeout: 30\n'''
yaml.safe_load(doc)['prod']  # {'retries': 3, 'timeout': 30}

Merge keys (<<) work on load, but dump never re-creates anchors from shared structure in a readable way; round-tripping loses them.

Alternatives

PackageRegistryPick it when
ruamel.yamlPyPIYou need round-trip editing that preserves comments and formatting, or YAML 1.2 semantics
strictyamlPyPIYou want a restricted, schema-validated YAML subset that removes implicit typing footguns entirely