ovld 0.4.6__tar.gz → 0.5.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.5.1/.github/workflows/publish.yml +47 -0
- {ovld-0.4.6 → ovld-0.5.1}/.github/workflows/python-package.yml +1 -1
- {ovld-0.4.6 → ovld-0.5.1}/PKG-INFO +62 -16
- {ovld-0.4.6 → ovld-0.5.1}/README.md +60 -14
- {ovld-0.4.6 → ovld-0.5.1}/benchmarks/common.py +32 -5
- {ovld-0.4.6 → ovld-0.5.1}/benchmarks/test_add.py +16 -19
- {ovld-0.4.6 → ovld-0.5.1}/benchmarks/test_ast.py +21 -21
- {ovld-0.4.6 → ovld-0.5.1}/benchmarks/test_calc.py +14 -15
- {ovld-0.4.6 → ovld-0.5.1}/benchmarks/test_fib.py +13 -14
- {ovld-0.4.6 → ovld-0.5.1}/benchmarks/test_multer.py +17 -20
- {ovld-0.4.6 → ovld-0.5.1}/benchmarks/test_regexp.py +25 -23
- {ovld-0.4.6 → ovld-0.5.1}/benchmarks/test_trivial.py +27 -32
- {ovld-0.4.6 → ovld-0.5.1}/benchmarks/test_tweaknum.py +13 -16
- ovld-0.5.1/docs/codegen.md +100 -0
- {ovld-0.4.6 → ovld-0.5.1}/docs/compare.md +10 -10
- {ovld-0.4.6 → ovld-0.5.1}/docs/index.md +1 -1
- ovld-0.5.1/docs/medley.md +180 -0
- {ovld-0.4.6 → ovld-0.5.1}/docs/types.md +2 -0
- {ovld-0.4.6 → ovld-0.5.1}/docs/usage.md +6 -0
- {ovld-0.4.6 → ovld-0.5.1}/mkdocs.yml +2 -0
- {ovld-0.4.6 → ovld-0.5.1}/pyproject.toml +9 -8
- {ovld-0.4.6 → ovld-0.5.1}/src/ovld/__init__.py +23 -1
- ovld-0.5.1/src/ovld/codegen.py +306 -0
- {ovld-0.4.6 → ovld-0.5.1}/src/ovld/core.py +76 -361
- {ovld-0.4.6 → ovld-0.5.1}/src/ovld/dependent.py +22 -71
- ovld-0.5.1/src/ovld/medley.py +416 -0
- {ovld-0.4.6 → ovld-0.5.1}/src/ovld/mro.py +1 -3
- {ovld-0.4.6 → ovld-0.5.1}/src/ovld/recode.py +96 -165
- ovld-0.5.1/src/ovld/signatures.py +275 -0
- {ovld-0.4.6 → ovld-0.5.1}/src/ovld/typemap.py +40 -38
- {ovld-0.4.6 → ovld-0.5.1}/src/ovld/types.py +36 -37
- {ovld-0.4.6 → ovld-0.5.1}/src/ovld/utils.py +48 -18
- ovld-0.5.1/src/ovld/version.py +1 -0
- ovld-0.5.1/tests/test_codegen/test_dataclass_gen.txt +13 -0
- ovld-0.5.1/tests/test_codegen/test_method.txt +6 -0
- ovld-0.5.1/tests/test_codegen/test_method_metaclass.txt +6 -0
- ovld-0.5.1/tests/test_codegen/test_method_per_instance.txt +14 -0
- ovld-0.5.1/tests/test_codegen/test_simple.txt +14 -0
- ovld-0.5.1/tests/test_codegen/test_variant_generation.txt +14 -0
- ovld-0.5.1/tests/test_codegen.py +323 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_dependent.py +8 -1
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_examples.py +1 -5
- ovld-0.5.1/tests/test_medley.py +532 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_mro.py +26 -12
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_ovld.py +49 -14
- ovld-0.5.1/tests/test_utils.py +19 -0
- ovld-0.5.1/uv.lock +717 -0
- ovld-0.4.6/.bsync-snap-20220324145902.852076 +0 -0
- ovld-0.4.6/.bsync-snap-20240916114026.355340 +0 -0
- ovld-0.4.6/.envrc +0 -2
- ovld-0.4.6/add.py +0 -58
- ovld-0.4.6/anal.py +0 -13
- ovld-0.4.6/beary.py +0 -120
- ovld-0.4.6/bench/requirements.txt +0 -2
- ovld-0.4.6/bench.py +0 -222
- ovld-0.4.6/benchd.py +0 -41
- ovld-0.4.6/benchmarks/test_tuple.py +0 -83
- ovld-0.4.6/benchmarks/test_typechecking.py +0 -160
- ovld-0.4.6/cloz.py +0 -24
- ovld-0.4.6/coco.py +0 -79
- ovld-0.4.6/data.py +0 -100
- ovld-0.4.6/didi.py +0 -60
- ovld-0.4.6/doo.py +0 -74
- ovld-0.4.6/edges.py +0 -64
- ovld-0.4.6/explore.py +0 -100
- ovld-0.4.6/facto.py +0 -51
- ovld-0.4.6/gen_comparison_table.py +0 -76
- ovld-0.4.6/gen_typecheck_table.py +0 -60
- ovld-0.4.6/gentest.py +0 -134
- ovld-0.4.6/hello.py +0 -6
- ovld-0.4.6/hntest.py +0 -65
- ovld-0.4.6/ichk.py +0 -39
- ovld-0.4.6/klos.py +0 -24
- ovld-0.4.6/kraken.py +0 -30
- ovld-0.4.6/nxt.py +0 -13
- ovld-0.4.6/old-dist/ovld-0.1.0-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.0.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.1-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.1.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.2-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.2.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.3-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.3.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.4-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.4.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.5-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.5.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.6-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.6.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.7-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.7.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.8-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.8.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.9-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.1.9.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.0-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.0.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.1-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.1.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.10-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.10.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.11-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.11.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.2-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.2.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.3-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.3.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.4-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.4.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.5-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.5.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.6-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.6.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.7-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.7.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.8-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.8.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.9-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.2.9.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.0-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.0.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.1-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.1.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.2-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.2.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.3-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.3.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.4-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.4.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.5-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.5.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.8-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.8.tar.gz +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.9-py3-none-any.whl +0 -0
- ovld-0.4.6/old-dist/ovld-0.3.9.tar.gz +0 -0
- ovld-0.4.6/one_two_three.py +0 -38
- ovld-0.4.6/outoftheway.toml +0 -42
- ovld-0.4.6/reddit.md +0 -56
- ovld-0.4.6/reddit.py +0 -24
- ovld-0.4.6/refun.py +0 -16
- ovld-0.4.6/reper.py +0 -6
- ovld-0.4.6/src/ovld/version.py +0 -1
- ovld-0.4.6/stuff.md +0 -43
- ovld-0.4.6/tb.py +0 -17
- ovld-0.4.6/tensor.py +0 -72
- ovld-0.4.6/tests/test_utils.py +0 -21
- ovld-0.4.6/todo.q +0 -33
- ovld-0.4.6/toot.py +0 -46
- ovld-0.4.6/typo.py +0 -36
- ovld-0.4.6/uv.lock +0 -675
- ovld-0.4.6/world.yaml +0 -16
- ovld-0.4.6/x.py +0 -51
- ovld-0.4.6/zaggo +0 -24
- {ovld-0.4.6 → ovld-0.5.1}/.gitignore +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/.python-version +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/.readthedocs.yaml +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/LICENSE +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/benchmarks/__init__.py +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/docs/dependent.md +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/docs/features.md +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/src/ovld/abc.py +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/src/ovld/py.typed +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/__init__.py +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/modules/gingerbread.py +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_abc.py +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_global.py +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_ovld/test_display.txt +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_ovld/test_display_more.txt +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_ovld/test_doc.txt +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_ovld/test_doc2.txt +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_ovld/test_method_doc.txt +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_typemap.py +0 -0
- {ovld-0.4.6 → ovld-0.5.1}/tests/test_types.py +0 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
name: Publish
|
2
|
+
on:
|
3
|
+
push:
|
4
|
+
tags:
|
5
|
+
- v**
|
6
|
+
jobs:
|
7
|
+
build:
|
8
|
+
name: Build
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
steps:
|
11
|
+
- uses: actions/checkout@v4
|
12
|
+
with:
|
13
|
+
persist-credentials: false
|
14
|
+
- name: Set up Python
|
15
|
+
uses: actions/setup-python@v5
|
16
|
+
with:
|
17
|
+
python-version: "3.x"
|
18
|
+
- name: Set up uv
|
19
|
+
uses: hynek/setup-cached-uv@v2
|
20
|
+
with:
|
21
|
+
cache-dependency-path: uv.lock
|
22
|
+
- name: Build
|
23
|
+
run: uvx hatch build
|
24
|
+
- name: Store the distribution packages
|
25
|
+
uses: actions/upload-artifact@v4
|
26
|
+
with:
|
27
|
+
name: python-package-distributions
|
28
|
+
path: dist/
|
29
|
+
publish-to-pypi:
|
30
|
+
name: >-
|
31
|
+
Publish to PyPI
|
32
|
+
needs:
|
33
|
+
- build
|
34
|
+
runs-on: ubuntu-latest
|
35
|
+
environment:
|
36
|
+
name: pypi
|
37
|
+
url: "https://pypi.org/p/${{ github.event.repository.name }}"
|
38
|
+
permissions:
|
39
|
+
id-token: write
|
40
|
+
steps:
|
41
|
+
- name: Download all the dists
|
42
|
+
uses: actions/download-artifact@v4
|
43
|
+
with:
|
44
|
+
name: python-package-distributions
|
45
|
+
path: dist/
|
46
|
+
- name: Publish
|
47
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: ovld
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.1
|
4
4
|
Summary: Overloading Python functions
|
5
5
|
Project-URL: Homepage, https://ovld.readthedocs.io/en/latest/
|
6
6
|
Project-URL: Documentation, https://ovld.readthedocs.io/en/latest/
|
@@ -20,10 +20,11 @@ Fast multiple dispatch in Python, with many extra features.
|
|
20
20
|
|
21
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
22
|
|
23
|
-
* ⚡️ **[Fast](https://ovld.readthedocs.io/en/latest/compare/#results):**
|
24
|
-
* 🚀 [**Variants**](https://ovld.readthedocs.io/en/latest/usage/#variants)
|
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), [**mixins**](https://ovld.readthedocs.io/en/latest/usage/#mixins) and [**medleys**](https://ovld.readthedocs.io/en/latest/medley) of functions and methods.
|
25
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
|
26
|
+
* 🔑 **[Extensive](https://ovld.readthedocs.io/en/latest/usage/#keyword-arguments):** Dispatch on functions, methods, positional arguments and even keyword arguments (with some restrictions).
|
27
|
+
* ⚙️ **[Codegen](https://ovld.readthedocs.io/en/latest/codegen/):** (Experimental) For advanced use cases, you can generate custom code for overloads.
|
27
28
|
|
28
29
|
## Example
|
29
30
|
|
@@ -62,7 +63,7 @@ A *variant* of an `ovld` is a copy of the `ovld`, with some methods added or cha
|
|
62
63
|
|
63
64
|
```python
|
64
65
|
@add.variant
|
65
|
-
def mul(
|
66
|
+
def mul(x: object, y: object):
|
66
67
|
return x * y
|
67
68
|
|
68
69
|
assert mul([1, 2], [3, 4]) == [3, 8]
|
@@ -199,19 +200,64 @@ assert Two().f(1) == "an integer"
|
|
199
200
|
assert Two().f("s") == "a string"
|
200
201
|
```
|
201
202
|
|
203
|
+
## Medleys
|
204
|
+
|
205
|
+
Inheriting from [`ovld.Medley`](https://ovld.readthedocs.io/en/latest/medley/) lets you combine functionality in a new way. Classes created that way are free-form medleys that you can (almost) arbitrarily combine together.
|
206
|
+
|
207
|
+
All medleys are dataclasses and you must define their data fields as you would for a normal dataclass (using `dataclass.field` if needed).
|
208
|
+
|
209
|
+
```python
|
210
|
+
from ovld import Medley
|
211
|
+
|
212
|
+
class Punctuator(Medley):
|
213
|
+
punctuation: str = "."
|
214
|
+
|
215
|
+
def __call__(self, x: str):
|
216
|
+
return f"{x}{self.punctuation}"
|
217
|
+
|
218
|
+
class Multiplier(Medley):
|
219
|
+
factor: int = 3
|
220
|
+
|
221
|
+
def __call__(self, x: int):
|
222
|
+
return x * self.factor
|
223
|
+
|
224
|
+
# You can add the classes together to merge their methods and fields using ovld
|
225
|
+
PuMu = Punctuator + Multiplier
|
226
|
+
f = PuMu(punctuation="!", factor=3)
|
227
|
+
|
228
|
+
# You can also combine existing instances!
|
229
|
+
f2 = Punctuator("!") + Multiplier(3)
|
230
|
+
|
231
|
+
assert f("hello") == f2("hello") == "hello!"
|
232
|
+
assert f(10) == f2(10) == 30
|
233
|
+
|
234
|
+
# You can also meld medleys inplace, but only if all new fields have defaults
|
235
|
+
class Total(Medley):
|
236
|
+
pass
|
237
|
+
|
238
|
+
Total.extend(Punctuator, Multiplier)
|
239
|
+
f3 = Total(punctuation="!", factor=3)
|
240
|
+
```
|
241
|
+
|
242
|
+
|
243
|
+
# Code generation
|
244
|
+
|
245
|
+
(Experimental) For advanced use cases, you can generate custom code for type checkers or overloads. [See here](https://ovld.readthedocs.io/en/latest/codegen/).
|
246
|
+
|
247
|
+
|
202
248
|
# Benchmarks
|
203
249
|
|
204
250
|
`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 has 1.5x to 100x less overhead.
|
205
251
|
|
206
252
|
Time relative to the fastest implementation (1.00) (lower is better).
|
207
253
|
|
208
|
-
| Benchmark | 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
|
209
|
-
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
210
|
-
|[trivial](https://github.com/breuleux/ovld/tree/master/benchmarks/test_trivial.py)|1.45|1.00|3.32|4.63|2.04|2.41|
|
211
|
-
|[multer](https://github.com/breuleux/ovld/tree/master/benchmarks/test_multer.py)|1.13|1.00|11.05|4.53|8.31|2.19|
|
212
|
-
|[add](https://github.com/breuleux/ovld/tree/master/benchmarks/test_add.py)|1.08|1.00|3.73|5.21|2.37|2.79|
|
213
|
-
|[ast](https://github.com/breuleux/ovld/tree/master/benchmarks/test_ast.py)|1.00|1.08|23.14|3.09|1.68|1.91|
|
214
|
-
|[calc](https://github.com/breuleux/ovld/tree/master/benchmarks/test_calc.py)|1.00|1.23|54.61|29.32|x|x|x|
|
215
|
-
|[regexp](https://github.com/breuleux/ovld/tree/master/benchmarks/test_regexp.py)|1.00|1.87|19.18|x|x|x|x|
|
216
|
-
|[fib](https://github.com/breuleux/ovld/tree/master/benchmarks/test_fib.py)|1.00|3.30|444.31|125.77|x|x|x|
|
217
|
-
|[tweaknum](https://github.com/breuleux/ovld/tree/master/benchmarks/test_tweaknum.py)|1.00|2.09|x|x|x|x|x|
|
254
|
+
| Benchmark | 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) | [sd](https://docs.python.org/3/library/functools.html#functools.singledispatch) |
|
255
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
256
|
+
|[trivial](https://github.com/breuleux/ovld/tree/master/benchmarks/test_trivial.py)|1.45|1.00|3.32|4.63|2.04|2.41|1.91|
|
257
|
+
|[multer](https://github.com/breuleux/ovld/tree/master/benchmarks/test_multer.py)|1.13|1.00|11.05|4.53|8.31|2.19|7.32|
|
258
|
+
|[add](https://github.com/breuleux/ovld/tree/master/benchmarks/test_add.py)|1.08|1.00|3.73|5.21|2.37|2.79|x|
|
259
|
+
|[ast](https://github.com/breuleux/ovld/tree/master/benchmarks/test_ast.py)|1.00|1.08|23.14|3.09|1.68|1.91|1.66|
|
260
|
+
|[calc](https://github.com/breuleux/ovld/tree/master/benchmarks/test_calc.py)|1.00|1.23|54.61|29.32|x|x|x|
|
261
|
+
|[regexp](https://github.com/breuleux/ovld/tree/master/benchmarks/test_regexp.py)|1.00|1.87|19.18|x|x|x|x|
|
262
|
+
|[fib](https://github.com/breuleux/ovld/tree/master/benchmarks/test_fib.py)|1.00|3.30|444.31|125.77|x|x|x|
|
263
|
+
|[tweaknum](https://github.com/breuleux/ovld/tree/master/benchmarks/test_tweaknum.py)|1.00|2.09|x|x|x|x|x|
|
@@ -7,10 +7,11 @@ Fast multiple dispatch in Python, with many extra features.
|
|
7
7
|
|
8
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
9
|
|
10
|
-
* ⚡️ **[Fast](https://ovld.readthedocs.io/en/latest/compare/#results):**
|
11
|
-
* 🚀 [**Variants**](https://ovld.readthedocs.io/en/latest/usage/#variants)
|
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), [**mixins**](https://ovld.readthedocs.io/en/latest/usage/#mixins) and [**medleys**](https://ovld.readthedocs.io/en/latest/medley) of functions and methods.
|
12
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
|
13
|
+
* 🔑 **[Extensive](https://ovld.readthedocs.io/en/latest/usage/#keyword-arguments):** Dispatch on functions, methods, positional arguments and even keyword arguments (with some restrictions).
|
14
|
+
* ⚙️ **[Codegen](https://ovld.readthedocs.io/en/latest/codegen/):** (Experimental) For advanced use cases, you can generate custom code for overloads.
|
14
15
|
|
15
16
|
## Example
|
16
17
|
|
@@ -49,7 +50,7 @@ A *variant* of an `ovld` is a copy of the `ovld`, with some methods added or cha
|
|
49
50
|
|
50
51
|
```python
|
51
52
|
@add.variant
|
52
|
-
def mul(
|
53
|
+
def mul(x: object, y: object):
|
53
54
|
return x * y
|
54
55
|
|
55
56
|
assert mul([1, 2], [3, 4]) == [3, 8]
|
@@ -186,19 +187,64 @@ assert Two().f(1) == "an integer"
|
|
186
187
|
assert Two().f("s") == "a string"
|
187
188
|
```
|
188
189
|
|
190
|
+
## Medleys
|
191
|
+
|
192
|
+
Inheriting from [`ovld.Medley`](https://ovld.readthedocs.io/en/latest/medley/) lets you combine functionality in a new way. Classes created that way are free-form medleys that you can (almost) arbitrarily combine together.
|
193
|
+
|
194
|
+
All medleys are dataclasses and you must define their data fields as you would for a normal dataclass (using `dataclass.field` if needed).
|
195
|
+
|
196
|
+
```python
|
197
|
+
from ovld import Medley
|
198
|
+
|
199
|
+
class Punctuator(Medley):
|
200
|
+
punctuation: str = "."
|
201
|
+
|
202
|
+
def __call__(self, x: str):
|
203
|
+
return f"{x}{self.punctuation}"
|
204
|
+
|
205
|
+
class Multiplier(Medley):
|
206
|
+
factor: int = 3
|
207
|
+
|
208
|
+
def __call__(self, x: int):
|
209
|
+
return x * self.factor
|
210
|
+
|
211
|
+
# You can add the classes together to merge their methods and fields using ovld
|
212
|
+
PuMu = Punctuator + Multiplier
|
213
|
+
f = PuMu(punctuation="!", factor=3)
|
214
|
+
|
215
|
+
# You can also combine existing instances!
|
216
|
+
f2 = Punctuator("!") + Multiplier(3)
|
217
|
+
|
218
|
+
assert f("hello") == f2("hello") == "hello!"
|
219
|
+
assert f(10) == f2(10) == 30
|
220
|
+
|
221
|
+
# You can also meld medleys inplace, but only if all new fields have defaults
|
222
|
+
class Total(Medley):
|
223
|
+
pass
|
224
|
+
|
225
|
+
Total.extend(Punctuator, Multiplier)
|
226
|
+
f3 = Total(punctuation="!", factor=3)
|
227
|
+
```
|
228
|
+
|
229
|
+
|
230
|
+
# Code generation
|
231
|
+
|
232
|
+
(Experimental) For advanced use cases, you can generate custom code for type checkers or overloads. [See here](https://ovld.readthedocs.io/en/latest/codegen/).
|
233
|
+
|
234
|
+
|
189
235
|
# Benchmarks
|
190
236
|
|
191
237
|
`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 has 1.5x to 100x less overhead.
|
192
238
|
|
193
239
|
Time relative to the fastest implementation (1.00) (lower is better).
|
194
240
|
|
195
|
-
| Benchmark | 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
|
196
|
-
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
197
|
-
|[trivial](https://github.com/breuleux/ovld/tree/master/benchmarks/test_trivial.py)|1.45|1.00|3.32|4.63|2.04|2.41|
|
198
|
-
|[multer](https://github.com/breuleux/ovld/tree/master/benchmarks/test_multer.py)|1.13|1.00|11.05|4.53|8.31|2.19|
|
199
|
-
|[add](https://github.com/breuleux/ovld/tree/master/benchmarks/test_add.py)|1.08|1.00|3.73|5.21|2.37|2.79|
|
200
|
-
|[ast](https://github.com/breuleux/ovld/tree/master/benchmarks/test_ast.py)|1.00|1.08|23.14|3.09|1.68|1.91|
|
201
|
-
|[calc](https://github.com/breuleux/ovld/tree/master/benchmarks/test_calc.py)|1.00|1.23|54.61|29.32|x|x|x|
|
202
|
-
|[regexp](https://github.com/breuleux/ovld/tree/master/benchmarks/test_regexp.py)|1.00|1.87|19.18|x|x|x|x|
|
203
|
-
|[fib](https://github.com/breuleux/ovld/tree/master/benchmarks/test_fib.py)|1.00|3.30|444.31|125.77|x|x|x|
|
204
|
-
|[tweaknum](https://github.com/breuleux/ovld/tree/master/benchmarks/test_tweaknum.py)|1.00|2.09|x|x|x|x|x|
|
241
|
+
| Benchmark | 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) | [sd](https://docs.python.org/3/library/functools.html#functools.singledispatch) |
|
242
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
243
|
+
|[trivial](https://github.com/breuleux/ovld/tree/master/benchmarks/test_trivial.py)|1.45|1.00|3.32|4.63|2.04|2.41|1.91|
|
244
|
+
|[multer](https://github.com/breuleux/ovld/tree/master/benchmarks/test_multer.py)|1.13|1.00|11.05|4.53|8.31|2.19|7.32|
|
245
|
+
|[add](https://github.com/breuleux/ovld/tree/master/benchmarks/test_add.py)|1.08|1.00|3.73|5.21|2.37|2.79|x|
|
246
|
+
|[ast](https://github.com/breuleux/ovld/tree/master/benchmarks/test_ast.py)|1.00|1.08|23.14|3.09|1.68|1.91|1.66|
|
247
|
+
|[calc](https://github.com/breuleux/ovld/tree/master/benchmarks/test_calc.py)|1.00|1.23|54.61|29.32|x|x|x|
|
248
|
+
|[regexp](https://github.com/breuleux/ovld/tree/master/benchmarks/test_regexp.py)|1.00|1.87|19.18|x|x|x|x|
|
249
|
+
|[fib](https://github.com/breuleux/ovld/tree/master/benchmarks/test_fib.py)|1.00|3.30|444.31|125.77|x|x|x|
|
250
|
+
|[tweaknum](https://github.com/breuleux/ovld/tree/master/benchmarks/test_tweaknum.py)|1.00|2.09|x|x|x|x|x|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import sys
|
2
2
|
from functools import singledispatch
|
3
3
|
|
4
|
-
|
4
|
+
import pytest
|
5
5
|
from multimethod import multimethod as multimethod_dispatch
|
6
6
|
from multipledispatch import dispatch as _md_dispatch
|
7
7
|
from plum import dispatch as plum_dispatch
|
@@ -19,9 +19,7 @@ def _locate(fn):
|
|
19
19
|
|
20
20
|
def _getanns(fn):
|
21
21
|
anns = fn.__annotations__.values()
|
22
|
-
anns = [
|
23
|
-
tuple(ann.__args__) if hasattr(ann, "__args__") else ann for ann in anns
|
24
|
-
]
|
22
|
+
anns = [tuple(ann.__args__) if hasattr(ann, "__args__") else ann for ann in anns]
|
25
23
|
return anns
|
26
24
|
|
27
25
|
|
@@ -44,12 +42,41 @@ def singledispatch_dispatch(fn):
|
|
44
42
|
return singledispatch(fn)
|
45
43
|
|
46
44
|
|
45
|
+
def with_functions(**tests):
|
46
|
+
return pytest.mark.parametrize(
|
47
|
+
"fn",
|
48
|
+
argvalues=list(tests.values()),
|
49
|
+
ids=list(tests.keys()),
|
50
|
+
)
|
51
|
+
|
52
|
+
|
53
|
+
def function_builder(defn):
|
54
|
+
def wrapper(dispatch):
|
55
|
+
try:
|
56
|
+
rval = defn(dispatch)
|
57
|
+
except Exception as exc:
|
58
|
+
e = exc
|
59
|
+
|
60
|
+
def reraise(*args, **kwargs):
|
61
|
+
raise e
|
62
|
+
|
63
|
+
rval = reraise
|
64
|
+
try:
|
65
|
+
rval._dispatch = dispatch
|
66
|
+
except AttributeError:
|
67
|
+
pass
|
68
|
+
return rval
|
69
|
+
|
70
|
+
return wrapper
|
71
|
+
|
72
|
+
|
47
73
|
__all__ = [
|
48
74
|
"multimethod_dispatch",
|
49
75
|
"plum_dispatch",
|
50
76
|
"runtype_dispatch",
|
51
77
|
"ovld_dispatch",
|
52
78
|
"multipledispatch_dispatch",
|
53
|
-
"fastcore_dispatch",
|
54
79
|
"singledispatch_dispatch",
|
80
|
+
"function_builder",
|
81
|
+
"with_functions",
|
55
82
|
]
|
@@ -1,12 +1,13 @@
|
|
1
1
|
import pytest
|
2
2
|
|
3
3
|
from .common import (
|
4
|
-
|
4
|
+
function_builder,
|
5
5
|
multimethod_dispatch,
|
6
6
|
multipledispatch_dispatch,
|
7
7
|
ovld_dispatch,
|
8
8
|
plum_dispatch,
|
9
9
|
runtype_dispatch,
|
10
|
+
with_functions,
|
10
11
|
)
|
11
12
|
|
12
13
|
###############################
|
@@ -14,6 +15,7 @@ from .common import (
|
|
14
15
|
###############################
|
15
16
|
|
16
17
|
|
18
|
+
@function_builder
|
17
19
|
def make_add(dispatch):
|
18
20
|
@dispatch
|
19
21
|
def add(x: list, y: list):
|
@@ -76,21 +78,16 @@ B = {"xs": list(range(10, 60)), "ys": ("x", (7, 6))}
|
|
76
78
|
C = {"xs": list(range(10, 110, 2)), "ys": ("ox", (13, 13))}
|
77
79
|
|
78
80
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
test_add_runtype = make_test(make_add(runtype_dispatch))
|
93
|
-
test_add_fastcore = make_test(make_add(fastcore_dispatch))
|
94
|
-
|
95
|
-
test_add_custom_isinstance = make_test(add_isinstance)
|
96
|
-
test_add_custom_match = make_test(add_match)
|
81
|
+
@pytest.mark.benchmark(group="add")
|
82
|
+
@with_functions(
|
83
|
+
ovld=make_add(ovld_dispatch),
|
84
|
+
plum=make_add(plum_dispatch),
|
85
|
+
multimethod=make_add(multimethod_dispatch),
|
86
|
+
multipledispatch=make_add(multipledispatch_dispatch),
|
87
|
+
runtype=make_add(runtype_dispatch),
|
88
|
+
isinstance=add_isinstance,
|
89
|
+
match=add_match,
|
90
|
+
)
|
91
|
+
def test_add(fn, benchmark):
|
92
|
+
result = benchmark(fn, A, B)
|
93
|
+
assert result == C
|
@@ -6,13 +6,14 @@ from types import NoneType
|
|
6
6
|
import pytest
|
7
7
|
|
8
8
|
from .common import (
|
9
|
-
|
9
|
+
function_builder,
|
10
10
|
multimethod_dispatch,
|
11
11
|
multipledispatch_dispatch,
|
12
12
|
ovld_dispatch,
|
13
13
|
plum_dispatch,
|
14
14
|
runtype_dispatch,
|
15
15
|
singledispatch_dispatch,
|
16
|
+
with_functions,
|
16
17
|
)
|
17
18
|
|
18
19
|
|
@@ -33,6 +34,7 @@ def foobar():
|
|
33
34
|
return ast.parse(inspect.getsource(foo)), ast.parse(inspect.getsource(bar))
|
34
35
|
|
35
36
|
|
37
|
+
@function_builder
|
36
38
|
def make_transform(dispatch):
|
37
39
|
@dispatch
|
38
40
|
def transform(node: list):
|
@@ -94,23 +96,21 @@ class NT(ast.NodeTransformer):
|
|
94
96
|
####################
|
95
97
|
|
96
98
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
test_ast_singledispatch = make_test(make_transform(singledispatch_dispatch))
|
116
|
-
test_ast_custom = make_test(NT().visit)
|
99
|
+
@pytest.mark.benchmark(group="ast")
|
100
|
+
@with_functions(
|
101
|
+
ovld=make_transform(ovld_dispatch),
|
102
|
+
plum=make_transform(plum_dispatch),
|
103
|
+
multimethod=make_transform(multimethod_dispatch),
|
104
|
+
multipledispatch=make_transform(multipledispatch_dispatch),
|
105
|
+
runtype=make_transform(runtype_dispatch),
|
106
|
+
singledispatch=make_transform(singledispatch_dispatch),
|
107
|
+
custom=NT().visit,
|
108
|
+
)
|
109
|
+
def test_ast(fn, foobar, benchmark):
|
110
|
+
if (
|
111
|
+
sys.version_info < (3, 11)
|
112
|
+
and getattr(fn, "_dispatch", None) is singledispatch_dispatch
|
113
|
+
):
|
114
|
+
pytest.skip()
|
115
|
+
result = benchmark(fn, foobar[0])
|
116
|
+
assert ast.dump(result, indent=2) == ast.dump(foobar[1], indent=2).replace("bar", "foo")
|
@@ -4,9 +4,11 @@ from typing import Literal
|
|
4
4
|
import pytest
|
5
5
|
|
6
6
|
from .common import (
|
7
|
+
function_builder,
|
7
8
|
multimethod_dispatch,
|
8
9
|
ovld_dispatch,
|
9
10
|
plum_dispatch,
|
11
|
+
with_functions,
|
10
12
|
)
|
11
13
|
|
12
14
|
###############################
|
@@ -14,6 +16,7 @@ from .common import (
|
|
14
16
|
###############################
|
15
17
|
|
16
18
|
|
19
|
+
@function_builder
|
17
20
|
def make_calc(dispatch):
|
18
21
|
@dispatch
|
19
22
|
def calc(num: Number):
|
@@ -127,18 +130,14 @@ expr = ("add", ("mul", ("sqrt", 4), 7), ("div", ("add", 6, 4), ("sub", 5, 3)))
|
|
127
130
|
expected_result = 19
|
128
131
|
|
129
132
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
test_calc_multimethod = make_test(make_calc(multimethod_dispatch))
|
142
|
-
|
143
|
-
test_calc_custom_dict = make_test(calc_dict)
|
144
|
-
test_calc_custom_match = make_test(calc_match)
|
133
|
+
@pytest.mark.benchmark(group="calc")
|
134
|
+
@with_functions(
|
135
|
+
ovld=make_calc(ovld_dispatch),
|
136
|
+
plum=make_calc(plum_dispatch),
|
137
|
+
multimethod=make_calc(multimethod_dispatch),
|
138
|
+
custom_dict=calc_dict,
|
139
|
+
custom_match=calc_match,
|
140
|
+
)
|
141
|
+
def test_calc(fn, benchmark):
|
142
|
+
result = benchmark(fn, expr)
|
143
|
+
assert result == expected_result
|
@@ -3,9 +3,11 @@ from typing import Literal
|
|
3
3
|
import pytest
|
4
4
|
|
5
5
|
from .common import (
|
6
|
+
function_builder,
|
6
7
|
multimethod_dispatch,
|
7
8
|
ovld_dispatch,
|
8
9
|
plum_dispatch,
|
10
|
+
with_functions,
|
9
11
|
)
|
10
12
|
|
11
13
|
###############################
|
@@ -13,6 +15,7 @@ from .common import (
|
|
13
15
|
###############################
|
14
16
|
|
15
17
|
|
18
|
+
@function_builder
|
16
19
|
def make_fib(dispatch):
|
17
20
|
@dispatch
|
18
21
|
def fib(n: Literal[0]):
|
@@ -46,17 +49,13 @@ def fib_normal(n):
|
|
46
49
|
####################
|
47
50
|
|
48
51
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
test_fib_plum = make_test(make_fib(plum_dispatch))
|
60
|
-
test_fib_multimethod = make_test(make_fib(multimethod_dispatch))
|
61
|
-
|
62
|
-
test_fib_custom = make_test(fib_normal)
|
52
|
+
@pytest.mark.benchmark(group="fib")
|
53
|
+
@with_functions(
|
54
|
+
ovld=make_fib(ovld_dispatch),
|
55
|
+
plum=make_fib(plum_dispatch),
|
56
|
+
multimethod=make_fib(multimethod_dispatch),
|
57
|
+
custom=fib_normal,
|
58
|
+
)
|
59
|
+
def test_fib(fn, benchmark):
|
60
|
+
result = benchmark(fn, 8)
|
61
|
+
assert result == 21
|
@@ -7,11 +7,12 @@ from ovld import recurse
|
|
7
7
|
from ovld.core import OvldBase
|
8
8
|
|
9
9
|
from .common import (
|
10
|
-
|
10
|
+
function_builder,
|
11
11
|
multimethod_dispatch,
|
12
12
|
ovld_dispatch,
|
13
13
|
plum_dispatch,
|
14
14
|
runtype_dispatch,
|
15
|
+
with_functions,
|
15
16
|
)
|
16
17
|
|
17
18
|
|
@@ -20,6 +21,7 @@ class BaseMulter:
|
|
20
21
|
self.factor = factor
|
21
22
|
|
22
23
|
|
24
|
+
@function_builder
|
23
25
|
def make_multer(dispatch):
|
24
26
|
class Multer(BaseMulter):
|
25
27
|
@dispatch
|
@@ -138,22 +140,17 @@ A = {"xs": list(range(0, 50)), "ys": ("o", (6, 7))}
|
|
138
140
|
C = {"xs": list(range(0, 150, 3)), "ys": ("ooo", (18, 21))}
|
139
141
|
|
140
142
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
test_multer_multipledispatch = make_test(MultipleDispatchMulter)
|
156
|
-
test_multer_runtype = make_test(make_multer(runtype_dispatch))
|
157
|
-
test_multer_fastcore = make_test(make_multer(fastcore_dispatch))
|
158
|
-
|
159
|
-
test_multer_custom_isinstance = make_test(IsinstanceMulter)
|
143
|
+
@pytest.mark.benchmark(group="multer")
|
144
|
+
@with_functions(
|
145
|
+
ovld=make_multer(ovld_dispatch),
|
146
|
+
recurse=OvldRecurseMulter,
|
147
|
+
plum=make_multer(plum_dispatch),
|
148
|
+
multimethod=make_multer(multimethod_dispatch),
|
149
|
+
singledispatch=SingleDispatchMulter,
|
150
|
+
multipledispatch=MultipleDispatchMulter,
|
151
|
+
runtype=make_multer(runtype_dispatch),
|
152
|
+
isinstance=IsinstanceMulter,
|
153
|
+
)
|
154
|
+
def test_multer(fn, benchmark):
|
155
|
+
result = benchmark(fn(3), A)
|
156
|
+
assert result == C
|