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.
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)))
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.
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.*.
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.
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 object | pygx | pygx topo=True | @dataclass | pydantic v2 |
|---|---|---|---|---|
| construct (kwargs) | 292 | 283 | 192 | 522 |
| attr get | 45 | 45 | 40 | 45 |
| clone (deep) | 915 | 882 | 2,270 | 1,620 |
| to dict/json | 389 | 388 | 631 | 532 |
| from dict/json | 977 | 982 | 196 | 656 |
Apple Silicon · read ratios, not absolutes · the full report covers ~50 operations across scales.
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.
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.
@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}
}
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.