Meta-program Python Objects
Website · Documentation · Bird's-eye view · Tutorials · API
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 code can generate, inspect, diff, patch and tune it
as easily as it runs it.
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}
# Edit it anywhere in the tree — validated, and everything it touched re-initializes
t.sym_rebind({'model.units': 256})
# Replace any value with a space, and the class IS the search space
space = Trainer(model=Model(units=pg.oneof([64, 128])), lr=pg.oneof([0.1, 0.01]))
list(pg.iter(space)) # 4 programs: (64, 0.1) (64, 0.01) (128, 0.1) (128, 0.01)
No separate schema to keep in sync, no loop to rewrite when a parameter is added,
and no path-walking getattr/setattr to apply an override. That one
difference is the whole library — everything else is a consequence of it. The
idea has a name: symbolic programming, a paradigm where a program can
manipulate its own components as if they were plain data.
Install
pip install pygx # Python 3.12+
pip install "pygx[io]" # fsspec-backed remote IO (GCS, S3, ...)
pip install "pygx[concurrent]" # parallel execute/map with retries + progress
More
Programs written by algorithms — hand the space to a search
algo = pg.algo.evolution.regularized_evolution(
pg.algo.evolution.mutators.Uniform())
for net, feedback in pg.iter(space, 30, algo):
feedback(score(net)) # your objective; the algorithm does the rest
Each net is a real, validated instance of your class — not a parameter vector
you have to decode. The algorithm mutates the representation, so the same
Uniform() mutator works on a neural architecture, a tour of cities, or a
symbolic expression, without being written for any of them.
Worked examples: OneMax · Traveling Salesperson · Function Regression
Two programs, structurally compared — pg.diff
pg.diff(baseline, candidate)
# Trainer(model=Model(units=Diff(left=8, right=16)))
Not a text diff of two dumps — a structural one that answers which knob differs, at the position it differs. When runs are configs, this is the question you actually ask.
Programs that aren't finished yet — holes with names, not None
p = Trainer.partial() # `model` has no default — so it's a hole
pg.is_partial(p) # True
p.sym_missing() # {'model': MISSING_VALUE}
An abstract object's __init__ doesn't even run until it becomes concrete — so a
program can be assembled in stages by a user, a config file, a search, or a
model, and still be a real, checkable object at every stage.
Values that resolve from where they sit — no threading through constructors
class Layer(pg.Object, topo=True):
dropout: Any = pg.symbolic.ValueFromParentChain()
Net(dropout=0.5, layer=Layer()).layer.dropout # 0.5 — read from the ancestor
The alternative is threading the argument through every constructor in between, or a global. Here the field is optional at construction and resolved at read time from its position in the tree.
Code you don't own — pg.symbolize and pg.detour
Sym = pg.symbolize(ThirdPartyClass) # make it symbolic, no source edits
with pg.detour([(Adam, LAMB)]): # change what a LIBRARY constructs
third_party_training_loop() # → returns a LAMB
detour redirects construction inside code you cannot edit — nested,
transitive, outer-scope-wins. The usual answers are monkeypatching or forking.
Provenance for generated programs — where did this one come from?
with pg.track_origin():
variant = base.sym_clone()
variant.sym_origin.source is base # True
variant.sym_origin.tag # 'clone'
When programs are produced by other programs — mutated, evolved, sampled — that bookkeeping is otherwise yours to build.
Seeing it — pg.to_html renders any value as a browsable tree
pg.to_html(trainer) # an interactive, collapsible tree
A deeply nested program is unreadable as repr output. Any symbolic value
renders as a browsable tree, in a notebook or a file.
See it all on www.pygx.com — every capability with the problem it solves, side by side with the plain-Python alternative.
Fast. 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. Validated
construction beats pydantic v2 with the whole symbolic model attached, and
attribute reads are at dataclass parity — see the
full report.
Portable. Wheels for Linux, macOS and Windows on CPython 3.12–3.14, plus a
genuinely free-threaded 3.14t wheel (the GIL stays off; see
docs/design/gil-free.md §3). Elsewhere PyGX falls
back to the pure-Python core with identical behavior.
And when not to reach for it. If your objects are only ever built and read — request payloads, plain records, a config loaded once and never manipulated — a dataclass or pydantic model is the better tool. PyGX earns its keep the moment a program becomes something you operate on.
Documentation
| Bird's-eye view | 5–10 minute tour of the core ideas |
| Tutorials | tracks for Python, ML, AutoML, and Evolution |
| Learning PyGX | Symbolic OOP and Symbolic Detour, conceptually |
| API Reference | generated from source |
| Style guide | authoring pg.Object subclasses |
Runnable notebooks live in docs/notebooks/, source examples
in examples/.
Upgrading? See the 0.5 migration guide. 0.5.2 removed three long-misspelled names without aliases; earlier versions are covered by the 0.4 guide.
Background
PyGX was originally built at Google Brain / DeepMind by Daiyi Peng to power automated machine learning, under the name PyGlove. The abstraction underneath — symbolic object-oriented programming — turned out to be far more general than AutoML. The original paper was published at NeurIPS 2020, and the same ideas drive Google Cloud Vertex AI NAS, Pax, and Vizier.
Citing PyGX
@inproceedings{peng2020pyglove,
title={PyGlove: Symbolic programming for automated machine learning},
author={Peng, Daiyi and Dong, Xuanyi and Real, Esteban and Tan, Mingxing and Lu, Yifeng and Bender, Gabriel and Liu, Hanxiao and Kraft, Adam and Liang, Chen and Le, Quoc},
booktitle={Advances in Neural Information Processing Systems (NeurIPS)},
volume={33},
pages={96--108},
year={2020}
}
License
Apache License 2.0. PyGX is derived from PyGlove (also Apache 2.0); see
LICENSE and the per-file copyright headers for attribution.
PyGX is developed by Daiyi Peng.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pygx-0.5.4.tar.gz.
File metadata
- Download URL: pygx-0.5.4.tar.gz
- Upload date:
- Size: 1.3 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b615fa56e0faefbdea84f98c2db90d40d583226b0a123a0f5e83d09ace2f8847
|
|
| MD5 |
be5bee6e51dfc91a7e63e386c4fa3252
|
|
| BLAKE2b-256 |
7fcdb7da3afd77a6c910f79a6de372e10e077cf69668976c10ea71604f3d75c8
|
Provenance
The following attestation bundles were made for pygx-0.5.4.tar.gz:
Publisher:
pypi.yaml on free-solo/pygx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pygx-0.5.4.tar.gz -
Subject digest:
b615fa56e0faefbdea84f98c2db90d40d583226b0a123a0f5e83d09ace2f8847 - Sigstore transparency entry: 2634279233
- Sigstore integration time:
-
Permalink:
free-solo/pygx@6f1b8356d2619d0343290d194112fc46f670ecc3 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/free-solo
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yaml@6f1b8356d2619d0343290d194112fc46f670ecc3 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pygx-0.5.4-py3-none-any.whl.
File metadata
- Download URL: pygx-0.5.4-py3-none-any.whl
- Upload date:
- Size: 1.5 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
708a44ef390b5ff6e24b69ce9a0ffdbd9991c27d2150d60a1907c147814e5ca7
|
|
| MD5 |
785816e961c884f40a67863344ad27be
|
|
| BLAKE2b-256 |
c140505ffe5699f7ed2d73bbaf8743dd5ce84a9fde668dd9424596fc89c04d4f
|
Provenance
The following attestation bundles were made for pygx-0.5.4-py3-none-any.whl:
Publisher:
pypi.yaml on free-solo/pygx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pygx-0.5.4-py3-none-any.whl -
Subject digest:
708a44ef390b5ff6e24b69ce9a0ffdbd9991c27d2150d60a1907c147814e5ca7 - Sigstore transparency entry: 2634279291
- Sigstore integration time:
-
Permalink:
free-solo/pygx@6f1b8356d2619d0343290d194112fc46f670ecc3 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/free-solo
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yaml@6f1b8356d2619d0343290d194112fc46f670ecc3 -
Trigger Event:
workflow_dispatch
-
Statement type: