ovld 0.4.5__py3-none-any.whl → 0.5.0__py3-none-any.whl
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/__init__.py +23 -1
- ovld/codegen.py +303 -0
- ovld/core.py +62 -349
- ovld/dependent.py +24 -72
- ovld/medley.py +408 -0
- ovld/mro.py +6 -3
- ovld/py.typed +0 -0
- ovld/recode.py +99 -165
- ovld/signatures.py +275 -0
- ovld/typemap.py +40 -38
- ovld/types.py +47 -44
- ovld/utils.py +55 -18
- ovld/version.py +1 -1
- {ovld-0.4.5.dist-info → ovld-0.5.0.dist-info}/METADATA +62 -16
- ovld-0.5.0.dist-info/RECORD +18 -0
- {ovld-0.4.5.dist-info → ovld-0.5.0.dist-info}/WHEEL +1 -1
- ovld-0.4.5.dist-info/RECORD +0 -14
- {ovld-0.4.5.dist-info → ovld-0.5.0.dist-info}/licenses/LICENSE +0 -0
ovld/typemap.py
CHANGED
@@ -6,7 +6,7 @@ from types import CodeType
|
|
6
6
|
|
7
7
|
from .mro import sort_types
|
8
8
|
from .recode import generate_dependent_dispatch
|
9
|
-
from .utils import MISSING, subtler_type
|
9
|
+
from .utils import MISSING, CodegenInProgress, subtler_type
|
10
10
|
|
11
11
|
|
12
12
|
class TypeMap(dict):
|
@@ -58,6 +58,7 @@ class TypeMap(dict):
|
|
58
58
|
@dataclass
|
59
59
|
class Candidate:
|
60
60
|
handler: object
|
61
|
+
base_handler: object
|
61
62
|
priority: float
|
62
63
|
specificity: tuple
|
63
64
|
tiebreak: int
|
@@ -69,9 +70,7 @@ class Candidate:
|
|
69
70
|
if self.priority > other.priority:
|
70
71
|
return True
|
71
72
|
elif self.specificity != other.specificity:
|
72
|
-
return all(
|
73
|
-
s1 >= s2 for s1, s2 in zip(self.specificity, other.specificity)
|
74
|
-
)
|
73
|
+
return all(s1 >= s2 for s1, s2 in zip(self.specificity, other.specificity))
|
75
74
|
else:
|
76
75
|
return self.tiebreak > other.tiebreak
|
77
76
|
|
@@ -95,7 +94,7 @@ class MultiTypeMap(dict):
|
|
95
94
|
specific than [object, int] (which means there is an ambiguity).
|
96
95
|
"""
|
97
96
|
|
98
|
-
def __init__(self, name="_ovld", key_error=KeyError):
|
97
|
+
def __init__(self, name="_ovld", key_error=KeyError, ovld=None):
|
99
98
|
self.maps = {}
|
100
99
|
self.priorities = {}
|
101
100
|
self.tiebreaks = {}
|
@@ -107,8 +106,10 @@ class MultiTypeMap(dict):
|
|
107
106
|
self.dispatch_id = count()
|
108
107
|
self.all = {}
|
109
108
|
self.errors = {}
|
109
|
+
self.ovld = ovld
|
110
|
+
self.in_progress = set()
|
110
111
|
|
111
|
-
def mro(self, obj_t_tup):
|
112
|
+
def mro(self, obj_t_tup, specialize=True):
|
112
113
|
specificities = {}
|
113
114
|
candidates = None
|
114
115
|
nargs = len([t for t in obj_t_tup if not isinstance(t, tuple)])
|
@@ -126,9 +127,7 @@ class MultiTypeMap(dict):
|
|
126
127
|
results = {
|
127
128
|
handler: spc
|
128
129
|
for (handler, sig), spc in results.items()
|
129
|
-
if sig.req_pos
|
130
|
-
<= nargs
|
131
|
-
<= (math.inf if sig.vararg else sig.max_pos)
|
130
|
+
if sig.req_pos <= nargs <= (math.inf if sig.vararg else sig.max_pos)
|
132
131
|
and not (sig.req_names - names)
|
133
132
|
}
|
134
133
|
|
@@ -152,14 +151,26 @@ class MultiTypeMap(dict):
|
|
152
151
|
for c in candidates:
|
153
152
|
specificities.setdefault(c, []).append(results[c])
|
154
153
|
|
154
|
+
def get_handler(func):
|
155
|
+
if specialize and (specializer := getattr(func, "specializer", False)):
|
156
|
+
key = (func, obj_t_tup)
|
157
|
+
if key in self.in_progress:
|
158
|
+
raise CodegenInProgress()
|
159
|
+
self.in_progress.add(key)
|
160
|
+
return specializer(self, func, obj_t_tup)
|
161
|
+
else:
|
162
|
+
return func
|
163
|
+
|
155
164
|
candidates = [
|
156
165
|
Candidate(
|
157
|
-
handler=
|
166
|
+
handler=h,
|
167
|
+
base_handler=c,
|
158
168
|
priority=self.priorities.get(c, 0),
|
159
169
|
specificity=tuple(specificities[c]),
|
160
170
|
tiebreak=self.tiebreaks.get(c, 0),
|
161
171
|
)
|
162
172
|
for c in candidates
|
173
|
+
if (h := get_handler(c))
|
163
174
|
]
|
164
175
|
|
165
176
|
# The sort ensures that if candidate A dominates candidate B, A will
|
@@ -170,9 +181,7 @@ class MultiTypeMap(dict):
|
|
170
181
|
|
171
182
|
candidates.sort(key=Candidate.sort_key, reverse=True)
|
172
183
|
|
173
|
-
self.all[obj_t_tup] = {
|
174
|
-
getattr(c.handler, "__code__", None) for c in candidates
|
175
|
-
}
|
184
|
+
self.all[obj_t_tup] = {getattr(c.handler, "__code__", None) for c in candidates}
|
176
185
|
|
177
186
|
processed = set()
|
178
187
|
|
@@ -239,7 +248,7 @@ class MultiTypeMap(dict):
|
|
239
248
|
width = 6
|
240
249
|
print(f"{prio:{width}} \033[1m{h.__name__}\033[0m")
|
241
250
|
co = h.__code__
|
242
|
-
print(f"{'':{width-2}} @ {co.co_filename}:{co.co_firstlineno}")
|
251
|
+
print(f"{'':{width - 2}} @ {co.co_filename}:{co.co_firstlineno}")
|
243
252
|
|
244
253
|
def display_resolution(self, *args, **kwargs):
|
245
254
|
from .dependent import is_dependent
|
@@ -263,9 +272,7 @@ class MultiTypeMap(dict):
|
|
263
272
|
for grp in self.mro(tuple(argt)):
|
264
273
|
grp.sort(key=lambda x: x.handler.__name__)
|
265
274
|
match = [
|
266
|
-
dependent_match(
|
267
|
-
self.type_tuples[c.handler], [*args, *kwargs.items()]
|
268
|
-
)
|
275
|
+
dependent_match(self.type_tuples[c.base_handler], [*args, *kwargs.items()])
|
269
276
|
for c in grp
|
270
277
|
]
|
271
278
|
ambiguous = len([m for m in match if m]) > 1
|
@@ -292,22 +299,17 @@ class MultiTypeMap(dict):
|
|
292
299
|
width = 2 * len(args) + 6
|
293
300
|
print(f"{color}{bullet} {lvl:{width}} {handler.__name__}")
|
294
301
|
co = handler.__code__
|
295
|
-
print(
|
296
|
-
f" {'':{width-1}}@ {co.co_filename}:{co.co_firstlineno}\033[0m"
|
297
|
-
)
|
302
|
+
print(f" {'':{width - 1}}@ {co.co_filename}:{co.co_firstlineno}\033[0m")
|
298
303
|
if ambiguous:
|
299
|
-
message +=
|
304
|
+
message += (
|
305
|
+
" There is ambiguity between multiple matching methods, marked '=='."
|
306
|
+
)
|
300
307
|
finished = True
|
301
308
|
print("Resolution:", message)
|
302
309
|
|
303
|
-
def wrap_dependent(self, tup,
|
304
|
-
|
305
|
-
|
306
|
-
slf = (
|
307
|
-
"self, "
|
308
|
-
if inspect.getfullargspec(handlers[0]).args[0] == "self"
|
309
|
-
else ""
|
310
|
-
)
|
310
|
+
def wrap_dependent(self, tup, group, next_call):
|
311
|
+
htup = [(c.handler, self.type_tuples[c.base_handler]) for c in group]
|
312
|
+
slf = "self, " if inspect.getfullargspec(group[0].handler).args[0] == "self" else ""
|
311
313
|
return generate_dependent_dispatch(
|
312
314
|
tup,
|
313
315
|
htup,
|
@@ -326,11 +328,9 @@ class MultiTypeMap(dict):
|
|
326
328
|
funcs = []
|
327
329
|
for group in reversed(results):
|
328
330
|
handlers = [c.handler for c in group]
|
329
|
-
dependent = any(self.dependent[c.
|
331
|
+
dependent = any(self.dependent[c.base_handler] for c in group)
|
330
332
|
if dependent:
|
331
|
-
nxt = self.wrap_dependent(
|
332
|
-
obj_t_tup, handlers, group, funcs[-1] if funcs else None
|
333
|
-
)
|
333
|
+
nxt = self.wrap_dependent(obj_t_tup, group, funcs[-1] if funcs else None)
|
334
334
|
elif len(group) != 1:
|
335
335
|
nxt = None
|
336
336
|
else:
|
@@ -342,11 +342,7 @@ class MultiTypeMap(dict):
|
|
342
342
|
|
343
343
|
parents = []
|
344
344
|
for group, (func, codes) in zip(results, funcs):
|
345
|
-
tups = (
|
346
|
-
[obj_t_tup]
|
347
|
-
if not parents
|
348
|
-
else [(parent, *obj_t_tup) for parent in parents]
|
349
|
-
)
|
345
|
+
tups = [obj_t_tup] if not parents else [(parent, *obj_t_tup) for parent in parents]
|
350
346
|
if func is None:
|
351
347
|
for tup in tups:
|
352
348
|
self.errors[tup] = self.key_error(obj_t_tup, group)
|
@@ -386,3 +382,9 @@ class MultiTypeMap(dict):
|
|
386
382
|
raise self.errors[obj_t_tup]
|
387
383
|
else:
|
388
384
|
return self[obj_t_tup]
|
385
|
+
|
386
|
+
def __call__(self, *obj_t_tup, after=None):
|
387
|
+
if after:
|
388
|
+
return self[(after, *obj_t_tup)]
|
389
|
+
else:
|
390
|
+
return self[obj_t_tup]
|
ovld/types.py
CHANGED
@@ -1,18 +1,41 @@
|
|
1
|
+
import importlib
|
1
2
|
import inspect
|
2
3
|
import sys
|
3
4
|
import typing
|
4
5
|
from dataclasses import dataclass
|
5
6
|
from functools import partial
|
6
|
-
from typing import Protocol, runtime_checkable
|
7
|
+
from typing import Protocol, get_args, runtime_checkable
|
7
8
|
|
9
|
+
from .codegen import Code
|
8
10
|
from .mro import Order, TypeRelationship, subclasscheck, typeorder
|
11
|
+
from .recode import generate_checking_code
|
9
12
|
from .typemap import TypeMap
|
10
|
-
from .utils import UsageError, clsstring
|
13
|
+
from .utils import UnionType, UnionTypes, UsageError, clsstring
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
|
16
|
+
def get_args(tp):
|
17
|
+
args = getattr(tp, "__args__", None)
|
18
|
+
if not isinstance(args, tuple):
|
19
|
+
args = ()
|
20
|
+
return args
|
21
|
+
|
22
|
+
|
23
|
+
def eval_annotation(t, ctx, locals, catch=False):
|
24
|
+
try:
|
25
|
+
if isinstance(t, str):
|
26
|
+
if hasattr(ctx, "__globals__"):
|
27
|
+
glb = ctx.__globals__
|
28
|
+
elif hasattr(ctx, "__module__"): # pragma: no cover
|
29
|
+
glb = vars(importlib.import_module(ctx.__module__))
|
30
|
+
else: # pragma: no cover
|
31
|
+
glb = {}
|
32
|
+
return eval(t, glb, locals)
|
33
|
+
else:
|
34
|
+
return t
|
35
|
+
except Exception: # pragma: no cover
|
36
|
+
if catch:
|
37
|
+
return None
|
38
|
+
raise
|
16
39
|
|
17
40
|
|
18
41
|
class TypeNormalizer:
|
@@ -29,7 +52,7 @@ class TypeNormalizer:
|
|
29
52
|
from .dependent import DependentType
|
30
53
|
|
31
54
|
if isinstance(t, str):
|
32
|
-
t =
|
55
|
+
t = eval_annotation(t, fn, {})
|
33
56
|
|
34
57
|
if t is type:
|
35
58
|
t = type[object]
|
@@ -37,6 +60,8 @@ class TypeNormalizer:
|
|
37
60
|
t = object
|
38
61
|
elif t is inspect._empty:
|
39
62
|
t = object
|
63
|
+
elif t in UnionTypes:
|
64
|
+
return type[t]
|
40
65
|
elif isinstance(t, typing._AnnotatedAlias):
|
41
66
|
t = t.__origin__
|
42
67
|
|
@@ -82,7 +107,7 @@ class MetaMC(type):
|
|
82
107
|
return super().__new__(T, name, (), {"_handler": handler})
|
83
108
|
|
84
109
|
def __init__(cls, name, handler):
|
85
|
-
cls.__args__ =
|
110
|
+
cls.__args__ = get_args(handler)
|
86
111
|
|
87
112
|
def codegen(cls):
|
88
113
|
return cls._handler.codegen()
|
@@ -212,11 +237,15 @@ def _getcls(ref):
|
|
212
237
|
|
213
238
|
|
214
239
|
class AllMC(type):
|
240
|
+
__bound__ = False
|
241
|
+
|
215
242
|
def __type_order__(self, other):
|
216
243
|
return Order.MORE
|
217
244
|
|
218
245
|
def __is_subtype__(self, other):
|
219
|
-
|
246
|
+
if not self.__bound__:
|
247
|
+
return True
|
248
|
+
return subclasscheck(self.__bound__, other) or subclasscheck(other, self.__bound__)
|
220
249
|
|
221
250
|
def __is_supertype__(self, other):
|
222
251
|
return False
|
@@ -227,6 +256,10 @@ class AllMC(type):
|
|
227
256
|
def __isinstance__(self, other): # pragma: no cover
|
228
257
|
return False
|
229
258
|
|
259
|
+
def __getitem__(self, item):
|
260
|
+
name = getattr(item, "__name__", str(item))
|
261
|
+
return type(f"All[{name}]", (self,), {"__bound__": item})
|
262
|
+
|
230
263
|
|
231
264
|
class All(metaclass=AllMC):
|
232
265
|
"""All is the empty/void/bottom type -- it acts as a subtype of all types.
|
@@ -304,11 +337,7 @@ def Exactly(cls, base_cls):
|
|
304
337
|
@parametrized_class_check
|
305
338
|
def StrictSubclass(cls, base_cls):
|
306
339
|
"""Match subclasses but not the base class."""
|
307
|
-
return (
|
308
|
-
isinstance(cls, type)
|
309
|
-
and issubclass(cls, base_cls)
|
310
|
-
and cls is not base_cls
|
311
|
-
)
|
340
|
+
return isinstance(cls, type) and issubclass(cls, base_cls) and cls is not base_cls
|
312
341
|
|
313
342
|
|
314
343
|
@parametrized_class_check
|
@@ -317,20 +346,13 @@ class Union:
|
|
317
346
|
self.__args__ = self.types = types
|
318
347
|
|
319
348
|
def codegen(self):
|
320
|
-
|
321
|
-
|
322
|
-
template = " or ".join("{}" for t in self.types)
|
323
|
-
return combine(
|
324
|
-
template, [generate_checking_code(t) for t in self.types]
|
325
|
-
)
|
349
|
+
return Code("($[ or ]checks)", checks=[generate_checking_code(t) for t in self.types])
|
326
350
|
|
327
351
|
def __type_order__(self, other):
|
328
352
|
if other is Union:
|
329
353
|
return Order.LESS
|
330
354
|
classes = self.types
|
331
|
-
compare = [
|
332
|
-
x for t in classes if (x := typeorder(t, other)) is not Order.NONE
|
333
|
-
]
|
355
|
+
compare = [x for t in classes if (x := typeorder(t, other)) is not Order.NONE]
|
334
356
|
if not compare:
|
335
357
|
return Order.NONE
|
336
358
|
elif any(x is Order.MORE or x is Order.SAME for x in compare):
|
@@ -368,20 +390,13 @@ class Intersection:
|
|
368
390
|
self.__args__ = self.types = types
|
369
391
|
|
370
392
|
def codegen(self):
|
371
|
-
|
372
|
-
|
373
|
-
template = " and ".join("{}" for t in self.types)
|
374
|
-
return combine(
|
375
|
-
template, [generate_checking_code(t) for t in self.types]
|
376
|
-
)
|
393
|
+
return Code("($[ and ]checks)", checks=[generate_checking_code(t) for t in self.types])
|
377
394
|
|
378
395
|
def __type_order__(self, other):
|
379
396
|
if other is Intersection:
|
380
397
|
return Order.LESS
|
381
398
|
classes = self.types
|
382
|
-
compare = [
|
383
|
-
x for t in classes if (x := typeorder(t, other)) is not Order.NONE
|
384
|
-
]
|
399
|
+
compare = [x for t in classes if (x := typeorder(t, other)) is not Order.NONE]
|
385
400
|
if not compare:
|
386
401
|
return Order.NONE
|
387
402
|
elif any(x is Order.LESS or x is Order.SAME for x in compare):
|
@@ -427,15 +442,3 @@ class Dataclass(Protocol):
|
|
427
442
|
return hasattr(subclass, "__dataclass_fields__") and hasattr(
|
428
443
|
subclass, "__dataclass_params__"
|
429
444
|
)
|
430
|
-
|
431
|
-
|
432
|
-
__all__ = [
|
433
|
-
"Dataclass",
|
434
|
-
"Deferred",
|
435
|
-
"Exactly",
|
436
|
-
"HasMethod",
|
437
|
-
"Intersection",
|
438
|
-
"StrictSubclass",
|
439
|
-
"class_check",
|
440
|
-
"parametrized_class_check",
|
441
|
-
]
|
ovld/utils.py
CHANGED
@@ -5,6 +5,15 @@ import re
|
|
5
5
|
import typing
|
6
6
|
from itertools import count
|
7
7
|
|
8
|
+
try:
|
9
|
+
from types import UnionType
|
10
|
+
|
11
|
+
UnionTypes = (type(typing.Union[int, str]), UnionType)
|
12
|
+
|
13
|
+
except ImportError: # pragma: no cover
|
14
|
+
UnionType = None
|
15
|
+
UnionTypes = (type(typing.Union[int, str]),)
|
16
|
+
|
8
17
|
|
9
18
|
class Named:
|
10
19
|
"""A named object.
|
@@ -48,13 +57,20 @@ def keyword_decorator(deco):
|
|
48
57
|
return new_deco
|
49
58
|
|
50
59
|
|
60
|
+
class CodegenInProgress(Exception):
|
61
|
+
pass
|
62
|
+
|
63
|
+
|
51
64
|
class UsageError(Exception):
|
52
65
|
pass
|
53
66
|
|
54
67
|
|
55
|
-
class
|
56
|
-
def __init__(self, message):
|
57
|
-
self.
|
68
|
+
class SpecialForm:
|
69
|
+
def __init__(self, name, message=None):
|
70
|
+
self.__name = name
|
71
|
+
self.__message = (
|
72
|
+
message or f"{name}() can only be used from inside an @ovld-registered function."
|
73
|
+
)
|
58
74
|
|
59
75
|
def __call__(self, *args, **kwargs):
|
60
76
|
raise UsageError(self.__message)
|
@@ -62,6 +78,11 @@ class Unusable:
|
|
62
78
|
def __getattr__(self, attr):
|
63
79
|
raise UsageError(self.__message)
|
64
80
|
|
81
|
+
def __str__(self):
|
82
|
+
return f"<SpecialForm {self.__name}>"
|
83
|
+
|
84
|
+
__repr__ = __str__
|
85
|
+
|
65
86
|
|
66
87
|
class GenericAliasMC(type):
|
67
88
|
def __instancecheck__(cls, obj):
|
@@ -87,9 +108,23 @@ def clsstring(cls):
|
|
87
108
|
return r
|
88
109
|
|
89
110
|
|
111
|
+
def typemap_entry_string(cls):
|
112
|
+
if isinstance(cls, tuple):
|
113
|
+
key, typ = cls
|
114
|
+
return f"{key}: {clsstring(typ)}"
|
115
|
+
else:
|
116
|
+
return clsstring(cls)
|
117
|
+
|
118
|
+
|
119
|
+
def sigstring(types):
|
120
|
+
return ", ".join(map(typemap_entry_string, types))
|
121
|
+
|
122
|
+
|
90
123
|
def subtler_type(obj):
|
91
124
|
if isinstance(obj, GenericAlias):
|
92
125
|
return type[obj]
|
126
|
+
elif isinstance(obj, UnionTypes):
|
127
|
+
return type[obj]
|
93
128
|
elif obj is typing.Any:
|
94
129
|
return type[object]
|
95
130
|
elif isinstance(obj, type):
|
@@ -99,7 +134,7 @@ def subtler_type(obj):
|
|
99
134
|
|
100
135
|
|
101
136
|
class NameDatabase:
|
102
|
-
def __init__(self, default_name):
|
137
|
+
def __init__(self, default_name="TMP"):
|
103
138
|
self.default_name = default_name
|
104
139
|
self.count = count()
|
105
140
|
self.variables = {}
|
@@ -109,32 +144,34 @@ class NameDatabase:
|
|
109
144
|
def register(self, name):
|
110
145
|
self.registered.add(name)
|
111
146
|
|
112
|
-
def gensym(self, desired_name):
|
147
|
+
def gensym(self, desired_name, value=None):
|
113
148
|
i = 1
|
114
149
|
name = desired_name
|
115
|
-
while name in self.registered
|
150
|
+
while name in self.registered or (
|
151
|
+
name in __builtins__ and __builtins__[name] != value
|
152
|
+
):
|
116
153
|
name = f"{desired_name}{i}"
|
117
154
|
i += 1
|
118
155
|
self.registered.add(name)
|
119
156
|
return name
|
120
157
|
|
121
|
-
def
|
158
|
+
def get(self, value, suggested_name=None):
|
122
159
|
if isinstance(value, (int, float, str)):
|
123
160
|
return repr(value)
|
124
161
|
if id(value) in self.names:
|
125
162
|
return self.names[id(value)]
|
126
|
-
|
127
|
-
if
|
128
|
-
|
129
|
-
|
163
|
+
dflt = suggested_name or self.default_name
|
164
|
+
if (
|
165
|
+
isinstance(value, GenericAlias) and typing.get_origin(value) is type
|
166
|
+
): # pragma: no cover
|
167
|
+
name = "t_" + getattr(typing.get_args(value)[0], "__name__", dflt)
|
168
|
+
else:
|
169
|
+
name = getattr(value, "__name__", dflt)
|
170
|
+
if not re.match(string=name, pattern=r"^[a-zA-Z_][a-zA-Z0-9_]*$"):
|
171
|
+
name = dflt
|
172
|
+
name = self.gensym(name, value)
|
130
173
|
self.variables[name] = value
|
131
174
|
self.names[id(value)] = name
|
132
175
|
return name
|
133
176
|
|
134
|
-
|
135
|
-
__all__ = [
|
136
|
-
"BOOTSTRAP",
|
137
|
-
"MISSING",
|
138
|
-
"Named",
|
139
|
-
"keyword_decorator",
|
140
|
-
]
|
177
|
+
__getitem__ = get
|
ovld/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
version = "0.
|
1
|
+
version = "0.5.0"
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: ovld
|
3
|
-
Version: 0.
|
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):**
|
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|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
ovld/__init__.py,sha256=JuCM8Sj65gobV0KYyLr95cSI23Pi6RYZ7X3_F3fdsSw,1821
|
2
|
+
ovld/abc.py,sha256=4qpZyYwI8dWgY1Oiv5FhdKg2uzNcyWxIpGmGJVcjXrs,1177
|
3
|
+
ovld/codegen.py,sha256=0ZZdXw-nn9t52BX93GX6zwTeBB_wCmc5OxSk37sBrVU,9112
|
4
|
+
ovld/core.py,sha256=uAnzuxa3hZjZSbCs3FW_0uR9qQlY-HZhgKpDTyvp59Y,17346
|
5
|
+
ovld/dependent.py,sha256=h3j4oQYTQfGqMzggWlLV6TpojX_GtYRFWAO0GcMB0Zs,9085
|
6
|
+
ovld/medley.py,sha256=Gin0EYeuQ7qZdDKNFFIwBY4o8W-VT5pl2bP8GCSopU4,12136
|
7
|
+
ovld/mro.py,sha256=nqe9jHvsaLu-CfXoP2f7tHg5_8IkcESWMnBmvr4XGIo,4605
|
8
|
+
ovld/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
ovld/recode.py,sha256=AoNgJw75RAlfSBeFL5-UeB_T7wN_Xqzuahr9VhqC_yI,16126
|
10
|
+
ovld/signatures.py,sha256=Q8JucSOun0ESGx14aWtHtBzLEiM6FxY5HP3imyqXoDo,8984
|
11
|
+
ovld/typemap.py,sha256=5Pro1Ee60fH4L7NW7k5nbN5EfDygA0LFHcI6o3mCagI,13596
|
12
|
+
ovld/types.py,sha256=0hkhAR5_5793NABdrM-fP1dSJBhYof85FILKqVP2YMg,13392
|
13
|
+
ovld/utils.py,sha256=q2zFrK3OifhDu4SsVyLP4WKIp8FDDcJmLwMEUoWHS6c,4227
|
14
|
+
ovld/version.py,sha256=-beU6vQRolzltfD0fQR6KSzR5yUgbfwYBlKbvzktqbg,18
|
15
|
+
ovld-0.5.0.dist-info/METADATA,sha256=HzSeUKkJrJRUbPJZqw1XZpkW58EKhgl6GOi32CaPPcw,9276
|
16
|
+
ovld-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
17
|
+
ovld-0.5.0.dist-info/licenses/LICENSE,sha256=cSwNTIzd1cbI89xt3PeZZYJP2y3j8Zus4bXgo4svpX8,1066
|
18
|
+
ovld-0.5.0.dist-info/RECORD,,
|
ovld-0.4.5.dist-info/RECORD
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
ovld/__init__.py,sha256=IVzs4_9skMa_UGZsGT7Ra_dIxj7KKNlV77XxlqcS6ow,1446
|
2
|
-
ovld/abc.py,sha256=4qpZyYwI8dWgY1Oiv5FhdKg2uzNcyWxIpGmGJVcjXrs,1177
|
3
|
-
ovld/core.py,sha256=MtRcJNhMwsix2Y7zP1fQS-taCy_t0bqkjHQdLF8WE8E,25719
|
4
|
-
ovld/dependent.py,sha256=nyrkkmgxGjcHnZjKqbk7_g4_D91SYfYTPIE32LOvC9o,10184
|
5
|
-
ovld/mro.py,sha256=0cJK_v-POCiuwjluwf8fWeQ3G-2chEUv3KYe57GC61Q,4552
|
6
|
-
ovld/recode.py,sha256=bwsdM-TPpSQEmJhv8G30yDLrF8_I5wyTXpITMCC1W9k,17961
|
7
|
-
ovld/typemap.py,sha256=PH5dy8ddVCcqj2TkQbgsM7fmCdHsJT9WGXFPn4JZsCA,13131
|
8
|
-
ovld/types.py,sha256=EZNv8pThbo47KBULYM3R0R3sM2C5LC4DWraXZImkiNs,12877
|
9
|
-
ovld/utils.py,sha256=jrrG_BqjI3W7x285nLuYiy5ui9LbNltblFVTlOWHYiU,3123
|
10
|
-
ovld/version.py,sha256=GzDw9yI90LiIOYlw5gZBih8gPe3URgiM0vAFi2-I3ro,18
|
11
|
-
ovld-0.4.5.dist-info/METADATA,sha256=AM7DiTgyU53Z0A7s8MIJJ3t1rgk1pxf3uqPU20kVO6o,7833
|
12
|
-
ovld-0.4.5.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
13
|
-
ovld-0.4.5.dist-info/licenses/LICENSE,sha256=cSwNTIzd1cbI89xt3PeZZYJP2y3j8Zus4bXgo4svpX8,1066
|
14
|
-
ovld-0.4.5.dist-info/RECORD,,
|
File without changes
|