Compare commits

...
5 Commits
9 changed files with 89 additions and 15 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run-tests: run-tests:
strategy: strategy:
matrix: matrix:
python-version: [ 3.7, 3.8, 3.9 ] python-version: [ 3.8, 3.9 ]
os: [ ubuntu-latest ] os: [ ubuntu-latest ]
fail-fast: false fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
+2 -2
View File
@@ -23,13 +23,13 @@ repos:
hooks: hooks:
- id: black - id: black
- repo: https://github.com/nbQA-dev/nbQA - repo: https://github.com/nbQA-dev/nbQA
rev: 1.2.3 rev: 1.6.3
hooks: hooks:
- id: nbqa-black - id: nbqa-black
- id: nbqa-isort - id: nbqa-isort
- id: nbqa-flake8 - id: nbqa-flake8
- repo: https://github.com/PyCQA/isort - repo: https://github.com/PyCQA/isort
rev: 5.10.1 rev: 5.12.0
hooks: hooks:
- id: isort - id: isort
- repo: https://github.com/pycqa/flake8 - repo: https://github.com/pycqa/flake8
+2 -1
View File
@@ -2,9 +2,10 @@
Type annotations **and runtime checking** for: Type annotations **and runtime checking** for:
1. shape and dtype of [JAX](https://github.com/google/jax) arrays; 1. shape and dtype of [JAX](https://github.com/google/jax) arrays; *(Now also supports PyTorch, NumPy, and TensorFlow!)*
2. [PyTrees](https://jax.readthedocs.io/en/latest/pytrees.html). 2. [PyTrees](https://jax.readthedocs.io/en/latest/pytrees.html).
**For example:** **For example:**
```python ```python
from jaxtyping import Array, Float, PyTree from jaxtyping import Array, Float, PyTree
+19 -6
View File
@@ -79,14 +79,27 @@ from .import_hook import install_import_hook as install_import_hook
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
_T = typing.TypeVar("_T") # Set up to deliberately confuse a static type checker.
PyTree = getattr(typing, "foo" + "bar")
class PyTree(typing_extensions.Protocol[_T]): # What's going on with this madness?
pass #
# At static-type-checking-time, we want `PyTree` to be a type for which both
# `PyTree` and `PyTree[Foo]` are equivalent to `Any`.
# (The intention is that `PyTree` be a runtime-only type; there's no real way to
# do more with static type checkers.)
#
# Unfortunately, this isn't possible: `Any` isn't subscriptable. And there's no
# equivalent way we can fake this using typing annotations. (In some sense the
# closest thing would be a `Protocol[T]` with no methods, but that's actually the
# opposite of what we want: that ends up allowing nothing at all.)
#
# The good news for us is that static type checkers have an internal escape hatch.
# If they can't figure out what a type is, then they just give up and allow
# anything. (I believe this is sometimes called `Unknown`.) Thus, this odd-looking
# annotation, which static type checkers aren't smart enough to resolve.
elif has_jax: elif has_jax:
from .pytree_type import PyTree from .pytree_type import PyTree
del has_jax del has_jax
__version__ = "0.2.10" __version__ = "0.2.12"
+5 -3
View File
@@ -66,9 +66,11 @@ def _call_with_frames_removed(f, *args, **kwargs):
def _optimized_cache_from_source(path, debug_override=None): def _optimized_cache_from_source(path, debug_override=None):
# Version 2: change the position of the `@jaxtyped` decorator, so need a # Version 2: change the position of the `@jaxtyped` decorator, so need a
# different name to avoid hitting old __pycache__ # different name to avoid hitting old __pycache__.
# Version 3: now also annotating classes. # Version 3: now also annotating classes.
return cache_from_source(path, debug_override, optimization="jaxtyping3") # Version 4: I'm honestly not sure, but bumping this fixed some kind of odd error.
# Maybe I changed something with hte classes part way through version 3?
return cache_from_source(path, debug_override, optimization="jaxtyping4")
def _dot_lookup(*elements): def _dot_lookup(*elements):
@@ -111,7 +113,7 @@ class _JaxtypingTransformer(ast.NodeVisitor):
args = [ast.Constant(None)] args = [ast.Constant(None)]
else: else:
args = [_dot_lookup(*self._typechecker)] args = [_dot_lookup(*self._typechecker)]
node.decorator_list.append(ast.Call(func, args, keywords=[])) node.decorator_list.insert(0, ast.Call(func, args, keywords=[]))
self._parents.append(node) self._parents.append(node)
self.generic_visit(node) self.generic_visit(node)
self._parents.pop() self._parents.pop()
+15
View File
@@ -17,6 +17,8 @@
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import dataclasses
import equinox as eqx import equinox as eqx
import jax.numpy as jnp import jax.numpy as jnp
import pytest import pytest
@@ -45,3 +47,16 @@ with pytest.raises(ParamError):
M(1.0, jnp.array([1.0])) M(1.0, jnp.array([1.0]))
with pytest.raises(ParamError): with pytest.raises(ParamError):
M(1, jnp.array(1.0)) M(1, jnp.array(1.0))
@dataclasses.dataclass
class D:
foo: int
bar: Float32[jnp.ndarray, " a"]
D(1, jnp.array([1.0]))
with pytest.raises(ParamError):
D(1.0, jnp.array([1.0]))
with pytest.raises(ParamError):
D(1, jnp.array(1.0))
+15
View File
@@ -17,6 +17,8 @@
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import dataclasses
import equinox as eqx import equinox as eqx
import jax.numpy as jnp import jax.numpy as jnp
import pytest import pytest
@@ -45,3 +47,16 @@ with pytest.raises(ParamError):
M(1.0, jnp.array([1.0])) M(1.0, jnp.array([1.0]))
with pytest.raises(ParamError): with pytest.raises(ParamError):
M(1, jnp.array(1.0)) M(1, jnp.array(1.0))
@dataclasses.dataclass
class D:
foo: int
bar: Float32[jnp.ndarray, " a"]
D(1, jnp.array([1.0]))
with pytest.raises(ParamError):
D(1.0, jnp.array([1.0]))
with pytest.raises(ParamError):
D(1, jnp.array(1.0))
+15
View File
@@ -17,6 +17,8 @@
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import dataclasses
import equinox as eqx import equinox as eqx
import jax.numpy as jnp import jax.numpy as jnp
import pytest import pytest
@@ -45,3 +47,16 @@ with pytest.raises(ParamError):
M(1.0, jnp.array([1.0])) M(1.0, jnp.array([1.0]))
with pytest.raises(ParamError): with pytest.raises(ParamError):
M(1, jnp.array(1.0)) M(1, jnp.array(1.0))
@dataclasses.dataclass
class D:
foo: int
bar: Float32[jnp.ndarray, " a"]
D(1, jnp.array([1.0]))
with pytest.raises(ParamError):
D(1.0, jnp.array([1.0]))
with pytest.raises(ParamError):
D(1, jnp.array(1.0))
+15 -2
View File
@@ -1,13 +1,26 @@
import abc
from jaxtyping import jaxtyped from jaxtyping import jaxtyped
class M: class M(metaclass=abc.ABCMeta):
@jaxtyped @jaxtyped
@classmethod @classmethod
def f(cls): def f(cls):
return 3 return 3
@jaxtyped
@abc.abstractmethod
def g(self):
...
# Check that the @jaxtyped decorator doesn't blat the __get__ of @classmethod # Check that the @jaxtyped decorator doesn't blat the __get__ of @classmethod
def test_decorator(): def test_classmethod():
assert M.f() == 3 assert M.f() == 3
# Check that the @jaxtyped decorator doesn't blat the __isabstractmethod__ of
# @abstractmethod
def test_abstractmethod():
assert M.g.__isabstractmethod__