Skip to content

refactor(module)!: A proposal to reorg modules and functions with new tree module and treespec module#186

Closed
lqhuang wants to merge 13 commits intometaopt:mainfrom
lqhuang:refactor-modules
Closed

refactor(module)!: A proposal to reorg modules and functions with new tree module and treespec module#186
lqhuang wants to merge 13 commits intometaopt:mainfrom
lqhuang:refactor-modules

Conversation

@lqhuang
Copy link
Contributor

@lqhuang lqhuang commented Feb 6, 2025

Description

Detail changes have been added into implementated tasks.

Motivation and Context

I'm here to proposal a breaking change that reorg the functions like tree_* and treesepc_* into new module tree and treespec

Pros:

  1. Improve how LSP hints the name of functions optree.tree_* -> tree.*
  2. We could use more specific shorter package name like
    from optree import tree
    from optree import treespec
    
    tree.flatten(...)
    tree.map_(...)
  3. Simplify namespace and module, which would be more intuitive. And we could refactor operations for global utils or only for tree / treespec
  4. Upstream project jax did it either ...

Cons:

  1. tree and treespec are quite common variable names, it increases the collision of naming.
  2. Many function names after refactor (like tree_map, tree_max ...) will be conflict with the Python global namespace. We need to refactor carefully to avoid unexpected behaviors.
  3. I havn't find better package name alias like
    import optree.tree as opt  # ???
    import optree.tree as optr # ???
    
    import optree.treespec as opts # this looks like better than above

Open questions:

  1. Should we add alias/deprecated warnings

Types of changes

What types of changes does your code introduce? Put an x in all the boxes that apply:

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds core functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation (update in the documentation)

Implemented Tasks and Details

  • Add new tree module, and update doc string of functions
    1. To avoid conflicating with global builtins (max, all, ...), I imported builtins module and rewrote those functions to builtins.map(), builtins.max() explicitly.
    2. Keep poential conflict functions with _tree prefix like _tree_map, _tree_leaves and redfine them at the end of module
    6. Check the last few line of optree/tree.py for full list of export functions via redfinition way.
  • Alias tree_* functions in ops module
  • Tune layout of tests for tree module
  • Add new treespec module, and update doc string of functions
  • Alias treespec_ functions in ops module
  • Tune layout of tests for treespec module
  • Refactor global documentations

Checklist

Go over all the following points, and put an x in all the boxes that apply.
If you are unsure about any of these, don't hesitate to ask. We are here to help!

  • I have read the CONTRIBUTION guide. (required)
  • My change requires a change to the documentation.
  • I have updated the tests accordingly. (required for a bug fix or a new feature)
  • I have updated the documentation accordingly.
  • I have reformatted the code using make format. (required)
  • I have checked the code using make lint. (required)
  • I have ensured make test pass. (required)

Appendix

Post local test and lint results

My local env

OS: macOS (Apple Silicon)
Python: 3.12.7
CMake: 3.31.3
clang: 16.0.0

Lint results

$ make ruff
/Users/lqhuang/Git/optree/.venv/bin/python3 -m pip show ruff &>/dev/null || (cd && /Users/lqhuang/Git/optree/.venv/bin/python3 -m pip install --upgrade ruff)
/Users/lqhuang/Git/optree/.venv/bin/python3 -m ruff --version
ruff 0.9.4
/Users/lqhuang/Git/optree/.venv/bin/python3 -m ruff check .
All checks passed!
$ make mypy
.../.venv/bin/python3 -m mypy --version
mypy 1.15.0 (compiled: yes)
.../.venv/bin/python3 -m mypy optree
Success: no issues found in 16 source files
$ make flake8
/Users/lqhuang/Git/optree/.venv/bin/python3 -m flake8 --version
7.1.1 (flake8-bugbear: 24.12.12, flake8-comprehensions: 3.16.0, flake8-docstrings: 1.7.0, flake8-pyi: 24.9.0, flake8_simplify: 0.21.0, mccabe: 0.7.0, pycodestyle:
4.12.1, pyflakes: 3.2.0) CPython 3.12.7 on Darwin
/Users/lqhuang/Git/optree/.venv/bin/python3 -m flake8 --doctests --count --show-source --statistics --exclude=.venv
./tests/integration/test_jax.py:25:1: E402 module level import not at top of file
import jax
^
./tests/integration/test_jax.py:26:1: E402 module level import not at top of file
import jax.numpy as jnp
^
./tests/integration/test_jax.py:27:1: E402 module level import not at top of file
import numpy as np
^
./tests/integration/test_jax.py:28:1: E402 module level import not at top of file
from jax import lax
^
./tests/integration/test_jax.py:29:1: E402 module level import not at top of file
from jax._src import dtypes
^
./tests/integration/test_jax.py:31:1: E402 module level import not at top of file
import optree
^
./tests/integration/test_jax.py:32:1: E402 module level import not at top of file
from helpers import LEAVES, TREES, parametrize
^
./tests/integration/test_numpy.py:25:1: E402 module level import not at top of file
import numpy as np
^
./tests/integration/test_numpy.py:27:1: E402 module level import not at top of file
import optree
^
./tests/integration/test_numpy.py:28:1: E402 module level import not at top of file
from helpers import LEAVES, TREES, parametrize
^
./tests/integration/test_torch.py:26:1: E402 module level import not at top of file
import torch
^
./tests/integration/test_torch.py:28:1: E402 module level import not at top of file
import optree
^
./tests/integration/test_torch.py:29:1: E402 module level import not at top of file
from helpers import LEAVES, TREES, parametrize
^
./tests/test_registry.py:898:5: SIM117 Use 'with pytest.raises(TypeError, match='The namespace must be a string'), optree.dict_insertion_ordered(True, namespace=1):' instead of multiple with statements
    with pytest.raises(TypeError, match='The namespace must be a string'):
    ^
./tests/test_registry.py:901:5: SIM117 Use 'with pytest.raises(ValueError, match=re.escape(
    'The namespace cannot be an empty string.')), optree.dict_insertion_ordered(True, namespace=''):' instead of multiple with statements
    with pytest.raises(ValueError, match=re.escape('The namespace cannot be an empty string.')):
    ^
./tests/test_treespec.py:108:16: SIM202 Use 'treespec == treespec' instead of 'not treespec != treespec'
        assert not (treespec != treespec)
               ^
./tests/test_treespec.py:122:16: SIM201 Use 'treespec != suffix_treespec' instead of 'not treespec == suffix_treespec'
        assert not (treespec == suffix_treespec)
               ^
./tests/test_treespec.py:129:16: SIM201 Use 'suffix_treespec != treespec' instead of 'not suffix_treespec == treespec'
        assert not (suffix_treespec == treespec)
               ^
./tests/test_treespec.py:633:20: SIM202 Use 'treespec == expected_treespec' instead of 'not treespec != expected_treespec'
            assert not (treespec != expected_treespec)
                   ^
13    E402 module level import not at top of file
2     SIM117 Use 'with pytest.raises(TypeError, match='The namespace must be a string'), optree.dict_insertion_ordered(True, namespace=1):' instead of multiple with statements
2     SIM201 Use 'treespec != suffix_treespec' instead of 'not treespec == suffix_treespec'
2     SIM202 Use 'treespec == treespec' instead of 'not treespec != treespec'
19
make: *** [flake8] Error 1

All doc test under /optree/optree/tree.py is passed

$ make doctest
=== Failed tests ===
python -m xdoctest /Users/lqhuang/Git/optree/optree/functools.py partial:0
python -m xdoctest /Users/lqhuang/Git/optree/optree/registry.py register_pytree_node:0
python -m xdoctest /Users/lqhuang/Git/optree/optree/typing.py PyTree:0
python -m xdoctest /Users/lqhuang/Git/optree/optree/typing.py PyTreeTypeVar:0
python -m xdoctest /Users/lqhuang/Git/optree/optree/integration/numpy.py tree_ravel:0
python -m xdoctest /Users/lqhuang/Git/optree/optree/integration/jax.py tree_ravel:0
python -m xdoctest /Users/lqhuang/Git/optree/optree/integration/torch.py tree_ravel:0
=== 7 failed, 110 passed, 5 warnings in 0.24 seconds ===
make: *** [xdoctest] Error 1

All doc test under /optree/optree/tree.py is passed

Testing results

$ make test

...
...
...

---------- coverage: platform darwin, python 3.12.7-final-0 ----------
Name                                                       Stmts   Miss  Cover   Missing
----------------------------------------------------------------------------------------
/Users/lqhuang/Git/optree/optree/__init__.py                  14      0   100%
/Users/lqhuang/Git/optree/optree/accessor.py                 203      0   100%
/Users/lqhuang/Git/optree/optree/dataclasses.py              121     20    83%   182-183, 273-278, 282-283, 430-435, 439-440, 452-453
/Users/lqhuang/Git/optree/optree/functools.py                 55      0   100%
/Users/lqhuang/Git/optree/optree/integration/__init__.py      15      2    87%   43-44
/Users/lqhuang/Git/optree/optree/integration/jax.py           57     48    16%   45-285
/Users/lqhuang/Git/optree/optree/integration/numpy.py         53     46    13%   26-210
/Users/lqhuang/Git/optree/optree/integration/torch.py         58     52    10%   26-215
/Users/lqhuang/Git/optree/optree/ops.py                      200      0   100%
/Users/lqhuang/Git/optree/optree/registry.py                 196      0   100%
/Users/lqhuang/Git/optree/optree/tree.py                     231      0   100%
/Users/lqhuang/Git/optree/optree/treespec.py                 346    346     0%   19-3560
/Users/lqhuang/Git/optree/optree/typing.py                   115     10    91%   167-168, 524-528, 547-554
/Users/lqhuang/Git/optree/optree/utils.py                     29      0   100%
----------------------------------------------------------------------------------------
TOTAL                                                       1693    524    69%
Coverage XML written to file coverage.xml


========================================================================== slowest 10 durations ==========================================================================
13.05s call     tests/test_typing.py::test_is_namedtuple_cache
13.03s call     tests/test_typing.py::test_is_structseq_cache
13.02s call     tests/test_typing.py::test_structseq_fields_cache
10.99s call     tests/test_registry.py::test_unregister_pytree_node_no_reference_leak
9.80s call     tests/test_typing.py::test_namedtuple_fields_cache
1.39s call     tests/test_treespec.py::test_treespec_self_referential
1.37s call     tests/test_treespec.py::test_treeiter_self_referential
0.79s call     tests/test_concurrent.py::test_treespec_self_referential
0.28s call     tests/test_prefix_errors.py::test_standard_dictionary[tree([Vector3D(x=3, y=None, z=[4, 'foo'])])-none_is_leaf(False)-namespace('')-dict_should_be_sorted(False)-dict_session_namespace('namespace')]
0.23s setup    tests/test_ops.py::test_round_trip_is_leaf[tree(deque([], maxlen=0))-is_leaf(<function is_list at 0x102a004d0>)-none_is_leaf(False)-namespace('')-dict_should_be_sorted(False)-dict_session_namespace('')]
============================================================== 93954 passed, 3 skipped in 187.23s (0:03:07) ==============================================================

Updates after first draft:

  1. I agree that

    For functions prefixed with treespec_*, users can use the PyTreeSpec.* methods directly.

    I still create a module named as treespec, but it only has constructor functions like treespec.list, treespec.nametuple, ..., etc for creating an instance of TreeSpec.

  2. Reimplment pytree (previous tree module) as a thin wrapper layer of optree.tree_* with updated documentations.

  3. How to test: I replaced all optree.tree_ into pytree. in test_ops.py, assume that the wrapper works then the implementation must work too. Luckily I didn't find any varable named as pytree in test_ops.py. Besides, I havn't find any test for functions like optree.treesepc_list, if I missed them mistakenly, please help me to locate them.

  4. Major lints and tests passed in my local env

  5. Added documentations and CHANGELOG

  6. Rebased from main HEAD.

  • Implement pytree. module as wrappers for ops.tree_*.
  • Tune tests for pytree module
  • Add new treespec module, and update doc string of functions
  • Refactor global documentations and changelog

PR Checklist

  • I have read the CONTRIBUTION guide. (required)
  • My change requires a change to the documentation.
  • I have updated the tests accordingly. (required for a bug fix or a new feature)
  • I have updated the documentation accordingly.
  • I have reformatted the code using make format. (required)
  • I have checked the code using make lint. (required)
  • I have ensured make test pass. (required)

Examples of new doc page

Screenshot 2025-02-13 at 19 33 36 Screenshot 2025-02-13 at 19 33 49

Merge of current PR will close #189

@lqhuang lqhuang requested a review from XuehaiPan as a code owner February 6, 2025 10:12
@lqhuang lqhuang marked this pull request as draft February 6, 2025 10:12
@lqhuang
Copy link
Contributor Author

lqhuang commented Feb 6, 2025

@XuehaiPan Hi, could you take a look on this proposal.

What's your opinion?

I only improve add a new tree module now. I will try to complete left work (treespec module, docs, ... etc) after your approval. It would be lots of work and could be separate into multiple PRs.

Any feedback is welcome. It's also fine to reject! Never mind, I understand that.

Thanks and appreciate your time!

@lqhuang lqhuang changed the title refactor(module): A proposal to reorg modules and functions with new tree module and treespec module refactor(module)!: A proposal to reorg modules and functions with new tree module and treespec module Feb 6, 2025
@lqhuang lqhuang requested a review from XuehaiPan February 6, 2025 11:01
@XuehaiPan XuehaiPan added enhancement New feature or request python Something related to the Python source code labels Feb 6, 2025
@codecov
Copy link

codecov bot commented Feb 6, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (51beb20) to head (86df30b).

Additional details and impacted files
@@            Coverage Diff             @@
##              main      #186    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           36        39     +3     
  Lines         3786      4038   +252     
==========================================
+ Hits          3786      4038   +252     
Flag Coverage Δ
unittests 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@lqhuang
Copy link
Contributor Author

lqhuang commented Feb 6, 2025

@XuehaiPan I checked codebase of jax to find some inspirations and references

Here is how jax refactors tree module.

# https://github.com/jax-ml/jax/blob/0fb278a0b9152f341df69662c573d14f991fd51a/jax/_src/tree_util.py#L350

@export
def tree_map(f: Callable[..., Any],
             tree: Any,
             *rest: Any,
             is_leaf: Callable[[Any], bool] | None = None) -> Any:
  """Alias of :func:`jax.tree.map`."""
  leaves, treedef = tree_flatten(tree, is_leaf)
  all_leaves = [leaves] + [treedef.flatten_up_to(r) for r in rest]
  return treedef.unflatten(f(*xs) for xs in zip(*all_leaves))

What I learned:

  1. keep src
  2. strip doc string
  3. mark as export
# https://github.com/jax-ml/jax/blob/0fb278a0b9152f341df69662c573d14f991fd51a/jax/_src/tree.py#L115C1-L156C1
def map(f: Callable[..., Any],
        tree: Any,
        *rest: Any,
        is_leaf: Callable[[Any], bool] | None = None) -> Any:
  """Maps a multi-input function over pytree args to produce a new pytree.

  Args:
    f: function that takes ``1 + len(rest)`` arguments, to be applied at the
      corresponding leaves of the pytrees.
    tree: a pytree to be mapped over, with each leaf providing the first
      positional argument to ``f``.
    rest: a tuple of pytrees, each of which has the same structure as ``tree``
      or has ``tree`` as a prefix.
    is_leaf: an optionally specified function that will be called at each
      flattening step. It should return a boolean, which indicates whether the
      flattening should traverse the current object, or if it should be stopped
      immediately, with the whole subtree being treated as a leaf.

  Returns:
    A new pytree with the same structure as ``tree`` but with the value at each
    leaf given by ``f(x, *xs)`` where ``x`` is the value at the corresponding
    leaf in ``tree`` and ``xs`` is the tuple of values at corresponding nodes in
    ``rest``.

  Examples:

    >>> import jax
    >>> jax.tree.map(lambda x: x + 1, {"x": 7, "y": 42})
    {'x': 8, 'y': 43}

    If multiple inputs are passed, the structure of the tree is taken from the
    first input; subsequent inputs need only have ``tree`` as a prefix:

    >>> jax.tree.map(lambda x, y: [x] + y, [5, 6], [[7, 9], [1, 2]])
    [[5, 7, 9], [6, 1, 2]]

  See Also:
    - :func:`jax.tree.leaves`
    - :func:`jax.tree.reduce`
  """
  return tree_util.tree_map(f, tree, *rest, is_leaf=is_leaf)

What I learned:

  1. write full function names and signatrues
  2. full doc string
  3. passing all arguments to original implementations

FYI

@XuehaiPan
Copy link
Member

Here is how jax refactors tree module.

@lqhuang I think we are different than jax because jax does not export tree_* functions to the top namespace jax.tree_*.

The rationale for having a separate jax.tree module is that users will need to use:

jax.tree_util.tree_map(...)

This is not as simple as:

jax.tree.map(...)

It trims _util.tree_ -> . and reduces 10 chars for each call.

However, for optree, all tree_* methods are exported in the top level.

@lqhuang
Copy link
Contributor Author

lqhuang commented Feb 6, 2025

Thanks for your feedback! 😄

However, for optree, all tree_* methods are exported in the top level.

You're right. That's why I put the motivation

  1. Upstream project jax did it either ...

as the last one. It's not that important.

On the other hand, tree.flatten is still shorter than optree.tree_flatten.

The most important experiences I want to share here is I'm always trying to input first tree to trigger LSP hints, then every time I realized I need to use optree to make IDE work.

Second, if what I want is treespec, I spend more time to search the correct function name with prefix treespec_* in IDE hints menu after I input optree.tr.

Don't worry, we could discuss more, and don't be in a hurry to draw a conclusion.

After we have more clear scenarios and consensus, we would find proper solutions we both glad with, then you can decide the final approach, at least achieving a minimal goal (like only adding two module tree and treespec by alias for convenience purpose) is easy, and I will follow your guidance and instructions to finish it.

@lqhuang
Copy link
Contributor Author

lqhuang commented Feb 7, 2025

I hope to supplement more context. In general, my personal engineering practice is that we should embrace simplicity (not simple) and API should be intuitive.

And I must point out a conflict of interest or the motivation only required by myself is my ongoing project heavily relies on operations of TreeSpec. In the past usage of PyTree, nobody cares about TreeDef / TreeSpec.

As same library developer and maintainer, I definitely understand the necessity of back forward compatibility and the pain of API evolution. And that is also where the community (like me) could help.

When I'm a new lib user of optree, I would be confused by what the difference between optree.tree_leaves() and optree.all_leaves(), optree.tree_broadcast_prefix() and optree.broadcast_prefix() without checking documentation.

Searching documentation obviously is a good habit. But what if we could do it better by using a well-structured namespace to avoid mistakes or cognitive burden w/o documentation.

I expect that optree will have different modules storing its own ops for different objects.

  1. optree.tree module: all related operations on PyTree
  2. optree.treespec module: all related operations on TreeSpec
  3. optree.accessor/ module: all entries for accessor
  4. optree.utils: Useful utility functions
  5. export common and frequent ops to top namespace optree

Just like the abstraction of np, it also has lots of function conflicted with Python stdlib. The difference is all these operations work on almost the same object NDArray, but we have multiple targets. And I propose we could alias optree.tree into optr, though I'm not very satisfied the abbr. yet.

I may want more about your thoughts your opinions, including and not limited, for more concrete solutions or specific roadmap.

@XuehaiPan
Copy link
Member

I prefer to add a shortcut module for tree_* functions only. Maybe named optree.tree or optree.pytree. The example usage:

from optree import pytree

max_value = pytree.max({'a': 1, 'b': (2, 3), 'c': {'d': [4, 5], 'e': 6}})

For functions prefixed with treespec_*, users can use the PyTreeSpec.* methods directly.

@lqhuang lqhuang force-pushed the refactor-modules branch 2 times, most recently from 254bc32 to 0c5a33d Compare February 13, 2025 07:27
@lqhuang lqhuang marked this pull request as ready for review February 13, 2025 07:33
@lqhuang
Copy link
Contributor Author

lqhuang commented Feb 13, 2025

optree/treespec.py: note: In function "dict":
optree/treespec.py:267:50: error: Function "optree.treespec.tuple" is not valid
as a type  [valid-type]
        mapping: Mapping[Any, PyTreeSpec] | Iterable[tuple[Any, PyTreeSpec...
                                                     ^

CI auto committed the code from Tuple to tuple

I intentionally used Tuple as type hints in treesepc.py like

mapping: Mapping[Any, PyTreeSpec] | Iterable[Tuple[Any, PyTreeSpec]] = (),  # noqa: UP006

instead of

mapping: Mapping[Any, PyTreeSpec] | Iterable[tuple[Any, PyTreeSpec]] = (),

Because there exists def tuple() function of the same scope.

My local ruff lint works fine

$ make ruff
/Users/lqhuang/Git/optree/.venv/bin/python3 -m pip show ruff &>/dev/null || (cd && /Users/lqhuang/Git/optree/.venv/bin/python3 -m pip install --upgrade ruff)
/Users/lqhuang/Git/optree/.venv/bin/python3 -m ruff --version
ruff 0.9.4
/Users/lqhuang/Git/optree/.venv/bin/python3 -m ruff check .
All checks passed!

But CI lint say it's an error? What's the different in config of CI env?

ruff.....................................................................Failed
- hook id: ruff
- exit code: 1
- files were modified by this hook

@XuehaiPan XuehaiPan self-assigned this Feb 13, 2025
@XuehaiPan
Copy link
Member

CI auto committed the code from Tuple to tuple

It is modified by pyupgrade which does not respect ruff noqas. If you still want to annotate the functions with tuple, you need to use builtins.tuple[...].

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert the changes in this file. The optree.tree_* functions are still the standard APIs. We should test on these.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any suggestion on how to reduce boilerplate tests?

I thought it. But after I make a copy of test_ops.py for optree.pytree, I find it would be noisy.

So I decided to test the wrapper directly.

I know it's still the standard, but in this way, we could test both two? If any change happens on optree.tree_*, from failed tests, we could also know we forget to update optree.pytree module.

Copy link
Member

@XuehaiPan XuehaiPan Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could add a new file test_shortcut.py to test optree.pytree re-exports all tree_* functions.

def test_pytree_reexports():
    assert set(optree.pytree.__all__) == {
        name[len('tree_'):] for name in optree.__all__ if name.startswith('tree_')
    }

    for name in optree.pytree.__all__:
        assert getattr(optree.pytree, name) is getattr(optree, f'tree_{name}')

none_is_leaf: bool = False,
namespace: str = '',
) -> tuple[list[T], PyTreeSpec]:
"""Flatten a pytree.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still do not recommend redefining APIs with duplicate type annotations and docstrings. Just import and re-export the functions with a different name is enough. As for the function names in the docstrings (e.g., tree_flatten vs. flatten). It isn't a big problem.

Re-exporting the functions will also not need to handle the annotation problems of Tuple vs. tuple.

Copy link
Contributor Author

@lqhuang lqhuang Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In file optree/pytree.py, there is no annotation problem between Tuple and tuple.

I'm already using the approach that just re-exporting, so your concrete suggest is striping all type hints and docs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm already using the approach that just re-exporting, so your concrete suggest is striping all type hints and docs?

My suggestion is:

from optree.ops import (
    tree_api1 as api1,
    tree_api2 as api2,
    tree_api3 as api3,
)


__all__ = [
    'api1',
    'api2',
    'api3',
]

Copy link
Contributor Author

@lqhuang lqhuang Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the docs would be the same 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And also no .. versionadded:: 0.14.1 flag

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to change the docstrings. #186 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sphinx can handle aliases. Readers can redirect to the docs of the original APIs.

https://optree.readthedocs.io/en/latest/typing.html#optree.PyTreeDef

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to change the docstrings.

That only come into existence while you wish to never deprecate old APIs.

Of course, I understand every developer is trying to keep their own crafted codes 👍.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sphinx can handle aliases. Readers can redirect to the docs of the original APIs.

https://optree.readthedocs.io/en/latest/typing.html#optree.PyTreeDef
image

It's probably only useful when you mark tree_map (real implement) is an alias for pytree.map (with full docs).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That only come into existence while you wish to never deprecate old APIs.

It is not only about backward compatibility but also makes the codebase easier to inspect. For example, you can easily find the implementation of tree_map by searching /tree_map/, while you will get many irrelevant occurrences when searching map.

@lqhuang
Copy link
Contributor Author

lqhuang commented Feb 13, 2025

you need to use builtins.tuple[...].

I tried this, it's a syntax error. 🥹

@XuehaiPan
Copy link
Member

you need to use builtins.tuple[...].

I tried this, it's a syntax error. 🥹

You may need to add from __future__ import annotations. But I think we do not need the annotations anyway.

@lqhuang
Copy link
Contributor Author

lqhuang commented Feb 13, 2025

you need to use builtins.tuple[...].

I tried this, it's a syntax error. 🥹

You may need to add from __future__ import annotations. But I think we do not need the annotations anyway.

I fixed it already by other ways. It's not the __future__ problem, from __future__ import annotations is added.

@lqhuang
Copy link
Contributor Author

lqhuang commented Feb 13, 2025

Definitely you're the BOSS and owner 😂 I respect to your flavor style.

I have opened a new PR with the simplest way. Please choose them as your option and review. Once accept one then close the another one.

Thanks!

@XuehaiPan
Copy link
Member

Closing in favor of #189.

@XuehaiPan XuehaiPan closed this Feb 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request python Something related to the Python source code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants