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.
Files changed (154) hide show
  1. ovld-0.4.1/.envrc +2 -0
  2. ovld-0.4.1/.github/workflows/python-package.yml +85 -0
  3. {ovld-0.3.9 → ovld-0.4.1}/.gitignore +1 -0
  4. ovld-0.4.1/.readthedocs.yaml +9 -0
  5. ovld-0.4.1/PKG-INFO +216 -0
  6. ovld-0.4.1/README.md +203 -0
  7. ovld-0.4.1/add.py +58 -0
  8. ovld-0.4.1/anal.py +13 -0
  9. ovld-0.4.1/bench/requirements.txt +2 -0
  10. {ovld-0.3.9 → ovld-0.4.1}/bench.py +53 -34
  11. ovld-0.4.1/benchd.py +41 -0
  12. ovld-0.4.1/benchmarks/common.py +55 -0
  13. ovld-0.4.1/benchmarks/test_add.py +96 -0
  14. ovld-0.4.1/benchmarks/test_ast.py +116 -0
  15. ovld-0.4.1/benchmarks/test_calc.py +144 -0
  16. ovld-0.4.1/benchmarks/test_fib.py +62 -0
  17. ovld-0.4.1/benchmarks/test_multer.py +159 -0
  18. ovld-0.4.1/benchmarks/test_trivial.py +131 -0
  19. ovld-0.4.1/benchmarks/test_tweaknum.py +80 -0
  20. ovld-0.4.1/cloz.py +24 -0
  21. ovld-0.4.1/data.py +100 -0
  22. ovld-0.4.1/didi.py +26 -0
  23. ovld-0.4.1/docs/compare.md +59 -0
  24. ovld-0.4.1/docs/dependent.md +107 -0
  25. ovld-0.4.1/docs/features.md +107 -0
  26. ovld-0.4.1/docs/index.md +52 -0
  27. ovld-0.4.1/docs/types.md +140 -0
  28. ovld-0.4.1/docs/usage.md +229 -0
  29. ovld-0.4.1/explore.py +100 -0
  30. ovld-0.4.1/facto.py +51 -0
  31. ovld-0.4.1/gen_comparison_table.py +66 -0
  32. ovld-0.4.1/gentest.py +134 -0
  33. ovld-0.4.1/hello.py +6 -0
  34. ovld-0.4.1/hntest.py +65 -0
  35. ovld-0.4.1/klos.py +24 -0
  36. ovld-0.4.1/mkdocs.yml +12 -0
  37. ovld-0.4.1/old-dist/ovld-0.1.0-py3-none-any.whl +0 -0
  38. ovld-0.4.1/old-dist/ovld-0.1.0.tar.gz +0 -0
  39. ovld-0.4.1/old-dist/ovld-0.1.1-py3-none-any.whl +0 -0
  40. ovld-0.4.1/old-dist/ovld-0.1.1.tar.gz +0 -0
  41. ovld-0.4.1/old-dist/ovld-0.1.2-py3-none-any.whl +0 -0
  42. ovld-0.4.1/old-dist/ovld-0.1.2.tar.gz +0 -0
  43. ovld-0.4.1/old-dist/ovld-0.1.3-py3-none-any.whl +0 -0
  44. ovld-0.4.1/old-dist/ovld-0.1.3.tar.gz +0 -0
  45. ovld-0.4.1/old-dist/ovld-0.1.4-py3-none-any.whl +0 -0
  46. ovld-0.4.1/old-dist/ovld-0.1.4.tar.gz +0 -0
  47. ovld-0.4.1/old-dist/ovld-0.1.5-py3-none-any.whl +0 -0
  48. ovld-0.4.1/old-dist/ovld-0.1.5.tar.gz +0 -0
  49. ovld-0.4.1/old-dist/ovld-0.1.6-py3-none-any.whl +0 -0
  50. ovld-0.4.1/old-dist/ovld-0.1.6.tar.gz +0 -0
  51. ovld-0.4.1/old-dist/ovld-0.1.7-py3-none-any.whl +0 -0
  52. ovld-0.4.1/old-dist/ovld-0.1.7.tar.gz +0 -0
  53. ovld-0.4.1/old-dist/ovld-0.1.8-py3-none-any.whl +0 -0
  54. ovld-0.4.1/old-dist/ovld-0.1.8.tar.gz +0 -0
  55. ovld-0.4.1/old-dist/ovld-0.1.9-py3-none-any.whl +0 -0
  56. ovld-0.4.1/old-dist/ovld-0.1.9.tar.gz +0 -0
  57. ovld-0.4.1/old-dist/ovld-0.2.0-py3-none-any.whl +0 -0
  58. ovld-0.4.1/old-dist/ovld-0.2.0.tar.gz +0 -0
  59. ovld-0.4.1/old-dist/ovld-0.2.1-py3-none-any.whl +0 -0
  60. ovld-0.4.1/old-dist/ovld-0.2.1.tar.gz +0 -0
  61. ovld-0.4.1/old-dist/ovld-0.2.10-py3-none-any.whl +0 -0
  62. ovld-0.4.1/old-dist/ovld-0.2.10.tar.gz +0 -0
  63. ovld-0.4.1/old-dist/ovld-0.2.11-py3-none-any.whl +0 -0
  64. ovld-0.4.1/old-dist/ovld-0.2.11.tar.gz +0 -0
  65. ovld-0.4.1/old-dist/ovld-0.2.2-py3-none-any.whl +0 -0
  66. ovld-0.4.1/old-dist/ovld-0.2.2.tar.gz +0 -0
  67. ovld-0.4.1/old-dist/ovld-0.2.3-py3-none-any.whl +0 -0
  68. ovld-0.4.1/old-dist/ovld-0.2.3.tar.gz +0 -0
  69. ovld-0.4.1/old-dist/ovld-0.2.4-py3-none-any.whl +0 -0
  70. ovld-0.4.1/old-dist/ovld-0.2.4.tar.gz +0 -0
  71. ovld-0.4.1/old-dist/ovld-0.2.5-py3-none-any.whl +0 -0
  72. ovld-0.4.1/old-dist/ovld-0.2.5.tar.gz +0 -0
  73. ovld-0.4.1/old-dist/ovld-0.2.6-py3-none-any.whl +0 -0
  74. ovld-0.4.1/old-dist/ovld-0.2.6.tar.gz +0 -0
  75. ovld-0.4.1/old-dist/ovld-0.2.7-py3-none-any.whl +0 -0
  76. ovld-0.4.1/old-dist/ovld-0.2.7.tar.gz +0 -0
  77. ovld-0.4.1/old-dist/ovld-0.2.8-py3-none-any.whl +0 -0
  78. ovld-0.4.1/old-dist/ovld-0.2.8.tar.gz +0 -0
  79. ovld-0.4.1/old-dist/ovld-0.2.9-py3-none-any.whl +0 -0
  80. ovld-0.4.1/old-dist/ovld-0.2.9.tar.gz +0 -0
  81. ovld-0.4.1/old-dist/ovld-0.3.0-py3-none-any.whl +0 -0
  82. ovld-0.4.1/old-dist/ovld-0.3.0.tar.gz +0 -0
  83. ovld-0.4.1/old-dist/ovld-0.3.1-py3-none-any.whl +0 -0
  84. ovld-0.4.1/old-dist/ovld-0.3.1.tar.gz +0 -0
  85. ovld-0.4.1/old-dist/ovld-0.3.2-py3-none-any.whl +0 -0
  86. ovld-0.4.1/old-dist/ovld-0.3.2.tar.gz +0 -0
  87. ovld-0.4.1/old-dist/ovld-0.3.3-py3-none-any.whl +0 -0
  88. ovld-0.4.1/old-dist/ovld-0.3.3.tar.gz +0 -0
  89. ovld-0.4.1/old-dist/ovld-0.3.4-py3-none-any.whl +0 -0
  90. ovld-0.4.1/old-dist/ovld-0.3.4.tar.gz +0 -0
  91. ovld-0.4.1/old-dist/ovld-0.3.5-py3-none-any.whl +0 -0
  92. ovld-0.4.1/old-dist/ovld-0.3.5.tar.gz +0 -0
  93. ovld-0.4.1/old-dist/ovld-0.3.8-py3-none-any.whl +0 -0
  94. ovld-0.4.1/old-dist/ovld-0.3.8.tar.gz +0 -0
  95. ovld-0.4.1/old-dist/ovld-0.3.9-py3-none-any.whl +0 -0
  96. ovld-0.4.1/old-dist/ovld-0.3.9.tar.gz +0 -0
  97. ovld-0.4.1/one_two_three.py +38 -0
  98. ovld-0.3.9/pyproject.toml → ovld-0.4.1/outoftheway.toml +2 -2
  99. ovld-0.4.1/pyproject.toml +52 -0
  100. ovld-0.4.1/src/ovld/__init__.py +74 -0
  101. ovld-0.4.1/src/ovld/core.py +816 -0
  102. ovld-0.4.1/src/ovld/dependent.py +275 -0
  103. ovld-0.4.1/src/ovld/mro.py +203 -0
  104. ovld-0.4.1/src/ovld/recode.py +582 -0
  105. ovld-0.4.1/src/ovld/typemap.py +383 -0
  106. ovld-0.4.1/src/ovld/types.py +219 -0
  107. ovld-0.4.1/src/ovld/utils.py +68 -0
  108. ovld-0.4.1/src/ovld/version.py +1 -0
  109. ovld-0.4.1/stuff.md +43 -0
  110. ovld-0.4.1/tb.py +17 -0
  111. ovld-0.4.1/tensor.py +72 -0
  112. ovld-0.4.1/tests/__init__.py +0 -0
  113. ovld-0.4.1/tests/test_dependent.py +366 -0
  114. ovld-0.4.1/tests/test_examples.py +155 -0
  115. {ovld-0.3.9 → ovld-0.4.1}/tests/test_global.py +8 -8
  116. ovld-0.4.1/tests/test_mro.py +51 -0
  117. ovld-0.4.1/tests/test_ovld/test_display.txt +78 -0
  118. ovld-0.4.1/tests/test_ovld/test_doc.txt +13 -0
  119. ovld-0.4.1/tests/test_ovld/test_doc2.txt +10 -0
  120. ovld-0.4.1/tests/test_ovld/test_method_doc.txt +10 -0
  121. {ovld-0.3.9 → ovld-0.4.1}/tests/test_ovld.py +526 -235
  122. {ovld-0.3.9 → ovld-0.4.1}/tests/test_typemap.py +33 -25
  123. ovld-0.4.1/tests/test_types.py +246 -0
  124. ovld-0.4.1/tests/test_utils.py +21 -0
  125. ovld-0.4.1/todo.q +29 -0
  126. ovld-0.4.1/toot.py +46 -0
  127. ovld-0.4.1/typo.py +25 -0
  128. ovld-0.4.1/uv.lock +675 -0
  129. ovld-0.4.1/world.yaml +16 -0
  130. ovld-0.4.1/x.py +51 -0
  131. ovld-0.4.1/zaggo +24 -0
  132. ovld-0.3.9/.envrc +0 -2
  133. ovld-0.3.9/.github/workflows/python-package.yml +0 -61
  134. ovld-0.3.9/PKG-INFO +0 -305
  135. ovld-0.3.9/README.md +0 -295
  136. ovld-0.3.9/explore.py +0 -69
  137. ovld-0.3.9/requirements-dev.lock +0 -25
  138. ovld-0.3.9/requirements.lock +0 -12
  139. ovld-0.3.9/src/ovld/__init__.py +0 -48
  140. ovld-0.3.9/src/ovld/core.py +0 -1001
  141. ovld-0.3.9/src/ovld/mro.py +0 -171
  142. ovld-0.3.9/src/ovld/recode.py +0 -80
  143. ovld-0.3.9/src/ovld/utils.py +0 -169
  144. ovld-0.3.9/src/ovld/version.py +0 -1
  145. ovld-0.3.9/tests/test_utils.py +0 -174
  146. ovld-0.3.9/x.py +0 -11
  147. {ovld-0.3.9 → ovld-0.4.1}/.bsync-snap-20220324145902.852076 +0 -0
  148. {ovld-0.3.9 → ovld-0.4.1}/.python-version +0 -0
  149. {ovld-0.3.9 → ovld-0.4.1}/LICENSE +0 -0
  150. {ovld-0.3.9/tests → ovld-0.4.1/benchmarks}/__init__.py +0 -0
  151. {ovld-0.3.9 → ovld-0.4.1}/doo.py +0 -0
  152. {ovld-0.3.9 → ovld-0.4.1}/edges.py +0 -0
  153. {ovld-0.3.9 → ovld-0.4.1}/nxt.py +0 -0
  154. {ovld-0.3.9 → ovld-0.4.1}/tests/modules/gingerbread.py +0 -0
ovld-0.4.1/.envrc ADDED
@@ -0,0 +1,2 @@
1
+ . .venv/bin/activate
2
+ # export PYTHONBREAKPOINT=breakword.breakpoint
@@ -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/
@@ -3,3 +3,4 @@ __pycache__
3
3
  dist/
4
4
  .pytest_cache
5
5
  .coverage
6
+ .benchmarks
@@ -0,0 +1,9 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-22.04
5
+ tools:
6
+ python: "3.12"
7
+
8
+ mkdocs:
9
+ configuration: mkdocs.yml
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
@@ -0,0 +1,13 @@
1
+ import inspect
2
+
3
+
4
+ def f(x, y, /, z, *, quack):
5
+ pass
6
+
7
+
8
+ print(inspect.getfullargspec(f))
9
+ print(sig := inspect.signature(f))
10
+
11
+ pa = sig.parameters
12
+
13
+ breakpoint()
@@ -0,0 +1,2 @@
1
+ multimethod
2
+ multipledispatch