Compare commits

...
19 Commits
Author SHA1 Message Date
Patrick Kidger e347c480d5 Hash is now stable across python runtimes 2023-08-17 16:23:33 +01:00
Patrick Kidger 13e6870fb8 Compatibility with JAX changes to opaque dtypes 2023-07-26 09:33:32 -07:00
Patrick Kidger 4c90808401 jaxtyping no longer appears in tracebacks 2023-07-26 09:33:32 -07:00
Patrick Kidger 5a57456e15 document Levanter 2023-07-12 19:36:09 +01:00
Patrick Kidger a6ab6c0d28 Have PRNGKeyArray appear correctly in downstream documentation 2023-06-29 18:39:22 -07:00
Patrick Kidger 83be9e9d16 Fixed jaxtyping doc generation 2023-06-25 12:00:16 -07:00
Patrick Kidger d2aa9c1e8d Merge branch 'main' of https://github.com/google/jaxtyping 2023-06-14 10:42:32 -07:00
Patrick Kidger 926dc53856 Have PyTreeDef appear correctly in docs 2023-06-14 10:42:14 -07:00
Patrick Kidger edc34f14f8 Update ecosystem links. 2023-06-07 15:35:45 +01:00
Patrick Kidger 8fa15050bc Update ecosystem links. 2023-06-07 15:35:14 +01:00
Patrick Kidger 356f5b7f7b Build fixes 2023-06-01 11:06:02 -07:00
Patrick Kidger 1b9c9fab52 Bump to Py3.9 2023-06-01 10:56:00 -07:00
Patrick Kidger 066a5b058f Made modules private. 2023-06-01 10:56:00 -07:00
Patrick Kidger 319d54abcf Avoid __builtins__ getting added as a key 2023-06-01 10:56:00 -07:00
Patrick Kidger 6a64ef114e Now provides PyTreeDef, and can detect PyTrees via issubclass(x, PyTree) 2023-06-01 10:56:00 -07:00
Patrick Kidger 10e1852b37 Fix favicon 2023-05-12 11:57:06 -07:00
Patrick Kidger a19149d23d Fixed pytest hook 2023-05-11 09:08:09 -07:00
Patrick Kidger 0c596ff373 Fix for non-JAX installations. 2023-05-10 17:16:42 -07:00
Patrick Kidger 7934d2afed Static typing fixes 2023-05-10 16:35:45 -07:00
17 changed files with 272 additions and 144 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
with: with:
python-version: "3.11" python-version: "3.11"
test-script: | test-script: |
python -m pip install pytest beartype equinox jaxlib cloudpickle python -m pip install -r ${{ github.workspace }}/test/requirements.txt
python -m pip install torch --extra-index-url https://download.pytorch.org/whl/cpu python -m pip install torch --extra-index-url https://download.pytorch.org/whl/cpu
cp -r ${{ github.workspace }}/test ./test cp -r ${{ github.workspace }}/test ./test
pytest pytest
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
python -m pip install pytest wheel beartype equinox jaxlib cloudpickle python -m pip install -r test/requirements.txt
python -m pip install torch --extra-index-url https://download.pytorch.org/whl/cpu python -m pip install torch --extra-index-url https://download.pytorch.org/whl/cpu
- name: Checks with pre-commit - name: Checks with pre-commit
+12 -6
View File
@@ -29,7 +29,7 @@ def accepts_pytree_of_arrays(x: PyTree[Float[Array, "batch c1 c2"]]):
pip install jaxtyping pip install jaxtyping
``` ```
Requires Python 3.8+. Requires Python 3.9+.
JAX is an optional dependency, required for a few JAX-specific types. If JAX is not installed then these will not be available, but you may still use jaxtyping to provide shape/dtype annotations for PyTorch/NumPy/TensorFlow/etc. JAX is an optional dependency, required for a few JAX-specific types. If JAX is not installed then these will not be available, but you may still use jaxtyping to provide shape/dtype annotations for PyTorch/NumPy/TensorFlow/etc.
@@ -41,15 +41,21 @@ Available at [https://docs.kidger.site/jaxtyping](https://docs.kidger.site/jaxty
## Finally ## Finally
### See also: other tools in the JAX ecosystem ### See also: other libraries in the JAX ecosystem
Neural networks: [Equinox](https://github.com/patrick-kidger/equinox). [Equinox](https://github.com/patrick-kidger/equinox): neural networks.
Numerical differential equation solvers: [Diffrax](https://github.com/patrick-kidger/diffrax). [Optax](https://github.com/deepmind/optax): first-order gradient (SGD, Adam, ...) optimisers.
Computer vision models: [Eqxvision](https://github.com/paganpasta/eqxvision). [Diffrax](https://github.com/patrick-kidger/diffrax): numerical differential equation solvers.
SymPy<->JAX conversion; train symbolic expressions via gradient descent: [sympy2jax](https://github.com/google/sympy2jax). [Lineax](https://github.com/google/lineax): linear solvers and linear least squares.
[Eqxvision](https://github.com/paganpasta/eqxvision): computer vision models.
[sympy2jax](https://github.com/google/sympy2jax): SymPy<->JAX conversion; train symbolic expressions via gradient descent.
[Levanter](https://github.com/stanford-crfm/levanter): scalable+reliable training of foundation models (e.g. LLMs).
### Disclaimer ### Disclaimer
Vendored Executable → Regular
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 807 B

After

Width:  |  Height:  |  Size: 541 B

+10 -5
View File
@@ -1,13 +1,18 @@
# Advanced features # Advanced features
## Abstract base classes ## Creating your own dtypes
::: jaxtyping.AbstractDtype ::: jaxtyping.AbstractDtype
selection: selection:
members: members:
false false
::: jaxtyping.AbstractArray ## Introspection
selection:
members: If you're writing your own type hint parser, then you may wish to detect if some Python object is a jaxtyping-provided type.
false
You can check for dtypes by doing `issubclass(x, AbstractDtype)`. For example, `issubclass(Float32, AbstractDtype)` will pass.
You can check for arrays by doing `issubclass(x, AbstractArray)`. Here, `AbstractArray` is the base class for all shape-and-dtype specified arrays, e.g. it's a base class for `Float32[Array, "foo"]`.
You can check for pytrees by doing `issubclass(x, PyTree)`. For example, `issubclass(PyTree[int], PyTree)` will pass.
+7 -1
View File
@@ -5,4 +5,10 @@
members: members:
false false
Note that `jaxtyping.PyTree` is only available if JAX has been installed. ---
:::jaxtyping.PyTreeDef
---
Note that `jaxtyping.{PyTree, PyTreeDef}` are only available if JAX has been installed.
+17 -1
View File
@@ -13,7 +13,7 @@ jaxtyping is a library providing type annotations **and runtime type-checking**
pip install jaxtyping pip install jaxtyping
``` ```
Requires Python 3.8+. Requires Python 3.9+.
JAX is an optional dependency, required for a few JAX-specific types. If JAX is not installed then these will not be available, but you may still use jaxtyping to provide shape/dtype annotations for PyTorch/NumPy/TensorFlow/etc. JAX is an optional dependency, required for a few JAX-specific types. If JAX is not installed then these will not be available, but you may still use jaxtyping to provide shape/dtype annotations for PyTorch/NumPy/TensorFlow/etc.
@@ -40,3 +40,19 @@ def accepts_pytree_of_arrays(x: PyTree[Float[Array, "batch c1 c2"]]):
## Next steps ## Next steps
Have a read of the [Array annotations](./api/array.md) documentation on the left-hand bar! Have a read of the [Array annotations](./api/array.md) documentation on the left-hand bar!
## See also: other libraries in the JAX ecosystem
[Equinox](https://github.com/patrick-kidger/equinox): neural networks.
[Optax](https://github.com/deepmind/optax): first-order gradient (SGD, Adam, ...) optimisers.
[Diffrax](https://github.com/patrick-kidger/diffrax): numerical differential equation solvers.
[Lineax](https://github.com/google/lineax): linear solvers and linear least squares.
[Eqxvision](https://github.com/paganpasta/eqxvision): computer vision models.
[sympy2jax](https://github.com/google/sympy2jax): SymPy<->JAX conversion; train symbolic expressions via gradient descent.
[Levanter](https://github.com/stanford-crfm/levanter): scalable+reliable training of foundation models (e.g. LLMs).
+101 -63
View File
@@ -30,14 +30,14 @@ else:
del jax del jax
# First import some things as normal # First import some things as normal
from .array_types import ( from ._array_types import (
AbstractArray as AbstractArray, AbstractArray as AbstractArray,
AbstractDtype as AbstractDtype, AbstractDtype as AbstractDtype,
get_array_name_format as get_array_name_format, get_array_name_format as get_array_name_format,
set_array_name_format as set_array_name_format, set_array_name_format as set_array_name_format,
) )
from .decorator import jaxtyped as jaxtyped from ._decorator import jaxtyped as jaxtyped
from .import_hook import install_import_hook as install_import_hook from ._import_hook import install_import_hook as install_import_hook
# Now import Array and ArrayLike # Now import Array and ArrayLike
@@ -69,70 +69,72 @@ elif has_jax:
# Import our dtypes # Import our dtypes
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
# Note that `from typing_extensions import Annotated; Bool = Annotated` # Introduce an indirection so that we can `import X as X` to make it clear that
# does not work with static type checkers. `Annotated` is a typeform rather # these are public.
# than a type, meaning it cannot be assigned. from ._indirection import (
from typing_extensions import ( BFloat16 as BFloat16,
Annotated as BFloat16, Bool as Bool,
Annotated as Bool, Complex as Complex,
Annotated as Complex, Complex64 as Complex64,
Annotated as Complex64, Complex128 as Complex128,
Annotated as Complex128, Float as Float,
Annotated as Float, Float16 as Float16,
Annotated as Float16, Float32 as Float32,
Annotated as Float32, Float64 as Float64,
Annotated as Float64, Inexact as Inexact,
Annotated as Inexact, Int as Int,
Annotated as Int, Int8 as Int8,
Annotated as Int8, Int16 as Int16,
Annotated as Int16, Int32 as Int32,
Annotated as Int32, Int64 as Int64,
Annotated as Int64, Integer as Integer,
Annotated as Integer, Key as Key,
Annotated as Key, Num as Num,
Annotated as Num, Shaped as Shaped,
Annotated as Shaped, UInt as UInt,
Annotated as UInt, UInt8 as UInt8,
Annotated as UInt8, UInt16 as UInt16,
Annotated as UInt16, UInt32 as UInt32,
Annotated as UInt32, UInt64 as UInt64,
Annotated as UInt64,
) )
else: else:
# noqas to work around ruff bug from ._array_types import (
from .array_types import ( BFloat16 as BFloat16,
BFloat16 as BFloat16, # noqa: F401 Bool as Bool,
Bool as Bool, # noqa: F401 Complex as Complex,
Complex as Complex, # noqa: F401 Complex64 as Complex64,
Complex64 as Complex64, # noqa: F401 Complex128 as Complex128,
Complex128 as Complex128, # noqa: F401 Float as Float,
Float as Float, # noqa: F401 Float16 as Float16,
Float16 as Float16, # noqa: F401 Float32 as Float32,
Float32 as Float32, # noqa: F401 Float64 as Float64,
Float64 as Float64, # noqa: F401 Inexact as Inexact,
Inexact as Inexact, # noqa: F401 Int as Int,
Int as Int, # noqa: F401 Int8 as Int8,
Int8 as Int8, # noqa: F401 Int16 as Int16,
Int16 as Int16, # noqa: F401 Int32 as Int32,
Int32 as Int32, # noqa: F401 Int64 as Int64,
Int64 as Int64, # noqa: F401 Integer as Integer,
Integer as Integer, # noqa: F401 Num as Num,
Key as Key, # noqa: F401 Shaped as Shaped,
Num as Num, # noqa: F401 UInt as UInt,
Shaped as Shaped, # noqa: F401 UInt8 as UInt8,
UInt as UInt, # noqa: F401 UInt16 as UInt16,
UInt8 as UInt8, # noqa: F401 UInt32 as UInt32,
UInt16 as UInt16, # noqa: F401 UInt64 as UInt64,
UInt32 as UInt32, # noqa: F401
UInt64 as UInt64, # noqa: F401
) )
if has_jax:
from ._array_types import Key as Key
# Now import PyTree
# Now import PyTreeDef and PyTree
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
# Set up to deliberately confuse a static type checker.
import typing_extensions import typing_extensions
from jax.tree_util import PyTreeDef as PyTreeDef
# Set up to deliberately confuse a static type checker.
PyTree: typing_extensions.TypeAlias = getattr(typing, "foo" + "bar") PyTree: typing_extensions.TypeAlias = getattr(typing, "foo" + "bar")
# What's going on with this madness? # What's going on with this madness?
# #
@@ -151,16 +153,52 @@ if typing.TYPE_CHECKING:
# anything. (I believe this is sometimes called `Unknown`.) Thus, this odd-looking # anything. (I believe this is sometimes called `Unknown`.) Thus, this odd-looking
# annotation, which static type checkers aren't smart enough to resolve. # annotation, which static type checkers aren't smart enough to resolve.
elif has_jax: elif has_jax:
from .pytree_type import PyTree as PyTree # noqa: F401 if hasattr(typing, "GENERATING_DOCUMENTATION"):
# Most parts of the Equinox ecosystem have
# `typing.GENERATING_DOCUMENTATION = True` when generating documentation, to
# add whatever shims are necessary to get pretty docs. E.g. to have type
# annotations appear as just `PyTree`, not `jaxtyping.PyTree`.
#
# As jaxtyping actually wants things to appear as e.g. `jaxtyping.PyTree`,
# rather than just `PyTree`, then it sets
# `typing.GENERATING_DOCUMENTATION = False`, to disable these shims.
#
# Here we do only a `hasattr` check, as we want to get this version of
# `PyTreeDef` in both the jaxtyping and the Equinox(/etc.) docs.
class PyTreeDef:
"""Alias for `jax.tree_util.PyTreeDef`, which is the type of the return
from `jax.tree_util.tree_structure(...)`.
"""
if typing.GENERATING_DOCUMENTATION:
# Equinox etc. docs get just `PyTreeDef`.
# jaxtyping docs get `jaxtyping.PyTreeDef`.
PyTreeDef.__module__ = "builtins"
else:
from jax.tree_util import PyTreeDef as PyTreeDef
from ._pytree_type import PyTree as PyTree # noqa: F401
# Conveniences # Conveniences
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from jax import Array as Scalar
from jax.random import PRNGKeyArray as PRNGKeyArray from jax.random import PRNGKeyArray as PRNGKeyArray
from jax.typing import ArrayLike as ScalarLike
from ._indirection import Scalar as Scalar, ScalarLike as ScalarLike
elif has_jax: elif has_jax:
from .array_types import PRNGKeyArray, Scalar, ScalarLike # noqa: F401 from ._array_types import Scalar, ScalarLike # noqa: F401
if getattr(typing, "GENERATING_DOCUMENTATION", False):
# That is, we're generating some downstream documentation, not the jaxtyping
# documentation itself.
class PRNGKeyArray:
pass
PRNGKeyArray.__module__ = "builtins"
else:
from ._array_types import PRNGKeyArray
del has_jax del has_jax
@@ -23,20 +23,11 @@ import re
import sys import sys
import types import types
import typing import typing
from typing import ( from typing import Any, Literal, NoReturn, Optional, Union
Any,
Dict,
List,
Literal,
NoReturn,
Optional,
Tuple,
Union,
)
import numpy as np import numpy as np
from .decorator import storage from ._decorator import storage
try: try:
@@ -108,9 +99,9 @@ _AbstractDim = Union[Literal[_anonymous_dim], _NamedDim, _FixedDim, _SymbolicDim
def _check_dims( def _check_dims(
cls_dims: List[_AbstractDim], cls_dims: list[_AbstractDim],
obj_shape: Tuple[int], obj_shape: tuple[int],
single_memo: Dict[str, int], single_memo: dict[str, int],
) -> bool: ) -> bool:
assert len(cls_dims) == len(obj_shape) assert len(cls_dims) == len(obj_shape)
for cls_dim, obj_size in zip(cls_dims, obj_shape): for cls_dim, obj_size in zip(cls_dims, obj_shape):
@@ -123,7 +114,8 @@ def _check_dims(
return False return False
elif type(cls_dim) is _SymbolicDim: elif type(cls_dim) is _SymbolicDim:
try: try:
eval_size = eval(cls_dim.expr, single_memo) # Make a copy to avoid `__builtins__` getting added as a key.
eval_size = eval(cls_dim.expr, single_memo.copy())
except NameError as e: except NameError as e:
raise NameError( raise NameError(
f"Cannot process symbolic dimension '{cls_dim.expr}' as some " f"Cannot process symbolic dimension '{cls_dim.expr}' as some "
@@ -145,12 +137,21 @@ def _check_dims(
return True return True
def _is_jax_extended_dtype(dtype: Any) -> bool:
if not has_jax:
return False
if hasattr(jax.dtypes, "extended"): # jax>=0.4.14
return jax.numpy.issubdtype(dtype, jax.dtypes.extended)
else: # jax<=0.4.13
return jax.core.is_opaque_dtype(dtype)
class _MetaAbstractArray(type): class _MetaAbstractArray(type):
def __instancecheck__(cls, obj): def __instancecheck__(cls, obj):
if not isinstance(obj, cls.array_type): if not isinstance(obj, cls.array_type):
return False return False
if has_jax and jax.core.is_opaque_dtype(obj.dtype): if _is_jax_extended_dtype(obj.dtype):
dtype = str(obj.dtype) dtype = str(obj.dtype)
elif hasattr(obj.dtype, "type") and hasattr(obj.dtype.type, "__name__"): elif hasattr(obj.dtype, "type") and hasattr(obj.dtype.type, "__name__"):
# JAX, numpy # JAX, numpy
@@ -213,9 +214,9 @@ class _MetaAbstractArray(type):
def _check_shape( def _check_shape(
cls, cls,
obj, obj,
single_memo: Dict[str, int], single_memo: dict[str, int],
variadic_memo: Dict[str, Tuple[int, ...]], variadic_memo: dict[str, tuple[int, ...]],
variadic_broadcast_memo: Dict[str, List[Tuple[int, ...]]], variadic_broadcast_memo: dict[str, list[tuple[int, ...]]],
): ):
if cls.index_variadic is None: if cls.index_variadic is None:
if obj.ndim != len(cls.dims): if obj.ndim != len(cls.dims):
@@ -289,8 +290,8 @@ class AbstractArray(metaclass=_MetaAbstractArray):
""" """
array_type: Any array_type: Any
dtypes: List[str] dtypes: list[str]
dims: Tuple[_AbstractDimOrVariadicDim, ...] dims: tuple[_AbstractDimOrVariadicDim, ...]
index_variadic: Optional[int] index_variadic: Optional[int]
dim_str: str dim_str: str
@@ -517,7 +518,7 @@ class _MetaAbstractDtype(type):
f'`jaxtyping.{cls.__name__}[jnp.ndarray, "..."]`.' f'`jaxtyping.{cls.__name__}[jnp.ndarray, "..."]`.'
) )
def __getitem__(cls, item: Tuple[Any, str]): def __getitem__(cls, item: tuple[Any, str]):
if not isinstance(item, tuple) or len(item) != 2: if not isinstance(item, tuple) or len(item) != 2:
raise ValueError( raise ValueError(
"As of jaxtyping v0.2.0, type annotations must now include an explicit " "As of jaxtyping v0.2.0, type annotations must now include an explicit "
@@ -570,7 +571,7 @@ class AbstractDtype(metaclass=_MetaAbstractDtype):
``` ```
""" """
dtypes: Union[Literal[_any_dtype], List[Union[str, re.Pattern]]] dtypes: Union[Literal[_any_dtype], list[Union[str, re.Pattern]]]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
raise RuntimeError( raise RuntimeError(
@@ -581,7 +582,7 @@ class AbstractDtype(metaclass=_MetaAbstractDtype):
def __init_subclass__(cls, **kwargs): def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs) super().__init_subclass__(**kwargs)
dtypes: Union[Literal[_any_dtype], str, List[str]] = cls.dtypes dtypes: Union[Literal[_any_dtype], str, list[str]] = cls.dtypes
if isinstance(dtypes, (str, re.Pattern)): if isinstance(dtypes, (str, re.Pattern)):
dtypes = (dtypes,) dtypes = (dtypes,)
elif dtypes is not _any_dtype: elif dtypes is not _any_dtype:
@@ -25,6 +25,14 @@ import types
import weakref import weakref
try:
import jax._src.traceback_util as traceback_util
except ImportError:
pass
else:
traceback_util.register_exclusion(__file__)
storage = threading.local() storage = threading.local()
@@ -51,12 +51,14 @@
import ast import ast
import functools as ft import functools as ft
import hashlib
import sys import sys
from collections.abc import Sequence
from importlib.abc import MetaPathFinder from importlib.abc import MetaPathFinder
from importlib.machinery import SourceFileLoader from importlib.machinery import SourceFileLoader
from importlib.util import cache_from_source, decode_source from importlib.util import cache_from_source, decode_source
from inspect import isclass from inspect import isclass
from typing import List, Optional, Sequence, Union from typing import Optional, Union
from unittest.mock import patch from unittest.mock import patch
@@ -94,7 +96,7 @@ def _str_lookup(string):
class _JaxtypingTransformer(ast.NodeVisitor): class _JaxtypingTransformer(ast.NodeVisitor):
def __init__(self, *, typechecker) -> None: def __init__(self, *, typechecker) -> None:
self._parents: List[ast.AST] = [] self._parents: list[ast.AST] = []
self._typechecker = typechecker self._typechecker = typechecker
def visit_Module(self, node: ast.Module): def visit_Module(self, node: ast.Module):
@@ -120,7 +122,7 @@ class _JaxtypingTransformer(ast.NodeVisitor):
return node return node
def visit_ClassDef(self, node: ast.ClassDef): def visit_ClassDef(self, node: ast.ClassDef):
func = _dot_lookup("jaxtyping", "decorator", "_jaxtyped_typechecker") func = _dot_lookup("jaxtyping", "_decorator", "_jaxtyped_typechecker")
if self._typechecker is None: if self._typechecker is None:
args = [ast.Constant(None)] args = [ast.Constant(None)]
else: else:
@@ -164,7 +166,9 @@ class _JaxtypingLoader(SourceFileLoader):
def __init__(self, *args, typechecker, **kwargs): def __init__(self, *args, typechecker, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._typechecker = typechecker self._typechecker = typechecker
self._typechecker_hash = str(abs(hash(self._typechecker))) self._typechecker_hash = hashlib.md5(
self._typechecker.encode("utf-8")
).hexdigest()
def source_to_code(self, data, path, *, _optimize=-1): def source_to_code(self, data, path, *, _optimize=-1):
source = decode_source(data) source = decode_source(data)
+32
View File
@@ -0,0 +1,32 @@
# Note that `from typing import Annotated; Bool = Annotated`
# does not work with static type checkers. `Annotated` is a typeform rather
# than a type, meaning it cannot be assigned.
from typing import (
Annotated as BFloat16, # noqa: F401
Annotated as Bool, # noqa: F401
Annotated as Complex, # noqa: F401
Annotated as Complex64, # noqa: F401
Annotated as Complex128, # noqa: F401
Annotated as Float, # noqa: F401
Annotated as Float16, # noqa: F401
Annotated as Float32, # noqa: F401
Annotated as Float64, # noqa: F401
Annotated as Inexact, # noqa: F401
Annotated as Int, # noqa: F401
Annotated as Int8, # noqa: F401
Annotated as Int16, # noqa: F401
Annotated as Int32, # noqa: F401
Annotated as Int64, # noqa: F401
Annotated as Integer, # noqa: F401
Annotated as Key, # noqa: F401
Annotated as Num, # noqa: F401
Annotated as Shaped, # noqa: F401
Annotated as UInt, # noqa: F401
Annotated as UInt8, # noqa: F401
Annotated as UInt16, # noqa: F401
Annotated as UInt32, # noqa: F401
Annotated as UInt64, # noqa: F401
)
from jax import Array as Scalar # noqa: F401
from jax.typing import ArrayLike as ScalarLike # noqa: F401
@@ -19,7 +19,7 @@
import sys import sys
from .import_hook import install_import_hook from ._import_hook import install_import_hook
def pytest_addoption(parser): def pytest_addoption(parser):
@@ -35,10 +35,6 @@ class _FakePyTree(Generic[_T]):
_FakePyTree.__name__ = "PyTree" _FakePyTree.__name__ = "PyTree"
_FakePyTree.__qualname__ = "PyTree" _FakePyTree.__qualname__ = "PyTree"
_FakePyTree.__module__ = "builtins" _FakePyTree.__module__ = "builtins"
# Can't do type("PyTree", (Generic[_T],), {}) because dynamic subclassing of typeforms
# isn't allowed.
# Can't do types.new_class("PyTree", (Generic[_T],), {}) because that has __module__
# "types", e.g. we get types.PyTree[int].
class _MetaPyTree(type): class _MetaPyTree(type):
@@ -46,32 +42,9 @@ class _MetaPyTree(type):
raise RuntimeError("PyTree cannot be instantiated") raise RuntimeError("PyTree cannot be instantiated")
def __instancecheck__(cls, obj): def __instancecheck__(cls, obj):
return True if not hasattr(cls, "leaftype"):
return True # Just `isinstance(x, PyTree)`
@ft.lru_cache(maxsize=None)
def __getitem__(cls, item):
name = str(_FakePyTree[item])
out = _MetaSubscriptPyTree(name, (), {"leaftype": item})
if getattr(typing, "GENERATING_DOCUMENTATION", False):
out.__module__ = "builtins"
else:
out.__module__ = "jaxtyping"
return out
try:
# new typeguard
_TypeCheckError = (TypeError, typeguard.TypeCheckError)
except AttributeError:
# old typeguard
_TypeCheckError = TypeError
class _MetaSubscriptPyTree(type):
def __call__(self, *args, **kwargs):
raise RuntimeError("PyTree cannot be instantiated")
def __instancecheck__(cls, obj):
# We could use `isinstance` here but that would fail for more complicated # We could use `isinstance` here but that would fail for more complicated
# types, e.g. PyTree[Tuple[int]]. So at least internally we make a particular # types, e.g. PyTree[Tuple[int]]. So at least internally we make a particular
# choice of typechecker. # choice of typechecker.
@@ -93,6 +66,36 @@ class _MetaSubscriptPyTree(type):
leaves = jtu.tree_leaves(obj, is_leaf=is_leaftype) leaves = jtu.tree_leaves(obj, is_leaf=is_leaftype)
return all(map(is_leaftype, leaves)) return all(map(is_leaftype, leaves))
# Can't return a generic (e.g. _FakePyTree[item]) because generic aliases don't do
# the custom __instancecheck__ that we want.
# We can't add that __instancecheck__ via subclassing, e.g.
# type("PyTree", (Generic[_T],), {}), because dynamic subclassing of typeforms
# isn't allowed.
# Likewise we can't do types.new_class("PyTree", (Generic[_T],), {}) because that
# has __module__ "types", e.g. we get types.PyTree[int].
@ft.lru_cache(maxsize=None)
def __getitem__(cls, item):
name = str(_FakePyTree[item])
class X(PyTree):
leaftype = item
X.__name__ = name
X.__qualname__ = name
if getattr(typing, "GENERATING_DOCUMENTATION", False):
X.__module__ = "builtins"
else:
X.__module__ = "jaxtyping"
return X
try:
# new typeguard
_TypeCheckError = (TypeError, typeguard.TypeCheckError)
except AttributeError:
# old typeguard
_TypeCheckError = TypeError
# Can't do `class PyTree(Generic[_T]): ...` because we need to override the # Can't do `class PyTree(Generic[_T]): ...` because we need to override the
# instancecheck for PyTree[foo], but subclassing # instancecheck for PyTree[foo], but subclassing
+3 -3
View File
@@ -1,9 +1,9 @@
[project] [project]
name = "jaxtyping" name = "jaxtyping"
version = "0.2.16" version = "0.2.21"
description = "Type annotations and runtime checking for shape and dtype of JAX arrays, and PyTrees." description = "Type annotations and runtime checking for shape and dtype of JAX arrays, and PyTrees."
readme = "README.md" readme = "README.md"
requires-python ="~=3.8" requires-python ="~=3.9"
license = {file = "LICENSE"} license = {file = "LICENSE"}
authors = [ authors = [
{name = "Patrick Kidger", email = "contact@kidger.site"}, {name = "Patrick Kidger", email = "contact@kidger.site"},
@@ -24,7 +24,7 @@ classifiers = [
] ]
urls = {repository = "https://github.com/google/jaxtyping" } urls = {repository = "https://github.com/google/jaxtyping" }
dependencies = ["numpy>=1.20.0", "typeguard>=2.13.3", "typing_extensions>=3.7.4.1"] dependencies = ["numpy>=1.20.0", "typeguard>=2.13.3", "typing_extensions>=3.7.4.1"]
entry_points = {pytest11 = ["jaxtyping = jaxtyping.pytest_plugin"]} entry-points = {pytest11 = {jaxtyping = "jaxtyping._pytest_plugin"}}
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]
+6 -5
View File
@@ -1,5 +1,6 @@
equinox>=0.5.3 beartype
pytest>=7.0.1 cloudpickle
beartype>=0.10.4 equinox
typeguard>=2.13.3 jaxlib
cloudpickle>=2.2.1 pytest
typeguard<3
+8
View File
@@ -185,3 +185,11 @@ def test_pytree_namedtuple(typecheck):
y=jax.random.normal(jax.random.PRNGKey(420), (2, 5)), y=jax.random.normal(jax.random.PRNGKey(420), (2, 5)),
) )
) )
def test_subclass_pytree():
x = PyTree
y = PyTree[int]
assert issubclass(x, PyTree)
assert issubclass(y, PyTree)
assert not issubclass(int, PyTree)