Symbolic Object Model for Python

Your program is
also data.

Ordinary objects are built and then sealed — the call that produced them is gone. A pg.Object behaves like any Python object and keeps the structure it was built from, so your program is also data your own code can query, edit, diff, and search over. Executed and manipulated, in sync.

$pip install pygx
Read the docs
Python 3.12–3.14 · GIL-free 3.14t Native Rust core Apache 2.0
trainer.py
import pygx as pg

class Model(pg.Object):
    units: int = 8

class Trainer(pg.Object):
    model: Model
    lr: float = 0.01

t = Trainer(model=Model(units=128))
t.sym_init_args           # {'model': Model(units=128), 'lr': 0.01}

t.sym_rebind({'model.units': 64})    # edit any depth — validated

pg.diff(t, Trainer(model=Model(units=8)))
# Trainer(model=Model(units=Diff(left=64, right=8)))
Battle-tested lineage
Born at Google Brain / DeepMind Drives Vertex AI NAS, Pax & Vizier NeurIPS 2020 paper Hot paths in a native Rust core
One object · six powers

Take only as much power as you need

The same class grows from a plain struct into a searchable, comparable symbolic program — and the tree is opt-in, so objects that never need a position never pay for one.

What's in the box

A small core, a deep toolkit

The symbolic object model is the foundation. Focused sub-packages build search, algorithms, IO, and instrumentation on top — most re-exported at the top level as pg.*.

A class that is also a space of programs

Turn any field into a space

Say it where the value lives: replace a value with pg.oneof and the class is the space — no separate schema to keep in sync, no sweep loop to rewrite when a parameter is added. Enumerate it, sample it, or hand it to a search.

Performance

The symbolic model used to be a tax. It isn't anymore.

Hot paths run in a native Rust core (pygx-core, installed automatically); the pure-Python implementation remains the executable specification, and the full suite runs against both cores on every PR. Wheels ship for Linux, macOS, and Windows on CPython 3.12–3.14 — plus a genuinely free-threaded 3.14t wheel.

Validated construction beats pydantic v2 with the whole symbolic model attached; attribute reads are at parity. Deserialization is slower — PyGX emits a _type tag so JSON round-trips back to the real class.

median ns/op · 3-field objectpygxpygx topo=True@dataclasspydantic v2
construct (kwargs)292283192522
attr get45454045
clone (deep)9158822,2701,620
to dict/json389388631532
from dict/json977982196656

Apple Silicon · read ratios, not absolutes · the full report covers ~50 operations across scales.

When to reach for it

Where these properties cash out

And when not to: if your objects are only ever built and read, a dataclass or pydantic model is the better tool. PyGX earns its keep the moment a program becomes something you operate on.

Background

From AutoML at DeepMind
to general Python

PyGX was originally built at Google Brain / DeepMind by Daiyi Peng to power automated machine learning, under the name PyGlove. The abstraction underneath — the symbolic object model — turned out to be far more general than AutoML, and it drives Google Cloud Vertex AI NAS, Pax, and Vizier.

The original PyGlove paper was published at NeurIPS 2020. PyGX continues that work independently — same paradigm, rewritten around one property: the object retains its own structure.

citation.bib
@inproceedings{peng2020pyglove,
  title  = {PyGlove: Symbolic programming
            for automated machine learning},
  author = {Peng, Daiyi and Dong, Xuanyi and Real,
            Esteban and Tan, Mingxing and others},
  booktitle = {NeurIPS}, volume = {33},
  pages = {96--108}, year = {2020}
}

Ask your program a question.

A search space is a program with holes. A patch is a rule from program to program. A diff is the difference between two of them. One object model — and they all speak about the same tree.

$pip install pygx
See examples