ovld 0.3.9__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ovld-0.4.1/.envrc +2 -0
- ovld-0.4.1/.github/workflows/python-package.yml +85 -0
- {ovld-0.3.9 → ovld-0.4.1}/.gitignore +1 -0
- ovld-0.4.1/.readthedocs.yaml +9 -0
- ovld-0.4.1/PKG-INFO +216 -0
- ovld-0.4.1/README.md +203 -0
- ovld-0.4.1/add.py +58 -0
- ovld-0.4.1/anal.py +13 -0
- ovld-0.4.1/bench/requirements.txt +2 -0
- {ovld-0.3.9 → ovld-0.4.1}/bench.py +53 -34
- ovld-0.4.1/benchd.py +41 -0
- ovld-0.4.1/benchmarks/common.py +55 -0
- ovld-0.4.1/benchmarks/test_add.py +96 -0
- ovld-0.4.1/benchmarks/test_ast.py +116 -0
- ovld-0.4.1/benchmarks/test_calc.py +144 -0
- ovld-0.4.1/benchmarks/test_fib.py +62 -0
- ovld-0.4.1/benchmarks/test_multer.py +159 -0
- ovld-0.4.1/benchmarks/test_trivial.py +131 -0
- ovld-0.4.1/benchmarks/test_tweaknum.py +80 -0
- ovld-0.4.1/cloz.py +24 -0
- ovld-0.4.1/data.py +100 -0
- ovld-0.4.1/didi.py +26 -0
- ovld-0.4.1/docs/compare.md +59 -0
- ovld-0.4.1/docs/dependent.md +107 -0
- ovld-0.4.1/docs/features.md +107 -0
- ovld-0.4.1/docs/index.md +52 -0
- ovld-0.4.1/docs/types.md +140 -0
- ovld-0.4.1/docs/usage.md +229 -0
- ovld-0.4.1/explore.py +100 -0
- ovld-0.4.1/facto.py +51 -0
- ovld-0.4.1/gen_comparison_table.py +66 -0
- ovld-0.4.1/gentest.py +134 -0
- ovld-0.4.1/hello.py +6 -0
- ovld-0.4.1/hntest.py +65 -0
- ovld-0.4.1/klos.py +24 -0
- ovld-0.4.1/mkdocs.yml +12 -0
- ovld-0.4.1/old-dist/ovld-0.1.0-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.0.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.1-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.1.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.2-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.2.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.3-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.3.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.4-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.4.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.5-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.5.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.6-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.6.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.7-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.7.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.8-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.8.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.9-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.1.9.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.0-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.0.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.1-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.1.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.10-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.10.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.11-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.11.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.2-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.2.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.3-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.3.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.4-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.4.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.5-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.5.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.6-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.6.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.7-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.7.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.8-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.8.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.9-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.2.9.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.0-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.0.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.1-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.1.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.2-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.2.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.3-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.3.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.4-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.4.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.5-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.5.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.8-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.8.tar.gz +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.9-py3-none-any.whl +0 -0
- ovld-0.4.1/old-dist/ovld-0.3.9.tar.gz +0 -0
- ovld-0.4.1/one_two_three.py +38 -0
- ovld-0.3.9/pyproject.toml → ovld-0.4.1/outoftheway.toml +2 -2
- ovld-0.4.1/pyproject.toml +52 -0
- ovld-0.4.1/src/ovld/__init__.py +74 -0
- ovld-0.4.1/src/ovld/core.py +816 -0
- ovld-0.4.1/src/ovld/dependent.py +275 -0
- ovld-0.4.1/src/ovld/mro.py +203 -0
- ovld-0.4.1/src/ovld/recode.py +582 -0
- ovld-0.4.1/src/ovld/typemap.py +383 -0
- ovld-0.4.1/src/ovld/types.py +219 -0
- ovld-0.4.1/src/ovld/utils.py +68 -0
- ovld-0.4.1/src/ovld/version.py +1 -0
- ovld-0.4.1/stuff.md +43 -0
- ovld-0.4.1/tb.py +17 -0
- ovld-0.4.1/tensor.py +72 -0
- ovld-0.4.1/tests/__init__.py +0 -0
- ovld-0.4.1/tests/test_dependent.py +366 -0
- ovld-0.4.1/tests/test_examples.py +155 -0
- {ovld-0.3.9 → ovld-0.4.1}/tests/test_global.py +8 -8
- ovld-0.4.1/tests/test_mro.py +51 -0
- ovld-0.4.1/tests/test_ovld/test_display.txt +78 -0
- ovld-0.4.1/tests/test_ovld/test_doc.txt +13 -0
- ovld-0.4.1/tests/test_ovld/test_doc2.txt +10 -0
- ovld-0.4.1/tests/test_ovld/test_method_doc.txt +10 -0
- {ovld-0.3.9 → ovld-0.4.1}/tests/test_ovld.py +526 -235
- {ovld-0.3.9 → ovld-0.4.1}/tests/test_typemap.py +33 -25
- ovld-0.4.1/tests/test_types.py +246 -0
- ovld-0.4.1/tests/test_utils.py +21 -0
- ovld-0.4.1/todo.q +29 -0
- ovld-0.4.1/toot.py +46 -0
- ovld-0.4.1/typo.py +25 -0
- ovld-0.4.1/uv.lock +675 -0
- ovld-0.4.1/world.yaml +16 -0
- ovld-0.4.1/x.py +51 -0
- ovld-0.4.1/zaggo +24 -0
- ovld-0.3.9/.envrc +0 -2
- ovld-0.3.9/.github/workflows/python-package.yml +0 -61
- ovld-0.3.9/PKG-INFO +0 -305
- ovld-0.3.9/README.md +0 -295
- ovld-0.3.9/explore.py +0 -69
- ovld-0.3.9/requirements-dev.lock +0 -25
- ovld-0.3.9/requirements.lock +0 -12
- ovld-0.3.9/src/ovld/__init__.py +0 -48
- ovld-0.3.9/src/ovld/core.py +0 -1001
- ovld-0.3.9/src/ovld/mro.py +0 -171
- ovld-0.3.9/src/ovld/recode.py +0 -80
- ovld-0.3.9/src/ovld/utils.py +0 -169
- ovld-0.3.9/src/ovld/version.py +0 -1
- ovld-0.3.9/tests/test_utils.py +0 -174
- ovld-0.3.9/x.py +0 -11
- {ovld-0.3.9 → ovld-0.4.1}/.bsync-snap-20220324145902.852076 +0 -0
- {ovld-0.3.9 → ovld-0.4.1}/.python-version +0 -0
- {ovld-0.3.9 → ovld-0.4.1}/LICENSE +0 -0
- {ovld-0.3.9/tests → ovld-0.4.1/benchmarks}/__init__.py +0 -0
- {ovld-0.3.9 → ovld-0.4.1}/doo.py +0 -0
- {ovld-0.3.9 → ovld-0.4.1}/edges.py +0 -0
- {ovld-0.3.9 → ovld-0.4.1}/nxt.py +0 -0
- {ovld-0.3.9 → ovld-0.4.1}/tests/modules/gingerbread.py +0 -0
ovld-0.4.1/.envrc
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
name: Python package
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ master ]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
lint:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
strategy:
|
13
|
+
matrix:
|
14
|
+
python-version: ['3.12']
|
15
|
+
steps:
|
16
|
+
- name: Check out the code
|
17
|
+
uses: actions/checkout@v3
|
18
|
+
- name: Set up uv
|
19
|
+
uses: hynek/setup-cached-uv@v2
|
20
|
+
with:
|
21
|
+
cache-dependency-path: uv.lock
|
22
|
+
- name: Pin Python version
|
23
|
+
run: uv python pin ${{ matrix.python-version }}
|
24
|
+
- name: Sync dependencies
|
25
|
+
run: uv sync
|
26
|
+
- name: Lint check
|
27
|
+
run: uvx ruff check
|
28
|
+
- name: Check formatting
|
29
|
+
run: uvx ruff format --check
|
30
|
+
|
31
|
+
test:
|
32
|
+
runs-on: ubuntu-latest
|
33
|
+
strategy:
|
34
|
+
matrix:
|
35
|
+
settings:
|
36
|
+
- python: '3.9'
|
37
|
+
coverage: false
|
38
|
+
- python: '3.10'
|
39
|
+
coverage: false
|
40
|
+
- python: '3.11'
|
41
|
+
coverage: false
|
42
|
+
- python: '3.12'
|
43
|
+
coverage: true
|
44
|
+
steps:
|
45
|
+
- name: Check out the code
|
46
|
+
uses: actions/checkout@v3
|
47
|
+
- name: Set up uv
|
48
|
+
uses: hynek/setup-cached-uv@v2
|
49
|
+
with:
|
50
|
+
cache-dependency-path: uv.lock
|
51
|
+
- name: Pin Python version
|
52
|
+
run: uv python pin ${{ matrix.settings.python }}
|
53
|
+
- name: Sync dependencies
|
54
|
+
run: uv sync
|
55
|
+
- name: Test with pytest
|
56
|
+
if: ${{ !matrix.settings.coverage }}
|
57
|
+
run: uv run pytest tests/
|
58
|
+
- name: Test with pytest and coverage
|
59
|
+
if: ${{ matrix.settings.coverage }}
|
60
|
+
run: uv run pytest --cov=src --cov-report term-missing tests/
|
61
|
+
- name: Verify coverage
|
62
|
+
if: ${{ matrix.settings.coverage }}
|
63
|
+
run: uv run coverage report | tail -1 | egrep "TOTAL +[0-9]+ +0 +100%"
|
64
|
+
|
65
|
+
benchmark:
|
66
|
+
runs-on: ubuntu-latest
|
67
|
+
strategy:
|
68
|
+
matrix:
|
69
|
+
settings:
|
70
|
+
- python: '3.10'
|
71
|
+
- python: '3.11'
|
72
|
+
- python: '3.12'
|
73
|
+
steps:
|
74
|
+
- name: Check out the code
|
75
|
+
uses: actions/checkout@v3
|
76
|
+
- name: Set up uv
|
77
|
+
uses: hynek/setup-cached-uv@v2
|
78
|
+
with:
|
79
|
+
cache-dependency-path: uv.lock
|
80
|
+
- name: Pin Python version
|
81
|
+
run: uv python pin ${{ matrix.settings.python }}
|
82
|
+
- name: Sync dependencies
|
83
|
+
run: uv sync
|
84
|
+
- name: Run benchmarks
|
85
|
+
run: uv run pytest benchmarks/
|
ovld-0.4.1/PKG-INFO
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: ovld
|
3
|
+
Version: 0.4.1
|
4
|
+
Summary: Overloading Python functions
|
5
|
+
Project-URL: Homepage, https://ovld.readthedocs.io/en/latest/
|
6
|
+
Project-URL: Documentation, https://ovld.readthedocs.io/en/latest/
|
7
|
+
Project-URL: Repository, https://github.com/breuleux/ovld
|
8
|
+
Author-email: Olivier Breuleux <breuleux@gmail.com>
|
9
|
+
License-Expression: MIT
|
10
|
+
License-File: LICENSE
|
11
|
+
Requires-Python: >=3.9
|
12
|
+
Description-Content-Type: text/markdown
|
13
|
+
|
14
|
+
|
15
|
+
# Ovld
|
16
|
+
|
17
|
+
Fast multiple dispatch in Python, with many extra features.
|
18
|
+
|
19
|
+
[📋 Documentation](https://ovld.readthedocs.io/en/latest/)
|
20
|
+
|
21
|
+
With ovld, you can write a version of the same function for every type signature using annotations instead of writing an awkward sequence of `isinstance` statements. Unlike Python's `singledispatch`, it works for multiple arguments.
|
22
|
+
|
23
|
+
* ⚡️ **[Fast](https://ovld.readthedocs.io/en/latest/compare/#results):** `ovld` is the fastest multiple dispatch library around, by some margin.
|
24
|
+
* 🚀 [**Variants**](https://ovld.readthedocs.io/en/latest/usage/#variants) and [**mixins**](https://ovld.readthedocs.io/en/latest/usage/#mixins) of functions and methods.
|
25
|
+
* 🦄 **[Dependent types](https://ovld.readthedocs.io/en/latest/dependent/):** Overloaded functions can depend on more than argument types: they can depend on actual values.
|
26
|
+
* 🔑 **Extensive:** Dispatch on functions, methods, positional arguments and even [keyword arguments](https://ovld.readthedocs.io/en/latest/usage/#keyword-arguments) (with some restrictions).
|
27
|
+
|
28
|
+
## Example
|
29
|
+
|
30
|
+
Here's a function that recursively adds lists, tuples and dictionaries:
|
31
|
+
|
32
|
+
```python
|
33
|
+
from ovld import ovld, recurse
|
34
|
+
|
35
|
+
@ovld
|
36
|
+
def add(x: list, y: list):
|
37
|
+
return [recurse(a, b) for a, b in zip(x, y)]
|
38
|
+
|
39
|
+
@ovld
|
40
|
+
def add(x: tuple, y: tuple):
|
41
|
+
return tuple(recurse(a, b) for a, b in zip(x, y))
|
42
|
+
|
43
|
+
@ovld
|
44
|
+
def add(x: dict, y: dict):
|
45
|
+
return {k: recurse(v, y[k]) for k, v in x.items()}
|
46
|
+
|
47
|
+
@ovld
|
48
|
+
def add(x: object, y: object):
|
49
|
+
return x + y
|
50
|
+
|
51
|
+
assert add([1, 2], [3, 4]) == [4, 6]
|
52
|
+
```
|
53
|
+
|
54
|
+
The `recurse` function is special: it will recursively call the current ovld object. You may ask: how is it different from simply calling `add`? The difference is that if you create a *variant* of `add`, `recurse` will automatically call the variant.
|
55
|
+
|
56
|
+
For example:
|
57
|
+
|
58
|
+
|
59
|
+
## Variants
|
60
|
+
|
61
|
+
A *variant* of an `ovld` is a copy of the `ovld`, with some methods added or changed. For example, let's take the definition of `add` above and make a variant that multiplies numbers instead:
|
62
|
+
|
63
|
+
```python
|
64
|
+
@add.variant
|
65
|
+
def mul(self, x: object, y: object):
|
66
|
+
return x * y
|
67
|
+
|
68
|
+
assert mul([1, 2], [3, 4]) == [3, 8]
|
69
|
+
```
|
70
|
+
|
71
|
+
Simple! This means you can define one `ovld` that recursively walks generic data structures, and then specialize it in various ways.
|
72
|
+
|
73
|
+
|
74
|
+
## Priority and call_next
|
75
|
+
|
76
|
+
You can define a numeric priority for each method (the default priority is 0):
|
77
|
+
|
78
|
+
```python
|
79
|
+
from ovld import call_next
|
80
|
+
|
81
|
+
@ovld(priority=1000)
|
82
|
+
def f(x: int):
|
83
|
+
return call_next(x + 1)
|
84
|
+
|
85
|
+
@ovld
|
86
|
+
def f(x: int):
|
87
|
+
return x * x
|
88
|
+
|
89
|
+
assert f(10) == 121
|
90
|
+
```
|
91
|
+
|
92
|
+
Both definitions above have the same type signature, but since the first has higher priority, that is the one that will be called.
|
93
|
+
|
94
|
+
However, that does not mean there is no way to call the second one. Indeed, when the first function calls the special function `call_next(x + 1)`, it will call the next function in the list below itself.
|
95
|
+
|
96
|
+
The pattern you see above is how you may wrap each call with some generic behavior. For instance, if you did something like that:
|
97
|
+
|
98
|
+
```python
|
99
|
+
@f.variant(priority=1000)
|
100
|
+
def f2(x: object)
|
101
|
+
print(f"f({x!r})")
|
102
|
+
return call_next(x)
|
103
|
+
```
|
104
|
+
|
105
|
+
You would effectively be creating a clone of `f` that traces every call.
|
106
|
+
|
107
|
+
|
108
|
+
## Dependent types
|
109
|
+
|
110
|
+
A dependent type is a type that depends on a value. `ovld` supports this, either through `Literal[value]` or `Dependent[bound, check]`. For example, this definition of factorial:
|
111
|
+
|
112
|
+
```python
|
113
|
+
from typing import Literal
|
114
|
+
from ovld import ovld, recurse, Dependent
|
115
|
+
|
116
|
+
@ovld
|
117
|
+
def fact(n: Literal[0]):
|
118
|
+
return 1
|
119
|
+
|
120
|
+
@ovld
|
121
|
+
def fact(n: Dependent[int, lambda n: n > 0]):
|
122
|
+
return n * recurse(n - 1)
|
123
|
+
|
124
|
+
assert fact(5) == 120
|
125
|
+
fact(-1) # Error!
|
126
|
+
```
|
127
|
+
|
128
|
+
The first argument to `Dependent` must be a type bound. The bound must match before the logic is called, which also ensures we don't get a performance hit for unrelated types. For type checking purposes, `Dependent[T, A]` is equivalent to `Annotated[T, A]`.
|
129
|
+
|
130
|
+
### dependent_check
|
131
|
+
|
132
|
+
Define your own types with the `@dependent_check` decorator:
|
133
|
+
|
134
|
+
```python
|
135
|
+
import torch
|
136
|
+
from ovld import ovld, dependent_check
|
137
|
+
|
138
|
+
@dependent_check
|
139
|
+
def Shape(tensor: torch.Tensor, *shape):
|
140
|
+
return (
|
141
|
+
len(tensor.shape) == len(shape)
|
142
|
+
and all(s2 is Any or s1 == s2 for s1, s2 in zip(tensor.shape, shape))
|
143
|
+
)
|
144
|
+
|
145
|
+
@dependent_check
|
146
|
+
def Dtype(tensor: torch.Tensor, dtype):
|
147
|
+
return tensor.dtype == dtype
|
148
|
+
|
149
|
+
@ovld
|
150
|
+
def f(tensor: Shape[3, Any]):
|
151
|
+
# Matches 3xN tensors
|
152
|
+
...
|
153
|
+
|
154
|
+
@ovld
|
155
|
+
def f(tensor: Shape[2, 2] & Dtype[torch.float32]):
|
156
|
+
# Only matches 2x2 tensors that also have the float32 dtype
|
157
|
+
...
|
158
|
+
```
|
159
|
+
|
160
|
+
The first parameter is the value to check. The type annotation (e.g. `value: torch.Tensor` above) is interpreted by `ovld` to be the bound for this type, so `Shape` will only be called on parameters of type `torch.Tensor`.
|
161
|
+
|
162
|
+
## Methods
|
163
|
+
|
164
|
+
Either inherit from `OvldBase` or use the `OvldMC` metaclass to use multiple dispatch on methods.
|
165
|
+
|
166
|
+
```python
|
167
|
+
from ovld import OvldBase, OvldMC
|
168
|
+
|
169
|
+
# class Cat(OvldBase): <= Also an option
|
170
|
+
class Cat(metaclass=OvldMC):
|
171
|
+
def interact(self, x: Mouse):
|
172
|
+
return "catch"
|
173
|
+
|
174
|
+
def interact(self, x: Food):
|
175
|
+
return "devour"
|
176
|
+
|
177
|
+
def interact(self, x: PricelessVase):
|
178
|
+
return "destroy"
|
179
|
+
```
|
180
|
+
|
181
|
+
### Subclasses
|
182
|
+
|
183
|
+
Subclasses inherit overloaded methods. They may define additional overloads for these methods which will only be valid for the subclass, but they need to use the `@extend_super` decorator (this is required for clarity):
|
184
|
+
|
185
|
+
|
186
|
+
```python
|
187
|
+
from ovld import OvldMC, extend_super
|
188
|
+
|
189
|
+
class One(metaclass=OvldMC):
|
190
|
+
def f(self, x: int):
|
191
|
+
return "an integer"
|
192
|
+
|
193
|
+
class Two(One):
|
194
|
+
@extend_super
|
195
|
+
def f(self, x: str):
|
196
|
+
return "a string"
|
197
|
+
|
198
|
+
assert Two().f(1) == "an integer"
|
199
|
+
assert Two().f("s") == "a string"
|
200
|
+
```
|
201
|
+
|
202
|
+
# Benchmarks
|
203
|
+
|
204
|
+
`ovld` is pretty fast: the overhead is comparable to `isinstance` or `match`, and only 2-3x slower when dispatching on `Literal` types. Compared to other multiple dispatch libraries, it is 1.5x to 100x faster.
|
205
|
+
|
206
|
+
Time relative to the fastest implementation (1.00) (lower is better).
|
207
|
+
|
208
|
+
| Bench | custom | [ovld](https://github.com/breuleux/ovld) | [plum](https://github.com/beartype/plum) | [multim](https://github.com/coady/multimethod) | [multid](https://github.com/mrocklin/multipledispatch/) | [runtype](https://github.com/erezsh/runtype) | [fastcore](https://github.com/fastai/fastcore) | [singled](https://docs.python.org/3/library/functools.html#functools.singledispatch) |
|
209
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
210
|
+
|[trivial](https://github.com/breuleux/ovld/tree/master/benchmarks/test_trivial.py)|1.14|1.00|2.53|3.64|1.61|1.86|41.31|1.54|
|
211
|
+
|[add](https://github.com/breuleux/ovld/tree/master/benchmarks/test_add.py)|1.01|1.00|3.46|4.83|2.21|2.66|56.08|x|
|
212
|
+
|[multer](https://github.com/breuleux/ovld/tree/master/benchmarks/test_multer.py)|1.00|1.06|9.79|4.11|7.19|1.89|40.37|6.34|
|
213
|
+
|[ast](https://github.com/breuleux/ovld/tree/master/benchmarks/test_ast.py)|1.00|1.06|23.07|3.04|1.68|1.87|29.11|1.63|
|
214
|
+
|[calc](https://github.com/breuleux/ovld/tree/master/benchmarks/test_calc.py)|1.00|1.96|80.00|43.21|x|x|x|x|
|
215
|
+
|[fib](https://github.com/breuleux/ovld/tree/master/benchmarks/test_fib.py)|1.00|3.58|438.97|123.58|x|x|x|x|
|
216
|
+
|[tweak](https://github.com/breuleux/ovld/tree/master/benchmarks/test_tweaknum.py)|1.00|2.59|x|x|x|x|x|x|
|
ovld-0.4.1/README.md
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
|
2
|
+
# Ovld
|
3
|
+
|
4
|
+
Fast multiple dispatch in Python, with many extra features.
|
5
|
+
|
6
|
+
[📋 Documentation](https://ovld.readthedocs.io/en/latest/)
|
7
|
+
|
8
|
+
With ovld, you can write a version of the same function for every type signature using annotations instead of writing an awkward sequence of `isinstance` statements. Unlike Python's `singledispatch`, it works for multiple arguments.
|
9
|
+
|
10
|
+
* ⚡️ **[Fast](https://ovld.readthedocs.io/en/latest/compare/#results):** `ovld` is the fastest multiple dispatch library around, by some margin.
|
11
|
+
* 🚀 [**Variants**](https://ovld.readthedocs.io/en/latest/usage/#variants) and [**mixins**](https://ovld.readthedocs.io/en/latest/usage/#mixins) of functions and methods.
|
12
|
+
* 🦄 **[Dependent types](https://ovld.readthedocs.io/en/latest/dependent/):** Overloaded functions can depend on more than argument types: they can depend on actual values.
|
13
|
+
* 🔑 **Extensive:** Dispatch on functions, methods, positional arguments and even [keyword arguments](https://ovld.readthedocs.io/en/latest/usage/#keyword-arguments) (with some restrictions).
|
14
|
+
|
15
|
+
## Example
|
16
|
+
|
17
|
+
Here's a function that recursively adds lists, tuples and dictionaries:
|
18
|
+
|
19
|
+
```python
|
20
|
+
from ovld import ovld, recurse
|
21
|
+
|
22
|
+
@ovld
|
23
|
+
def add(x: list, y: list):
|
24
|
+
return [recurse(a, b) for a, b in zip(x, y)]
|
25
|
+
|
26
|
+
@ovld
|
27
|
+
def add(x: tuple, y: tuple):
|
28
|
+
return tuple(recurse(a, b) for a, b in zip(x, y))
|
29
|
+
|
30
|
+
@ovld
|
31
|
+
def add(x: dict, y: dict):
|
32
|
+
return {k: recurse(v, y[k]) for k, v in x.items()}
|
33
|
+
|
34
|
+
@ovld
|
35
|
+
def add(x: object, y: object):
|
36
|
+
return x + y
|
37
|
+
|
38
|
+
assert add([1, 2], [3, 4]) == [4, 6]
|
39
|
+
```
|
40
|
+
|
41
|
+
The `recurse` function is special: it will recursively call the current ovld object. You may ask: how is it different from simply calling `add`? The difference is that if you create a *variant* of `add`, `recurse` will automatically call the variant.
|
42
|
+
|
43
|
+
For example:
|
44
|
+
|
45
|
+
|
46
|
+
## Variants
|
47
|
+
|
48
|
+
A *variant* of an `ovld` is a copy of the `ovld`, with some methods added or changed. For example, let's take the definition of `add` above and make a variant that multiplies numbers instead:
|
49
|
+
|
50
|
+
```python
|
51
|
+
@add.variant
|
52
|
+
def mul(self, x: object, y: object):
|
53
|
+
return x * y
|
54
|
+
|
55
|
+
assert mul([1, 2], [3, 4]) == [3, 8]
|
56
|
+
```
|
57
|
+
|
58
|
+
Simple! This means you can define one `ovld` that recursively walks generic data structures, and then specialize it in various ways.
|
59
|
+
|
60
|
+
|
61
|
+
## Priority and call_next
|
62
|
+
|
63
|
+
You can define a numeric priority for each method (the default priority is 0):
|
64
|
+
|
65
|
+
```python
|
66
|
+
from ovld import call_next
|
67
|
+
|
68
|
+
@ovld(priority=1000)
|
69
|
+
def f(x: int):
|
70
|
+
return call_next(x + 1)
|
71
|
+
|
72
|
+
@ovld
|
73
|
+
def f(x: int):
|
74
|
+
return x * x
|
75
|
+
|
76
|
+
assert f(10) == 121
|
77
|
+
```
|
78
|
+
|
79
|
+
Both definitions above have the same type signature, but since the first has higher priority, that is the one that will be called.
|
80
|
+
|
81
|
+
However, that does not mean there is no way to call the second one. Indeed, when the first function calls the special function `call_next(x + 1)`, it will call the next function in the list below itself.
|
82
|
+
|
83
|
+
The pattern you see above is how you may wrap each call with some generic behavior. For instance, if you did something like that:
|
84
|
+
|
85
|
+
```python
|
86
|
+
@f.variant(priority=1000)
|
87
|
+
def f2(x: object)
|
88
|
+
print(f"f({x!r})")
|
89
|
+
return call_next(x)
|
90
|
+
```
|
91
|
+
|
92
|
+
You would effectively be creating a clone of `f` that traces every call.
|
93
|
+
|
94
|
+
|
95
|
+
## Dependent types
|
96
|
+
|
97
|
+
A dependent type is a type that depends on a value. `ovld` supports this, either through `Literal[value]` or `Dependent[bound, check]`. For example, this definition of factorial:
|
98
|
+
|
99
|
+
```python
|
100
|
+
from typing import Literal
|
101
|
+
from ovld import ovld, recurse, Dependent
|
102
|
+
|
103
|
+
@ovld
|
104
|
+
def fact(n: Literal[0]):
|
105
|
+
return 1
|
106
|
+
|
107
|
+
@ovld
|
108
|
+
def fact(n: Dependent[int, lambda n: n > 0]):
|
109
|
+
return n * recurse(n - 1)
|
110
|
+
|
111
|
+
assert fact(5) == 120
|
112
|
+
fact(-1) # Error!
|
113
|
+
```
|
114
|
+
|
115
|
+
The first argument to `Dependent` must be a type bound. The bound must match before the logic is called, which also ensures we don't get a performance hit for unrelated types. For type checking purposes, `Dependent[T, A]` is equivalent to `Annotated[T, A]`.
|
116
|
+
|
117
|
+
### dependent_check
|
118
|
+
|
119
|
+
Define your own types with the `@dependent_check` decorator:
|
120
|
+
|
121
|
+
```python
|
122
|
+
import torch
|
123
|
+
from ovld import ovld, dependent_check
|
124
|
+
|
125
|
+
@dependent_check
|
126
|
+
def Shape(tensor: torch.Tensor, *shape):
|
127
|
+
return (
|
128
|
+
len(tensor.shape) == len(shape)
|
129
|
+
and all(s2 is Any or s1 == s2 for s1, s2 in zip(tensor.shape, shape))
|
130
|
+
)
|
131
|
+
|
132
|
+
@dependent_check
|
133
|
+
def Dtype(tensor: torch.Tensor, dtype):
|
134
|
+
return tensor.dtype == dtype
|
135
|
+
|
136
|
+
@ovld
|
137
|
+
def f(tensor: Shape[3, Any]):
|
138
|
+
# Matches 3xN tensors
|
139
|
+
...
|
140
|
+
|
141
|
+
@ovld
|
142
|
+
def f(tensor: Shape[2, 2] & Dtype[torch.float32]):
|
143
|
+
# Only matches 2x2 tensors that also have the float32 dtype
|
144
|
+
...
|
145
|
+
```
|
146
|
+
|
147
|
+
The first parameter is the value to check. The type annotation (e.g. `value: torch.Tensor` above) is interpreted by `ovld` to be the bound for this type, so `Shape` will only be called on parameters of type `torch.Tensor`.
|
148
|
+
|
149
|
+
## Methods
|
150
|
+
|
151
|
+
Either inherit from `OvldBase` or use the `OvldMC` metaclass to use multiple dispatch on methods.
|
152
|
+
|
153
|
+
```python
|
154
|
+
from ovld import OvldBase, OvldMC
|
155
|
+
|
156
|
+
# class Cat(OvldBase): <= Also an option
|
157
|
+
class Cat(metaclass=OvldMC):
|
158
|
+
def interact(self, x: Mouse):
|
159
|
+
return "catch"
|
160
|
+
|
161
|
+
def interact(self, x: Food):
|
162
|
+
return "devour"
|
163
|
+
|
164
|
+
def interact(self, x: PricelessVase):
|
165
|
+
return "destroy"
|
166
|
+
```
|
167
|
+
|
168
|
+
### Subclasses
|
169
|
+
|
170
|
+
Subclasses inherit overloaded methods. They may define additional overloads for these methods which will only be valid for the subclass, but they need to use the `@extend_super` decorator (this is required for clarity):
|
171
|
+
|
172
|
+
|
173
|
+
```python
|
174
|
+
from ovld import OvldMC, extend_super
|
175
|
+
|
176
|
+
class One(metaclass=OvldMC):
|
177
|
+
def f(self, x: int):
|
178
|
+
return "an integer"
|
179
|
+
|
180
|
+
class Two(One):
|
181
|
+
@extend_super
|
182
|
+
def f(self, x: str):
|
183
|
+
return "a string"
|
184
|
+
|
185
|
+
assert Two().f(1) == "an integer"
|
186
|
+
assert Two().f("s") == "a string"
|
187
|
+
```
|
188
|
+
|
189
|
+
# Benchmarks
|
190
|
+
|
191
|
+
`ovld` is pretty fast: the overhead is comparable to `isinstance` or `match`, and only 2-3x slower when dispatching on `Literal` types. Compared to other multiple dispatch libraries, it is 1.5x to 100x faster.
|
192
|
+
|
193
|
+
Time relative to the fastest implementation (1.00) (lower is better).
|
194
|
+
|
195
|
+
| Bench | custom | [ovld](https://github.com/breuleux/ovld) | [plum](https://github.com/beartype/plum) | [multim](https://github.com/coady/multimethod) | [multid](https://github.com/mrocklin/multipledispatch/) | [runtype](https://github.com/erezsh/runtype) | [fastcore](https://github.com/fastai/fastcore) | [singled](https://docs.python.org/3/library/functools.html#functools.singledispatch) |
|
196
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
197
|
+
|[trivial](https://github.com/breuleux/ovld/tree/master/benchmarks/test_trivial.py)|1.14|1.00|2.53|3.64|1.61|1.86|41.31|1.54|
|
198
|
+
|[add](https://github.com/breuleux/ovld/tree/master/benchmarks/test_add.py)|1.01|1.00|3.46|4.83|2.21|2.66|56.08|x|
|
199
|
+
|[multer](https://github.com/breuleux/ovld/tree/master/benchmarks/test_multer.py)|1.00|1.06|9.79|4.11|7.19|1.89|40.37|6.34|
|
200
|
+
|[ast](https://github.com/breuleux/ovld/tree/master/benchmarks/test_ast.py)|1.00|1.06|23.07|3.04|1.68|1.87|29.11|1.63|
|
201
|
+
|[calc](https://github.com/breuleux/ovld/tree/master/benchmarks/test_calc.py)|1.00|1.96|80.00|43.21|x|x|x|x|
|
202
|
+
|[fib](https://github.com/breuleux/ovld/tree/master/benchmarks/test_fib.py)|1.00|3.58|438.97|123.58|x|x|x|x|
|
203
|
+
|[tweak](https://github.com/breuleux/ovld/tree/master/benchmarks/test_tweaknum.py)|1.00|2.59|x|x|x|x|x|x|
|
ovld-0.4.1/add.py
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
from ovld.core import ovld
|
2
|
+
|
3
|
+
# @ovld(priority=1000)
|
4
|
+
# def f(x: object):
|
5
|
+
# return "A"
|
6
|
+
|
7
|
+
|
8
|
+
# @ovld
|
9
|
+
# def f(x: Number):
|
10
|
+
# return "B"
|
11
|
+
|
12
|
+
|
13
|
+
# @ovld
|
14
|
+
# def f(x: Dependent[int, lambda x: x < 0]):
|
15
|
+
# return "C"
|
16
|
+
|
17
|
+
|
18
|
+
# @ovld
|
19
|
+
# def f(x: int):
|
20
|
+
# return "D"
|
21
|
+
|
22
|
+
|
23
|
+
# @ovld
|
24
|
+
# def f(x: str):
|
25
|
+
# return "E"
|
26
|
+
|
27
|
+
|
28
|
+
# @ovld(priority=-1)
|
29
|
+
# def f(x: object):
|
30
|
+
# return "F"
|
31
|
+
|
32
|
+
|
33
|
+
# f.display_resolution(123)
|
34
|
+
# print("=" * 50)
|
35
|
+
# f.display_resolution("hello")
|
36
|
+
|
37
|
+
|
38
|
+
@ovld
|
39
|
+
def add(x: list, y: list):
|
40
|
+
return [add(a, b) for a, b in zip(x, y)]
|
41
|
+
|
42
|
+
|
43
|
+
@ovld
|
44
|
+
def add(x: tuple, y: tuple):
|
45
|
+
return tuple(add(a, b) for a, b in zip(x, y))
|
46
|
+
|
47
|
+
|
48
|
+
@ovld
|
49
|
+
def add(x: dict, y: dict):
|
50
|
+
return {k: add(v, y[k]) for k, v in x.items()}
|
51
|
+
|
52
|
+
|
53
|
+
@ovld
|
54
|
+
def add(x: object, y: object):
|
55
|
+
return x + y
|
56
|
+
|
57
|
+
|
58
|
+
add([[[1]]], [[[[2]]]])
|
ovld-0.4.1/anal.py
ADDED