NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #1536 most downloaded on PyPI
Optimized PyTree Utilities.
Last release 1 months ago
20 Aug 2026
Ships fairly regularly
a new release about every 2 months
Nearly every release is documented
notes for 14 of 14 stable releases
15 versions withdrawn
withdrawn after publishing
4 years old
30 releases · first in 2022
One column per quarter.
Preserve dict insertion order on unflatten for the Python tree_flatten_one_level path mirroring the C++ path by @XuehaiPan in #280 .
unflatten for the Python tree_flatten_one_level path mirroring the C++ path by @XuehaiPan in #280.frozendict support for Python 3.15+ (PEP 814), with PyTreeKind.FROZENDICT, optree.treespec_frozendict(), optree.treespec.frozendict(), and optree.typing.FrozenDict by @XuehaiPan in #274.Python_FIND_ABI for CMake's FindPython by @XuehaiPan in #292.typing-extensions to 4.10.0 for typing_extensions.TypeIs by @XuehaiPan in #285.Py_GIL_DISABLED for free-threaded debug builds on Windows when building the C extension to work around an upstream CMake FindPython bug by @XuehaiPan in #285.__repr__ while the registry lock is held and wedges any thread that is flattening by @XuehaiPan in #290.collections.namedtuple subclass or PyStructSequence emitting the override warning even though the registration is rejected, which raised UserWarning instead of ValueError under warnings-as-errors by @XuehaiPan in #290.__del__ or weakref callback re-entered the registry while its lock was held by @XuehaiPan in #290.SystemError from optree._C.get_registry_size() when a concurrent registration slipped between its two reads by @XuehaiPan in #290.optree.tree_iter() hanging uninterruptibly when the is_leaf predicate or a custom flatten function advances the same iterator, now reported as a RuntimeError by @XuehaiPan in #290.PyStructSequence field cache, caused by re-acquiring the GIL while still holding the cache lock by @XuehaiPan in #290.optree.structseq_fields() and the struct sequence accessors reporting the trailing hidden field names for unnamed sequence slots, by mapping each field to the slot its member offset names; the pure Python fallback, which cannot read those offsets, recovers the positions from a probe instance instead of assuming the unnamed slots trail the named ones by @XuehaiPan in #290.<unnamed@N> by @XuehaiPan in #290.StructSequenceEntry reporting an unnamed sequence slot as field='unnamed field', which reads as a real field name, now reported by its index by @XuehaiPan in #290.PyTreeSpec.__setstate__() accepting a malformed state, which could read out of bounds or abort the interpreter when the treespec was later used by @XuehaiPan in #290.PyTreeSpec.__getstate__() returning the treespec's internal mutable containers, so mutating the pickled state corrupted an otherwise immutable treespec by @XuehaiPan in #290.PyTreeSpec.__setstate__() borrowing the key lists from the state it was given rather than copying them, so mutating that state afterwards silently reordered the restored treespec by @XuehaiPan in #290.PyTreeSpec with protocol 0 or 1 aborting the interpreter, by reducing through copyreg.__newobj__ by @XuehaiPan in #290.PyTreeSpec.compose(), PyTreeSpec.broadcast_to_common_suffix(), treespec_transform(), and treespec_from_collection() silently rebinding a custom node to a different registration when an empty namespace adopted a non-empty one by @XuehaiPan in #290.PyTreeSpec.broadcast_to_common_suffix() sorting the argument treespec's dictionary keys in place while building its key-mismatch error message, corrupting a treespec the caller still holds by @XuehaiPan in #290.treespec_from_collection() on a leaf reporting an escalated UserWarning as a confusing SystemError, by checking the return value of PyErr_WarnEx() by @XuehaiPan in #290.treespec_from_collection() keeping the caller's namespace on a leaf or None root, which made otherwise identical treespecs compare unequal by @XuehaiPan in #290.treespec_is_prefix() and treespec_is_suffix() comparing against a stale subtree when a dictionary node's keys had been reordered by @XuehaiPan in #290.is_leaf predicate to the garbage collector, leaking any reference cycle that passes through it by @XuehaiPan in #290.is_leaf predicate part way through flattening being read out of bounds on Python versions before 3.13.0a4, now raising IndexError by @XuehaiPan in #290.treespec_paths(), treespec_accessors(), and PyTreeSpec.broadcast_to_common_suffix() instead of raising RecursionError by @XuehaiPan in #290.DataclassEntry and AttrsEntry resolving an integer entry against every declared field rather than the children the registration emitted, which returned the wrong attribute for a class holding a metadata or non-init field, now read from the record register_node() leaves on the class itself by @XuehaiPan in #290.optree.dataclasses.register_node() and optree.integrations.attrs.register_node() leaving a class in a half-registered state that could never be registered again: the registry entry and the field marker are now committed together, and a failure of either rolls the other back by @XuehaiPan in #290.optree.dataclasses.register_node() silently dropping InitVar pseudo-fields, which are neither children nor metadata and cannot round-trip, now rejected with a pointer to the generic API by @XuehaiPan in #290.tree_broadcast_common() and broadcast_common() applying the caller's is_leaf predicate to an internal sentinel tree, which could raise from the predicate or collapse a filled subtree and under-replicate by @XuehaiPan in #290.PyTreeEntry equality and hashing comparing the bytecode of __call__() and codify() rather than the methods themselves, so two entry classes that happened to share an implementation compared equal by @XuehaiPan in #290.PyTreeAccessor overriding __eq__() while inheriting tuple.__ne__(), so comparing one with a plain tuple of equal entries reported False for both == and != by @XuehaiPan in #290.GetAttrEntry.codify() emitting invalid attribute access for a field name that is not an identifier, now rendered as a getattr() call by @XuehaiPan in #290.prefix_errors() raising AssertionError instead of reporting an error for a custom node whose per-instance entries differ while its metadata does not, a pair broadcast_prefix() accepts by @XuehaiPan in #290.prefix_errors() raising TypeError while formatting a dictionary key mismatch when the keys have different types, now ordered with total_order_sorted() by @XuehaiPan in #290.tree_transpose_map(), tree_transpose_map_with_path(), tree_transpose_map_with_accessor(), and tree_partition() rejecting a leafless outer structure when an explicit inner_treespec leaves nothing to infer by @XuehaiPan in #290.tree_broadcast_map() family flattening a custom node twice when called with a single input tree, which broke a one-shot flatten function and contradicted the documented equivalence with tree_map() by @XuehaiPan in #290.treespec_entry() and treespec_child() annotating their index as int while the runtime accepts any SupportsInt or SupportsIndex object by @XuehaiPan in #290.optree.utils.total_order_sorted() mistaking a TypeError raised by the caller's key callback for a comparison failure and silently returning the sequence unsorted, and calling the callback twice per element by @XuehaiPan in #290.tree_broadcast_common() documenting that its results share one structure and broadcast_common() documenting that it returns two pytrees rather than two lists of leaves by @XuehaiPan in #290.Full Changelog: v0.19.1...v0.20.0
Add attrs integration module optree.integrations.attrs with field , define , frozen , mutable , make_class , register_node , and AttrsEntry by @Xuehai
attrs integration module optree.integrations.attrs with field, define, frozen, mutable, make_class, register_node, and AttrsEntry by @XuehaiPan in #273.optree.dataclasses.register_node to register existing dataclasses as pytree nodes by @XuehaiPan in #273.GetAttrEntry to support dotted attribute paths for traversing nested attributes (e.g., a.b.c) by @XuehaiPan.functools.Placeholder support and re-export for optree.functools.partial (Python 3.14+) by @XuehaiPan in #276.msvcp140.dll by disabling MSVC's constexpr std::mutex constructor by @XuehaiPan in #279. Issued by @jmkolbe in #278.Full Changelog: v0.19.0...v0.19.1
Add subinterpreters support for Python 3.14+ by @XuehaiPan in #245.
Full Changelog: https://github.com/metaopt/optree/compare/v0.18.0...v0.19.0
Full Changelog: v0.18.0...v0.19.0
Remove previously deprecated singular-named modules in #209 by @XuehaiPan in #238.
manylinux-riscv64 wheels by @XuehaiPan in #243.cp{313,314}-ios / cp{313,314}-android / cp{312,313}-pyodide wheels by @XuehaiPan in #242 and #244.(__tree_flatten__, __tree_unflatten__) for register_pytree_node_class(cls) by @XuehaiPan in #253.Full Changelog: https://github.com/metaopt/optree/compare/v0.17.0...v0.18.0
Add WASM support by @XuehaiPan in #226.
cibuildwheel from 2.23 to 3.0 by @dependabot in #228.pybind11 3.0.0 by @XuehaiPan in #231.pybind11 macro defined as 0 instead of non-exist by @XuehaiPan in #227.Full Changelog: https://github.com/metaopt/optree/compare/v0.16.0...v0.17.0
Explicitly set recursion limit for recursion tests by @XuehaiPan in #207.
pybind11::native_enum to create enum class PyTreeKind if available by @XuehaiPan in #214.pybind11::smart_holder to create class PyTreeSpec and PyTreeIter if available by @XuehaiPan in #217.tp_clear for class PyTreeSpec and PyTreeIter by @XuehaiPan in #218.tree_partition by @pfackeldey in #222.optree.{accessor,integration} -> optree.{accessors,integrations} by @XuehaiPan in #209.setuptools>=77 for PEP-639 by @XuehaiPan in #208.typing-extensions to 4.6.0 for typing_extensions.TypeAliasType by @XuehaiPan in #216.PyType_Ready twice and use PyType_Modified instead by @XuehaiPan in #214.optree.typing.PyTree[T] for Python 3.14 due to immutable UnionType by @XuehaiPan in #216.Full Changelog: https://github.com/metaopt/optree/compare/v0.15.0...v0.16.0
Remove deprecated key path APIs by @XuehaiPan in #195.
PyTreeSpec.traverse by @XuehaiPan in #197.cmake version and guard minimum version in setup.py by @XuehaiPan in #205.optree.Partial by @XuehaiPan in #196.Full Changelog: https://github.com/metaopt/optree/compare/v0.14.1...v0.15.0
Support using system cmake executable during setup by @mgorny in #188.
cmake executable during setup by @mgorny in #188.optree.pytree and optree.treespec by @lqhuang in #189.namespace via register_pytree_node.get() by @XuehaiPan in #190.Full Changelog: https://github.com/metaopt/optree/compare/v0.14.0...v0.14.1
Add method PyTreeSpec.one_level and PyTreeSpec.is_one_level by @XuehaiPan in #179.
PyTreeSpec.one_level and PyTreeSpec.is_one_level by @XuehaiPan in #179.PyTreeSpec.transform by @XuehaiPan in #177.Full Changelog: https://github.com/metaopt/optree/compare/v0.13.1...v0.14.0
Nothing published for this version
CI workflow improvements and minor fixes for typing annotation.
tree_flatten_one_level by @XuehaiPan in #168.PyTree[T] and registry lookup / register functions by @XuehaiPan in #160 and #166.include/{ => optree}/*.h by @XuehaiPan in #167.optree.dataclasses.dataclass and optree.dataclasses.field by @manulari in #165.Full Changelog: https://github.com/metaopt/optree/compare/v0.13.0...v0.13.1
@manulari made their first contribution in #165.
tree_flatten_one_level by @XuehaiPan in #168.PyTree[T] and registry lookup / register functions by @XuehaiPan in #160 and #166.include/{ => optree}/*.h by @XuehaiPan in #167.optree.dataclasses.dataclass and optree.dataclasses.field by @manulari in #165.Full Changelog: v0.13.0...v0.13.1
Add Python 3.13 and Python 3.13t (free-threading build) support.
dataclasses integration.namedtuple and PyStructSequence by @XuehaiPan in #157.dataclasses integration by @XuehaiPan in #142.pybind11_DIR by @XuehaiPan in #155.inline / constexpr / noexcept qualifiers by @XuehaiPan in #159.cmake's FindPython module by @XuehaiPan in #151.structseq_fields cache support by @XuehaiPan in #150.Full Changelog: https://github.com/metaopt/optree/compare/v0.12.1...v0.13.0
Fix warning regression during import when launch with strict warning filters by @XuehaiPan in #149.
Full Changelog: https://github.com/metaopt/optree/compare/v0.12.0...v0.12.1
Update minimal version of typing-extensions to 4.5.0 for typing_extensions.deprecated by @XuehaiPan in #134.
tree_flatten_with_accessor and PyTreeSpec.accessors by @XuehaiPan in #108.optree.functools by @XuehaiPan in #134.stable tag instead of 2.12.0 for pybind11 version by @XuehaiPan in #146.setup.py with importlib utilities by @XuehaiPan in #135.typing-extensions to 4.5.0 for typing_extensions.deprecated by @XuehaiPan in #134.OrderedDict by @XuehaiPan in #133.tp_traverse by @XuehaiPan in #144.optree.Partial and replace with optree.functools.partial by @XuehaiPan in #134.Full Changelog: https://github.com/metaopt/optree/compare/v0.11.0...v0.12.0
Add function is_namedtuple_instance and is_structseq_instance and result caches by @XuehaiPan in #121.
is_namedtuple_instance and is_structseq_instance and result caches by @XuehaiPan in #121.tree_iter function by @XuehaiPan in #130.tree_transpose_map and tree_transpose_map_with_path by @XuehaiPan in #127.PyTreeSpec instances by @XuehaiPan in #120.str objects in PyObject_GetAttr calls by @XuehaiPan in #106 and #109.clang-format and clang-tidy from PyPI by @XuehaiPan in #107._make and _asdict in function is_namedtuple_class by @XuehaiPan in #105.treespec_is_leaf as strict by default by @XuehaiPan in #120.py::handle and py::object usage in function signature by @XuehaiPan in #115.namedtuple and PyStructSequence types by @XuehaiPan in #111.__bases__ rather than __base__ in function is_structseq_class by @XuehaiPan in #104.treespec.entries() by @XuehaiPan in #116.Full Changelog: https://github.com/metaopt/optree/compare/v0.10.0...v0.11.0
Add tree_ravel function for JAX/NumPy/PyTorch array/tensor tree manipulation by @XuehaiPan in #100.
tree_ravel function for JAX/NumPy/PyTorch array/tensor tree manipulation by @XuehaiPan in #100.PyTreeSpec by @XuehaiPan in #98.tree_flatten_one_level by @XuehaiPan in #101.broadcast_common, tree_broadcast_common, tree_broadcast_map, and tree_broadcast_map_with_path by @XuehaiPan in #87.tree_is_leaf and add is_leaf argument to function all_leaves by @XuehaiPan in #93.PyTreeSpec.entry and PyTreeSpec.child by @XuehaiPan in #88.PyTreeSpec.is_prefix to be consistent with PyTreeSpec.flatten_up_to by @XuehaiPan in #94.MAX_RECURSION_DEPTH to 2000 on Windows by @XuehaiPan in #85.abseil-cpp version to 20230802.1 by @XuehaiPan in #80.repr / hash calls to resolve infinite recursion under self-referential case by @XuehaiPan and @JieRen98 in #82.abseil-cpp by @XuehaiPan in #85.Full Changelog: https://github.com/metaopt/optree/compare/v0.9.2...v0.10.0
Bump pybind11 version to 2.11.1 and add initial Python 3.12 support by @XuehaiPan in #78.
pybind11 version to 2.11.1 and add initial Python 3.12 support by @XuehaiPan in #78.abseil-cpp version to 20230802.0 by @XuehaiPan in #79.is_leaf function by @XuehaiPan in #76.Full Changelog: https://github.com/metaopt/optree/compare/v0.9.1...v0.9.2
Use py::handle_of(obj) rather than deprecated obj.get_type() by @XuehaiPan in #49.
py::type::handle_of(obj) rather than deprecated obj.get_type() by @XuehaiPan in #49.abseil-cpp version to 20230125.3 by @XuehaiPan in #57.@runtime_checkable decorator for CustomTreeNode protocol class by @XuehaiPan in #56.Full Changelog: https://github.com/metaopt/optree/compare/v0.9.0...v0.9.1
Now `optree` preserves `dict` / `defaultdict` key order in the output of `tree_unflatten`, `tree_map`, and `tree_map_with_path`.
Now optree preserves dict / defaultdict key order in the output of tree_unflatten, tree_map, and tree_map_with_path.
This behavior needs extra operations during flattening/unflattening. There would be performance regression for dict and defaultdict. No impact is added for other collection types, such as OrderedDict, list, tuple.
optree v0.8.0: the output dict from tree_unflatten is stored in sorted order.
tree_flatten and tree_unflatten will lose the information above the insertion order of the input dict. Map over a dict with the identity function will change the key order.
>>> optree.tree_map(lambda x: x, {'b': 2, 'a': 1})
{'a': 1, 'b': 2}
optree v0.9.0: the output dict from tree_unflatten will have the consistent key order with the input dict.
>>> optree.tree_map(lambda x: x, {'b': 2, 'a': 1})
{'b': 2, 'a': 1}
As the key order is preserved, it's safe to use tree_map to process "unordered dict" (builtins.dict) (e.g., **kwargs in functions). In this case, users no longer need to convert dict to OrderedDict manually.
def func(*args, **kwargs):
args, kwargs = optree.tree_map(do_something, (args, kwargs))
# optree v0.8.0: args, kwargs = optree.tree_map(do_something, (args, OrderedDict(kwargs)))
...
Note that tree_map still maps the leaves in sorted key order (the same order as tree_flatten and tree_leaves).
>>> leaves = []
...
... def add_leaves(x):
... leaves.append(x)
... return x
...
>>> ptree.tree_map(add_leaves, {'b': 2, 'a': 1})
{'b': 2, 'a': 1}
>>> leaves
[1, 2]
Revert "Change keyword argument initial to initializer for tree_reduce to align with functools.reduce". The previous change is reverted due to a documentation issue for Python (see also python/cpython#102757 and python/cpython#102759). The argument name is changed back to initial in tree_reduce. Users are recommended to use positional arguments.
tree_unflatten, tree_map, and tree_map_with_path by @XuehaiPan in #46.initializer back to initial for tree_reduce to align with functools.reduce C implementation by @XuehaiPan in #47.Full Changelog: https://github.com/metaopt/optree/compare/v0.8.0...v0.9.0
Add new and refactor utility functions for namedtuple and PyStructSequence types.
Add new and refactor utility functions for namedtuple and PyStructSequence types.
is_namedtupleis_namedtuple_classis_structseqis_structseq_classnamedtuple_fields (new)structseq_fieldsAdd more methods and properties for PyTreeSpec class.
PyTreeSpec.is_prefix (treespec_is_prefix and __le__ / __lt__)PyTreeSpec.is_suffix (treespec_is_suffix and __ge__ / __gt__)PyTreeSpec.paths (treespec_paths)PyTreeSpec.entries (treespec_entries)Add tree reduce functions.
tree_sumtree_maxtree_minMiscellaneous changes.
Rephrase error messages.
>>> optree.tree_map(lambda x, y: x + y, {'a': 1, 'b': 2}, {'b': 3, 'c': 4})
Traceback (most recent call last):
...
ValueError: dictionary key mismatch; expected key(s): ['a', 'b'], got key(s): ['b', 'c'], missing key(s): ['a'], extra key(s): ['c']; dict: {'b': 3, 'c': 4}.
Add function tree_broadcast_prefix.
Linter integration for flake8 plugins and ruff.
PyTreeSpec.flatten_up_to now allows ordered (OrderedDict) and unordered (dict and defaultdict) dictionaries mutually exchangeable.
This behavior will affect all functions based on flatten_up_to, such as tree_map, tree_map_with_path, tree_broadcast_prefix.
Calling multi-treemap with mixed inputs of ordered and unordered dictionaries will no longer raise ValueError.
optree v0.7.0: raise ValueError if the dictionary type mismatch
>>> from collections import *
>>> import optree
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> od = OrderedDict([('b', 5), ('c', 6), ('a', 4)])
>>> dd = defaultdict(int, {'c': 9, 'a': 7, 'b': 8})
>>> optree.tree_map(lambda x, y, z: 100 * x + 10 * y + z, d, od, dd)
Traceback (most recent call last):
...
ValueError: Expected dict, got OrderedDict([('b', 5), ('c', 6), ('a', 4)]).
>>> optree.tree_map(lambda x, y, z: 100 * x + 10 * y + z, od, dd, d)
Traceback (most recent call last):
...
ValueError: Expected collections.OrderedDict, got defaultdict(<class 'int'>, {'c': 9, 'a': 7, 'b': 8}).
>>> optree.tree_map(lambda x, y, z: 100 * x + 10 * y + z, od, dd, d)
Traceback (most recent call last):
...
ValueError: Expected collections.defaultdict, got {'a': 1, 'b': 2, 'c': 3}.
optree v0.8.0: allow mixed input of dict, OrderedDict, and defaultdict.
The result pytree type and key ordering are determined by the first input pytree.
>>> from collections import *
>>> import optree
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> od = OrderedDict([('b', 5), ('c', 6), ('a', 4)])
>>> dd = defaultdict(int, {'c': 9, 'a': 7, 'b': 8})
>>> optree.tree_map(lambda x, y, z: 100 * x + 10 * y + z, d, od, dd)
{'a': 147, 'b': 258, 'c': 369}
>>> optree.tree_map(lambda x, y, z: 100 * x + 10 * y + z, od, dd, d)
OrderedDict([('b', 582), ('c', 693), ('a', 471)])
>>> optree.tree_map(lambda x, y, z: 100 * x + 10 * y + z, dd, d, od)
defaultdict(<class 'int'>, {'a': 714, 'b': 825, 'c': 936})
Use more appropriate exception types.
structseq_fields now raises TypeError rather than ValueError when got non-PyStructSequence types
optree v0.7.0: raise ValueError when got non-PyStructSequence types
>>> from collections import *
>>> import optree
>>> optree.structseq_fields((1, 2))
Traceback (most recent call last):
...
ValueError: Expected StructSequence, got (1, 2).
>>> mytuple = namedtuple('mytuple', ['a', 'b'])
>>> optree.structseq_fields(mytuple(1, 2))
Traceback (most recent call last):
...
ValueError: Expected StructSequence, got mytuple(a=1, b=2).
optree v0.8.0: raise TypeError when got non-PyStructSequence types
>>> from collections import *
>>> import optree
>>> optree.structseq_fields((1, 2))
Traceback (most recent call last):
...
TypeError: Expected an instance of PyStructSequence type, got (1, 2).
>>> mytuple = namedtuple('mytuple', ['a', 'b'])
>>> optree.structseq_fields(mytuple(1, 2))
Traceback (most recent call last):
...
TypeError: Expected an instance of PyStructSequence type, got mytuple(a=1, b=2).
optree._C.InternalError now inherits from SystemError rather than RuntimeError.
optree v0.7.0: need to use RuntimeError to catch InternalError
>>> import optree
>>> optree._C.InternalError.mro()
[<class 'optree._C.InternalError'>, <class 'RuntimeError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>]
optree v0.8.0: need to use SystemError to catch InternalError
>>> import optree
>>> optree._C.InternalError.mro()
[<class 'optree._C.InternalError'>, <class 'SystemError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>]
Change keyword argument initial to initializer for tree_reduce to align with functools.reduce.
PyTreeSpec.paths and PyTreeSpec.entries by @XuehaiPan in #43.namedtuple and PyStructSequence type by @XuehaiPan in #41.PyTreeSpec.is_prefix and PyTreeSpec.is_suffix and function tree_broadcast_prefix by @XuehaiPan in #40.tree_sum, tree_max, and tree_min by @XuehaiPan in #39.PyDict_Contains ($O (n)$) rather than sorting ($O (n \log n)$) by @XuehaiPan in #37.ruff and flake8 plugins integration by @XuehaiPan in #33.ValueError to TypeError in structseq_fields) by @XuehaiPan in #41.optree._C.InternalError from SystemError rather than RuntimeError by @XuehaiPan in #41.initial to initializer for tree_reduce to align with functools.reduce by @XuehaiPan in #39.Full Changelog: https://github.com/metaopt/optree/compare/v0.7.0...v0.8.0
Add PyStructSequence types as internal node types by @XuehaiPan in #30.
PyStructSequence types as internal node types by @XuehaiPan in #30.PyStructSequence types as internal node types by @XuehaiPan in #30.Full Changelog: https://github.com/metaopt/optree/compare/v0.6.0...v0.7.0
Add Linux AArch64 and Windows ARM64 wheels by @XuehaiPan in #25.
PyTreeSpec.type and method PyTreeSpec.is_leaf by @XuehaiPan in #26.namedtuple by @XuehaiPan in #24.clang-tidy integration and update code style by @XuehaiPan in #20.doctest integration and fix docstring by @XuehaiPan in #23.Add property PyTreeSpec.num_children by @XuehaiPan.
PyTreeSpec.num_children by @XuehaiPan.Add custom exceptions for internal error handling by @XuehaiPan.
PyTreeSpec equality test and hash by @XuehaiPan.Better internal error handling by @XuehaiPan in #17.
Fix segmentation fault error for global imports @XuehaiPan in #14.
Add namespace support for custom node type registry by @XuehaiPan in #12.
tree_map_ by @XuehaiPan.Add Read the Docs integration by @XuehaiPan.
None is Node" and "None is Leaf" by @XuehaiPan.OrderedDict and defaultdict and deque as builtin support by @XuehaiPan.PyTreeDef to PyTreeSpec by @XuehaiPan.NamedTuple for Python 3.8-3.10 by @XuehaiPan.Add cibuildwheel integration for building wheels by @XuehaiPan.
cibuildwheel integration for building wheels by @XuehaiPan.The first beta release of OpTree by @XuehaiPan.
Your coding agent can read these notes before it upgrades. Set up the MCP server →