ovld 0.4.6__tar.gz → 0.5.0__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.
Files changed (173) hide show
  1. ovld-0.5.0/.github/workflows/publish.yml +47 -0
  2. {ovld-0.4.6 → ovld-0.5.0}/.github/workflows/python-package.yml +1 -1
  3. {ovld-0.4.6 → ovld-0.5.0}/PKG-INFO +62 -16
  4. {ovld-0.4.6 → ovld-0.5.0}/README.md +60 -14
  5. {ovld-0.4.6 → ovld-0.5.0}/benchmarks/common.py +32 -5
  6. {ovld-0.4.6 → ovld-0.5.0}/benchmarks/test_add.py +16 -19
  7. {ovld-0.4.6 → ovld-0.5.0}/benchmarks/test_ast.py +21 -21
  8. {ovld-0.4.6 → ovld-0.5.0}/benchmarks/test_calc.py +14 -15
  9. {ovld-0.4.6 → ovld-0.5.0}/benchmarks/test_fib.py +13 -14
  10. {ovld-0.4.6 → ovld-0.5.0}/benchmarks/test_multer.py +17 -20
  11. {ovld-0.4.6 → ovld-0.5.0}/benchmarks/test_regexp.py +25 -23
  12. {ovld-0.4.6 → ovld-0.5.0}/benchmarks/test_trivial.py +27 -32
  13. {ovld-0.4.6 → ovld-0.5.0}/benchmarks/test_tweaknum.py +13 -16
  14. ovld-0.5.0/docs/codegen.md +100 -0
  15. {ovld-0.4.6 → ovld-0.5.0}/docs/compare.md +10 -10
  16. {ovld-0.4.6 → ovld-0.5.0}/docs/index.md +1 -1
  17. ovld-0.5.0/docs/medley.md +180 -0
  18. {ovld-0.4.6 → ovld-0.5.0}/docs/types.md +2 -0
  19. {ovld-0.4.6 → ovld-0.5.0}/docs/usage.md +6 -0
  20. {ovld-0.4.6 → ovld-0.5.0}/mkdocs.yml +2 -0
  21. {ovld-0.4.6 → ovld-0.5.0}/pyproject.toml +8 -7
  22. {ovld-0.4.6 → ovld-0.5.0}/src/ovld/__init__.py +23 -1
  23. ovld-0.5.0/src/ovld/codegen.py +303 -0
  24. {ovld-0.4.6 → ovld-0.5.0}/src/ovld/core.py +62 -349
  25. {ovld-0.4.6 → ovld-0.5.0}/src/ovld/dependent.py +22 -71
  26. ovld-0.5.0/src/ovld/medley.py +408 -0
  27. {ovld-0.4.6 → ovld-0.5.0}/src/ovld/mro.py +1 -3
  28. {ovld-0.4.6 → ovld-0.5.0}/src/ovld/recode.py +99 -165
  29. ovld-0.5.0/src/ovld/signatures.py +275 -0
  30. {ovld-0.4.6 → ovld-0.5.0}/src/ovld/typemap.py +40 -38
  31. {ovld-0.4.6 → ovld-0.5.0}/src/ovld/types.py +36 -37
  32. {ovld-0.4.6 → ovld-0.5.0}/src/ovld/utils.py +44 -18
  33. ovld-0.5.0/src/ovld/version.py +1 -0
  34. ovld-0.5.0/tests/test_codegen/test_dataclass_gen.txt +13 -0
  35. ovld-0.5.0/tests/test_codegen/test_method.txt +6 -0
  36. ovld-0.5.0/tests/test_codegen/test_method_metaclass.txt +6 -0
  37. ovld-0.5.0/tests/test_codegen/test_method_per_instance.txt +14 -0
  38. ovld-0.5.0/tests/test_codegen/test_simple.txt +14 -0
  39. ovld-0.5.0/tests/test_codegen/test_variant_generation.txt +14 -0
  40. ovld-0.5.0/tests/test_codegen.py +309 -0
  41. {ovld-0.4.6 → ovld-0.5.0}/tests/test_dependent.py +8 -1
  42. {ovld-0.4.6 → ovld-0.5.0}/tests/test_examples.py +1 -5
  43. ovld-0.5.0/tests/test_medley.py +532 -0
  44. {ovld-0.4.6 → ovld-0.5.0}/tests/test_mro.py +26 -12
  45. {ovld-0.4.6 → ovld-0.5.0}/tests/test_ovld.py +49 -14
  46. ovld-0.5.0/tests/test_utils.py +19 -0
  47. ovld-0.5.0/uv.lock +718 -0
  48. ovld-0.4.6/.bsync-snap-20220324145902.852076 +0 -0
  49. ovld-0.4.6/.bsync-snap-20240916114026.355340 +0 -0
  50. ovld-0.4.6/.envrc +0 -2
  51. ovld-0.4.6/add.py +0 -58
  52. ovld-0.4.6/anal.py +0 -13
  53. ovld-0.4.6/beary.py +0 -120
  54. ovld-0.4.6/bench/requirements.txt +0 -2
  55. ovld-0.4.6/bench.py +0 -222
  56. ovld-0.4.6/benchd.py +0 -41
  57. ovld-0.4.6/benchmarks/test_tuple.py +0 -83
  58. ovld-0.4.6/benchmarks/test_typechecking.py +0 -160
  59. ovld-0.4.6/cloz.py +0 -24
  60. ovld-0.4.6/coco.py +0 -79
  61. ovld-0.4.6/data.py +0 -100
  62. ovld-0.4.6/didi.py +0 -60
  63. ovld-0.4.6/doo.py +0 -74
  64. ovld-0.4.6/edges.py +0 -64
  65. ovld-0.4.6/explore.py +0 -100
  66. ovld-0.4.6/facto.py +0 -51
  67. ovld-0.4.6/gen_comparison_table.py +0 -76
  68. ovld-0.4.6/gen_typecheck_table.py +0 -60
  69. ovld-0.4.6/gentest.py +0 -134
  70. ovld-0.4.6/hello.py +0 -6
  71. ovld-0.4.6/hntest.py +0 -65
  72. ovld-0.4.6/ichk.py +0 -39
  73. ovld-0.4.6/klos.py +0 -24
  74. ovld-0.4.6/kraken.py +0 -30
  75. ovld-0.4.6/nxt.py +0 -13
  76. ovld-0.4.6/old-dist/ovld-0.1.0-py3-none-any.whl +0 -0
  77. ovld-0.4.6/old-dist/ovld-0.1.0.tar.gz +0 -0
  78. ovld-0.4.6/old-dist/ovld-0.1.1-py3-none-any.whl +0 -0
  79. ovld-0.4.6/old-dist/ovld-0.1.1.tar.gz +0 -0
  80. ovld-0.4.6/old-dist/ovld-0.1.2-py3-none-any.whl +0 -0
  81. ovld-0.4.6/old-dist/ovld-0.1.2.tar.gz +0 -0
  82. ovld-0.4.6/old-dist/ovld-0.1.3-py3-none-any.whl +0 -0
  83. ovld-0.4.6/old-dist/ovld-0.1.3.tar.gz +0 -0
  84. ovld-0.4.6/old-dist/ovld-0.1.4-py3-none-any.whl +0 -0
  85. ovld-0.4.6/old-dist/ovld-0.1.4.tar.gz +0 -0
  86. ovld-0.4.6/old-dist/ovld-0.1.5-py3-none-any.whl +0 -0
  87. ovld-0.4.6/old-dist/ovld-0.1.5.tar.gz +0 -0
  88. ovld-0.4.6/old-dist/ovld-0.1.6-py3-none-any.whl +0 -0
  89. ovld-0.4.6/old-dist/ovld-0.1.6.tar.gz +0 -0
  90. ovld-0.4.6/old-dist/ovld-0.1.7-py3-none-any.whl +0 -0
  91. ovld-0.4.6/old-dist/ovld-0.1.7.tar.gz +0 -0
  92. ovld-0.4.6/old-dist/ovld-0.1.8-py3-none-any.whl +0 -0
  93. ovld-0.4.6/old-dist/ovld-0.1.8.tar.gz +0 -0
  94. ovld-0.4.6/old-dist/ovld-0.1.9-py3-none-any.whl +0 -0
  95. ovld-0.4.6/old-dist/ovld-0.1.9.tar.gz +0 -0
  96. ovld-0.4.6/old-dist/ovld-0.2.0-py3-none-any.whl +0 -0
  97. ovld-0.4.6/old-dist/ovld-0.2.0.tar.gz +0 -0
  98. ovld-0.4.6/old-dist/ovld-0.2.1-py3-none-any.whl +0 -0
  99. ovld-0.4.6/old-dist/ovld-0.2.1.tar.gz +0 -0
  100. ovld-0.4.6/old-dist/ovld-0.2.10-py3-none-any.whl +0 -0
  101. ovld-0.4.6/old-dist/ovld-0.2.10.tar.gz +0 -0
  102. ovld-0.4.6/old-dist/ovld-0.2.11-py3-none-any.whl +0 -0
  103. ovld-0.4.6/old-dist/ovld-0.2.11.tar.gz +0 -0
  104. ovld-0.4.6/old-dist/ovld-0.2.2-py3-none-any.whl +0 -0
  105. ovld-0.4.6/old-dist/ovld-0.2.2.tar.gz +0 -0
  106. ovld-0.4.6/old-dist/ovld-0.2.3-py3-none-any.whl +0 -0
  107. ovld-0.4.6/old-dist/ovld-0.2.3.tar.gz +0 -0
  108. ovld-0.4.6/old-dist/ovld-0.2.4-py3-none-any.whl +0 -0
  109. ovld-0.4.6/old-dist/ovld-0.2.4.tar.gz +0 -0
  110. ovld-0.4.6/old-dist/ovld-0.2.5-py3-none-any.whl +0 -0
  111. ovld-0.4.6/old-dist/ovld-0.2.5.tar.gz +0 -0
  112. ovld-0.4.6/old-dist/ovld-0.2.6-py3-none-any.whl +0 -0
  113. ovld-0.4.6/old-dist/ovld-0.2.6.tar.gz +0 -0
  114. ovld-0.4.6/old-dist/ovld-0.2.7-py3-none-any.whl +0 -0
  115. ovld-0.4.6/old-dist/ovld-0.2.7.tar.gz +0 -0
  116. ovld-0.4.6/old-dist/ovld-0.2.8-py3-none-any.whl +0 -0
  117. ovld-0.4.6/old-dist/ovld-0.2.8.tar.gz +0 -0
  118. ovld-0.4.6/old-dist/ovld-0.2.9-py3-none-any.whl +0 -0
  119. ovld-0.4.6/old-dist/ovld-0.2.9.tar.gz +0 -0
  120. ovld-0.4.6/old-dist/ovld-0.3.0-py3-none-any.whl +0 -0
  121. ovld-0.4.6/old-dist/ovld-0.3.0.tar.gz +0 -0
  122. ovld-0.4.6/old-dist/ovld-0.3.1-py3-none-any.whl +0 -0
  123. ovld-0.4.6/old-dist/ovld-0.3.1.tar.gz +0 -0
  124. ovld-0.4.6/old-dist/ovld-0.3.2-py3-none-any.whl +0 -0
  125. ovld-0.4.6/old-dist/ovld-0.3.2.tar.gz +0 -0
  126. ovld-0.4.6/old-dist/ovld-0.3.3-py3-none-any.whl +0 -0
  127. ovld-0.4.6/old-dist/ovld-0.3.3.tar.gz +0 -0
  128. ovld-0.4.6/old-dist/ovld-0.3.4-py3-none-any.whl +0 -0
  129. ovld-0.4.6/old-dist/ovld-0.3.4.tar.gz +0 -0
  130. ovld-0.4.6/old-dist/ovld-0.3.5-py3-none-any.whl +0 -0
  131. ovld-0.4.6/old-dist/ovld-0.3.5.tar.gz +0 -0
  132. ovld-0.4.6/old-dist/ovld-0.3.8-py3-none-any.whl +0 -0
  133. ovld-0.4.6/old-dist/ovld-0.3.8.tar.gz +0 -0
  134. ovld-0.4.6/old-dist/ovld-0.3.9-py3-none-any.whl +0 -0
  135. ovld-0.4.6/old-dist/ovld-0.3.9.tar.gz +0 -0
  136. ovld-0.4.6/one_two_three.py +0 -38
  137. ovld-0.4.6/outoftheway.toml +0 -42
  138. ovld-0.4.6/reddit.md +0 -56
  139. ovld-0.4.6/reddit.py +0 -24
  140. ovld-0.4.6/refun.py +0 -16
  141. ovld-0.4.6/reper.py +0 -6
  142. ovld-0.4.6/src/ovld/version.py +0 -1
  143. ovld-0.4.6/stuff.md +0 -43
  144. ovld-0.4.6/tb.py +0 -17
  145. ovld-0.4.6/tensor.py +0 -72
  146. ovld-0.4.6/tests/test_utils.py +0 -21
  147. ovld-0.4.6/todo.q +0 -33
  148. ovld-0.4.6/toot.py +0 -46
  149. ovld-0.4.6/typo.py +0 -36
  150. ovld-0.4.6/uv.lock +0 -675
  151. ovld-0.4.6/world.yaml +0 -16
  152. ovld-0.4.6/x.py +0 -51
  153. ovld-0.4.6/zaggo +0 -24
  154. {ovld-0.4.6 → ovld-0.5.0}/.gitignore +0 -0
  155. {ovld-0.4.6 → ovld-0.5.0}/.python-version +0 -0
  156. {ovld-0.4.6 → ovld-0.5.0}/.readthedocs.yaml +0 -0
  157. {ovld-0.4.6 → ovld-0.5.0}/LICENSE +0 -0
  158. {ovld-0.4.6 → ovld-0.5.0}/benchmarks/__init__.py +0 -0
  159. {ovld-0.4.6 → ovld-0.5.0}/docs/dependent.md +0 -0
  160. {ovld-0.4.6 → ovld-0.5.0}/docs/features.md +0 -0
  161. {ovld-0.4.6 → ovld-0.5.0}/src/ovld/abc.py +0 -0
  162. {ovld-0.4.6 → ovld-0.5.0}/src/ovld/py.typed +0 -0
  163. {ovld-0.4.6 → ovld-0.5.0}/tests/__init__.py +0 -0
  164. {ovld-0.4.6 → ovld-0.5.0}/tests/modules/gingerbread.py +0 -0
  165. {ovld-0.4.6 → ovld-0.5.0}/tests/test_abc.py +0 -0
  166. {ovld-0.4.6 → ovld-0.5.0}/tests/test_global.py +0 -0
  167. {ovld-0.4.6 → ovld-0.5.0}/tests/test_ovld/test_display.txt +0 -0
  168. {ovld-0.4.6 → ovld-0.5.0}/tests/test_ovld/test_display_more.txt +0 -0
  169. {ovld-0.4.6 → ovld-0.5.0}/tests/test_ovld/test_doc.txt +0 -0
  170. {ovld-0.4.6 → ovld-0.5.0}/tests/test_ovld/test_doc2.txt +0 -0
  171. {ovld-0.4.6 → ovld-0.5.0}/tests/test_ovld/test_method_doc.txt +0 -0
  172. {ovld-0.4.6 → ovld-0.5.0}/tests/test_typemap.py +0 -0
  173. {ovld-0.4.6 → ovld-0.5.0}/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
@@ -83,6 +83,6 @@ jobs:
83
83
  - name: Pin Python version
84
84
  run: uv python pin ${{ matrix.settings.python }}
85
85
  - name: Sync dependencies
86
- run: uv sync
86
+ run: uv sync --group benchmark
87
87
  - name: Run benchmarks
88
88
  run: uv run pytest benchmarks/
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: ovld
3
- Version: 0.4.6
3
+ Version: 0.5.0
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):** `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.
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 [keyword arguments](https://ovld.readthedocs.io/en/latest/usage/#keyword-arguments) (with some restrictions).
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(self, x: object, y: object):
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/) | [runtype](https://github.com/erezsh/runtype) | [fastcore](https://github.com/fastai/fastcore) | [sd](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.45|1.00|3.32|4.63|2.04|2.41|51.93|1.91|
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|46.74|7.32|
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|59.31|x|
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|28.39|1.66|
214
- |[calc](https://github.com/breuleux/ovld/tree/master/benchmarks/test_calc.py)|1.00|1.23|54.61|29.32|x|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|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|x|
217
- |[tweaknum](https://github.com/breuleux/ovld/tree/master/benchmarks/test_tweaknum.py)|1.00|2.09|x|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):** `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.
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 [keyword arguments](https://ovld.readthedocs.io/en/latest/usage/#keyword-arguments) (with some restrictions).
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(self, x: object, y: object):
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/) | [runtype](https://github.com/erezsh/runtype) | [fastcore](https://github.com/fastai/fastcore) | [sd](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.45|1.00|3.32|4.63|2.04|2.41|51.93|1.91|
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|46.74|7.32|
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|59.31|x|
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|28.39|1.66|
201
- |[calc](https://github.com/breuleux/ovld/tree/master/benchmarks/test_calc.py)|1.00|1.23|54.61|29.32|x|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|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|x|
204
- |[tweaknum](https://github.com/breuleux/ovld/tree/master/benchmarks/test_tweaknum.py)|1.00|2.09|x|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
- from fastcore.dispatch import typedispatch as fastcore_dispatch
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
- fastcore_dispatch,
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
- def make_test(fn):
80
- @pytest.mark.benchmark(group="add")
81
- def test(benchmark):
82
- result = benchmark(fn, A, B)
83
- assert result == C
84
-
85
- return test
86
-
87
-
88
- test_add_ovld = make_test(make_add(ovld_dispatch))
89
- test_add_plum = make_test(make_add(plum_dispatch))
90
- test_add_multimethod = make_test(make_add(multimethod_dispatch))
91
- test_add_multipledispatch = make_test(make_add(multipledispatch_dispatch))
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
- fastcore_dispatch,
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
- def make_test(fn):
98
- @pytest.mark.benchmark(group="ast")
99
- def test(benchmark, foobar):
100
- result = benchmark(fn, foobar[0])
101
- assert ast.dump(result, indent=2) == ast.dump(
102
- foobar[1], indent=2
103
- ).replace("bar", "foo")
104
-
105
- return test
106
-
107
-
108
- test_ast_ovld = make_test(make_transform(ovld_dispatch))
109
- test_ast_plum = make_test(make_transform(plum_dispatch))
110
- test_ast_multimethod = make_test(make_transform(multimethod_dispatch))
111
- test_ast_multipledispatch = make_test(make_transform(multipledispatch_dispatch))
112
- test_ast_runtype = make_test(make_transform(runtype_dispatch))
113
- test_ast_fastcore = make_test(make_transform(fastcore_dispatch))
114
- if sys.version_info >= (3, 11):
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
- def make_test(fn):
131
- @pytest.mark.benchmark(group="calc")
132
- def test(benchmark):
133
- result = benchmark(fn, expr)
134
- assert result == expected_result
135
-
136
- return test
137
-
138
-
139
- test_calc_ovld = make_test(make_calc(ovld_dispatch))
140
- test_calc_plum = make_test(make_calc(plum_dispatch))
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
- def make_test(fn):
50
- @pytest.mark.benchmark(group="fib")
51
- def test(benchmark):
52
- result = benchmark(fn, 8)
53
- assert result == 21
54
-
55
- return test
56
-
57
-
58
- test_fib_ovld = make_test(make_fib(ovld_dispatch))
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
- fastcore_dispatch,
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
- def make_test(cls):
142
- @pytest.mark.benchmark(group="multer")
143
- def test(benchmark):
144
- result = benchmark(cls(3), A)
145
- assert result == C
146
-
147
- return test
148
-
149
-
150
- test_multer_ovld = make_test(make_multer(ovld_dispatch))
151
- test_multer_recurse = make_test(OvldRecurseMulter)
152
- test_multer_plum = make_test(make_multer(plum_dispatch))
153
- test_multer_multimethod = make_test(make_multer(multimethod_dispatch))
154
- test_multer_singledispatch = make_test(SingleDispatchMulter)
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