ovld 0.4.6__py3-none-any.whl → 0.5.1__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 +306 -0
- ovld/core.py +76 -361
- ovld/dependent.py +22 -71
- ovld/medley.py +416 -0
- ovld/mro.py +1 -3
- ovld/recode.py +96 -165
- ovld/signatures.py +275 -0
- ovld/typemap.py +40 -38
- ovld/types.py +36 -37
- ovld/utils.py +48 -18
- ovld/version.py +1 -1
- {ovld-0.4.6.dist-info → ovld-0.5.1.dist-info}/METADATA +62 -16
- ovld-0.5.1.dist-info/RECORD +18 -0
- {ovld-0.4.6.dist-info → ovld-0.5.1.dist-info}/WHEEL +1 -1
- ovld-0.4.6.dist-info/RECORD +0 -15
- {ovld-0.4.6.dist-info → ovld-0.5.1.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,3 +1,4 @@
|
|
1
|
+
import importlib
|
1
2
|
import inspect
|
2
3
|
import sys
|
3
4
|
import typing
|
@@ -5,7 +6,9 @@ from dataclasses import dataclass
|
|
5
6
|
from functools import partial
|
6
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
13
|
from .utils import UnionType, UnionTypes, UsageError, clsstring
|
11
14
|
|
@@ -17,6 +20,24 @@ def get_args(tp):
|
|
17
20
|
return args
|
18
21
|
|
19
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
|
39
|
+
|
40
|
+
|
20
41
|
class TypeNormalizer:
|
21
42
|
def __init__(self, generic_handlers=None):
|
22
43
|
self.generic_handlers = generic_handlers or TypeMap()
|
@@ -31,7 +52,7 @@ class TypeNormalizer:
|
|
31
52
|
from .dependent import DependentType
|
32
53
|
|
33
54
|
if isinstance(t, str):
|
34
|
-
t =
|
55
|
+
t = eval_annotation(t, fn, {})
|
35
56
|
|
36
57
|
if t is type:
|
37
58
|
t = type[object]
|
@@ -216,11 +237,15 @@ def _getcls(ref):
|
|
216
237
|
|
217
238
|
|
218
239
|
class AllMC(type):
|
240
|
+
__bound__ = False
|
241
|
+
|
219
242
|
def __type_order__(self, other):
|
220
243
|
return Order.MORE
|
221
244
|
|
222
245
|
def __is_subtype__(self, other):
|
223
|
-
|
246
|
+
if not self.__bound__:
|
247
|
+
return True
|
248
|
+
return subclasscheck(self.__bound__, other) or subclasscheck(other, self.__bound__)
|
224
249
|
|
225
250
|
def __is_supertype__(self, other):
|
226
251
|
return False
|
@@ -231,6 +256,10 @@ class AllMC(type):
|
|
231
256
|
def __isinstance__(self, other): # pragma: no cover
|
232
257
|
return False
|
233
258
|
|
259
|
+
def __getitem__(self, item):
|
260
|
+
name = getattr(item, "__name__", str(item))
|
261
|
+
return type(f"All[{name}]", (self,), {"__bound__": item})
|
262
|
+
|
234
263
|
|
235
264
|
class All(metaclass=AllMC):
|
236
265
|
"""All is the empty/void/bottom type -- it acts as a subtype of all types.
|
@@ -308,11 +337,7 @@ def Exactly(cls, base_cls):
|
|
308
337
|
@parametrized_class_check
|
309
338
|
def StrictSubclass(cls, base_cls):
|
310
339
|
"""Match subclasses but not the base class."""
|
311
|
-
return (
|
312
|
-
isinstance(cls, type)
|
313
|
-
and issubclass(cls, base_cls)
|
314
|
-
and cls is not base_cls
|
315
|
-
)
|
340
|
+
return isinstance(cls, type) and issubclass(cls, base_cls) and cls is not base_cls
|
316
341
|
|
317
342
|
|
318
343
|
@parametrized_class_check
|
@@ -321,20 +346,13 @@ class Union:
|
|
321
346
|
self.__args__ = self.types = types
|
322
347
|
|
323
348
|
def codegen(self):
|
324
|
-
|
325
|
-
|
326
|
-
template = " or ".join("{}" for t in self.types)
|
327
|
-
return combine(
|
328
|
-
template, [generate_checking_code(t) for t in self.types]
|
329
|
-
)
|
349
|
+
return Code("($[ or ]checks)", checks=[generate_checking_code(t) for t in self.types])
|
330
350
|
|
331
351
|
def __type_order__(self, other):
|
332
352
|
if other is Union:
|
333
353
|
return Order.LESS
|
334
354
|
classes = self.types
|
335
|
-
compare = [
|
336
|
-
x for t in classes if (x := typeorder(t, other)) is not Order.NONE
|
337
|
-
]
|
355
|
+
compare = [x for t in classes if (x := typeorder(t, other)) is not Order.NONE]
|
338
356
|
if not compare:
|
339
357
|
return Order.NONE
|
340
358
|
elif any(x is Order.MORE or x is Order.SAME for x in compare):
|
@@ -372,20 +390,13 @@ class Intersection:
|
|
372
390
|
self.__args__ = self.types = types
|
373
391
|
|
374
392
|
def codegen(self):
|
375
|
-
|
376
|
-
|
377
|
-
template = " and ".join("{}" for t in self.types)
|
378
|
-
return combine(
|
379
|
-
template, [generate_checking_code(t) for t in self.types]
|
380
|
-
)
|
393
|
+
return Code("($[ and ]checks)", checks=[generate_checking_code(t) for t in self.types])
|
381
394
|
|
382
395
|
def __type_order__(self, other):
|
383
396
|
if other is Intersection:
|
384
397
|
return Order.LESS
|
385
398
|
classes = self.types
|
386
|
-
compare = [
|
387
|
-
x for t in classes if (x := typeorder(t, other)) is not Order.NONE
|
388
|
-
]
|
399
|
+
compare = [x for t in classes if (x := typeorder(t, other)) is not Order.NONE]
|
389
400
|
if not compare:
|
390
401
|
return Order.NONE
|
391
402
|
elif any(x is Order.LESS or x is Order.SAME for x in compare):
|
@@ -431,15 +442,3 @@ class Dataclass(Protocol):
|
|
431
442
|
return hasattr(subclass, "__dataclass_fields__") and hasattr(
|
432
443
|
subclass, "__dataclass_params__"
|
433
444
|
)
|
434
|
-
|
435
|
-
|
436
|
-
__all__ = [
|
437
|
-
"Dataclass",
|
438
|
-
"Deferred",
|
439
|
-
"Exactly",
|
440
|
-
"HasMethod",
|
441
|
-
"Intersection",
|
442
|
-
"StrictSubclass",
|
443
|
-
"class_check",
|
444
|
-
"parametrized_class_check",
|
445
|
-
]
|
ovld/utils.py
CHANGED
@@ -57,13 +57,24 @@ def keyword_decorator(deco):
|
|
57
57
|
return new_deco
|
58
58
|
|
59
59
|
|
60
|
+
class CodegenInProgress(Exception):
|
61
|
+
pass
|
62
|
+
|
63
|
+
|
60
64
|
class UsageError(Exception):
|
61
65
|
pass
|
62
66
|
|
63
67
|
|
64
|
-
class
|
65
|
-
|
66
|
-
|
68
|
+
class ResolutionError(TypeError):
|
69
|
+
pass
|
70
|
+
|
71
|
+
|
72
|
+
class SpecialForm:
|
73
|
+
def __init__(self, name, message=None):
|
74
|
+
self.__name = name
|
75
|
+
self.__message = (
|
76
|
+
message or f"{name}() can only be used from inside an @ovld-registered function."
|
77
|
+
)
|
67
78
|
|
68
79
|
def __call__(self, *args, **kwargs):
|
69
80
|
raise UsageError(self.__message)
|
@@ -71,6 +82,11 @@ class Unusable:
|
|
71
82
|
def __getattr__(self, attr):
|
72
83
|
raise UsageError(self.__message)
|
73
84
|
|
85
|
+
def __str__(self):
|
86
|
+
return f"<SpecialForm {self.__name}>"
|
87
|
+
|
88
|
+
__repr__ = __str__
|
89
|
+
|
74
90
|
|
75
91
|
class GenericAliasMC(type):
|
76
92
|
def __instancecheck__(cls, obj):
|
@@ -96,6 +112,18 @@ def clsstring(cls):
|
|
96
112
|
return r
|
97
113
|
|
98
114
|
|
115
|
+
def typemap_entry_string(cls):
|
116
|
+
if isinstance(cls, tuple):
|
117
|
+
key, typ = cls
|
118
|
+
return f"{key}: {clsstring(typ)}"
|
119
|
+
else:
|
120
|
+
return clsstring(cls)
|
121
|
+
|
122
|
+
|
123
|
+
def sigstring(types):
|
124
|
+
return ", ".join(map(typemap_entry_string, types))
|
125
|
+
|
126
|
+
|
99
127
|
def subtler_type(obj):
|
100
128
|
if isinstance(obj, GenericAlias):
|
101
129
|
return type[obj]
|
@@ -110,7 +138,7 @@ def subtler_type(obj):
|
|
110
138
|
|
111
139
|
|
112
140
|
class NameDatabase:
|
113
|
-
def __init__(self, default_name):
|
141
|
+
def __init__(self, default_name="TMP"):
|
114
142
|
self.default_name = default_name
|
115
143
|
self.count = count()
|
116
144
|
self.variables = {}
|
@@ -120,32 +148,34 @@ class NameDatabase:
|
|
120
148
|
def register(self, name):
|
121
149
|
self.registered.add(name)
|
122
150
|
|
123
|
-
def gensym(self, desired_name):
|
151
|
+
def gensym(self, desired_name, value=None):
|
124
152
|
i = 1
|
125
153
|
name = desired_name
|
126
|
-
while name in self.registered
|
154
|
+
while name in self.registered or (
|
155
|
+
name in __builtins__ and __builtins__[name] != value
|
156
|
+
):
|
127
157
|
name = f"{desired_name}{i}"
|
128
158
|
i += 1
|
129
159
|
self.registered.add(name)
|
130
160
|
return name
|
131
161
|
|
132
|
-
def
|
162
|
+
def get(self, value, suggested_name=None):
|
133
163
|
if isinstance(value, (int, float, str)):
|
134
164
|
return repr(value)
|
135
165
|
if id(value) in self.names:
|
136
166
|
return self.names[id(value)]
|
137
|
-
|
138
|
-
if
|
139
|
-
|
140
|
-
|
167
|
+
dflt = suggested_name or self.default_name
|
168
|
+
if (
|
169
|
+
isinstance(value, GenericAlias) and typing.get_origin(value) is type
|
170
|
+
): # pragma: no cover
|
171
|
+
name = "t_" + getattr(typing.get_args(value)[0], "__name__", dflt)
|
172
|
+
else:
|
173
|
+
name = getattr(value, "__name__", dflt)
|
174
|
+
if not re.match(string=name, pattern=r"^[a-zA-Z_][a-zA-Z0-9_]*$"):
|
175
|
+
name = dflt
|
176
|
+
name = self.gensym(name, value)
|
141
177
|
self.variables[name] = value
|
142
178
|
self.names[id(value)] = name
|
143
179
|
return name
|
144
180
|
|
145
|
-
|
146
|
-
__all__ = [
|
147
|
-
"BOOTSTRAP",
|
148
|
-
"MISSING",
|
149
|
-
"Named",
|
150
|
-
"keyword_decorator",
|
151
|
-
]
|
181
|
+
__getitem__ = get
|
ovld/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
version = "0.
|
1
|
+
version = "0.5.1"
|
@@ -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|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
ovld/__init__.py,sha256=JuCM8Sj65gobV0KYyLr95cSI23Pi6RYZ7X3_F3fdsSw,1821
|
2
|
+
ovld/abc.py,sha256=4qpZyYwI8dWgY1Oiv5FhdKg2uzNcyWxIpGmGJVcjXrs,1177
|
3
|
+
ovld/codegen.py,sha256=oHfQHlVJUB1Y0PO--rhhkfAmgOhIhKNZyBcodrS1GEs,9210
|
4
|
+
ovld/core.py,sha256=bnp2LIVBUCvNhI53csWaAs7hlxinOBOXhPeoAw_GD3s,17443
|
5
|
+
ovld/dependent.py,sha256=h3j4oQYTQfGqMzggWlLV6TpojX_GtYRFWAO0GcMB0Zs,9085
|
6
|
+
ovld/medley.py,sha256=bp4e-6qH5_Qq05poPu7AcuWf-0mqNCqKMrdK44K2hSs,12667
|
7
|
+
ovld/mro.py,sha256=nqe9jHvsaLu-CfXoP2f7tHg5_8IkcESWMnBmvr4XGIo,4605
|
8
|
+
ovld/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
ovld/recode.py,sha256=YrftCClBcP_luOdqeeQ_EQJddRjikLpY5RmYGRCQrOs,16008
|
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=wO9IQqPVkcS1Opd8cl79WMMpWdCbMQY66PuWF2hMkvc,4272
|
14
|
+
ovld/version.py,sha256=-u_JHy_NHod_JgjQcxN3CYo6TWCz6XQgbb-Z1FmF2Fg,18
|
15
|
+
ovld-0.5.1.dist-info/METADATA,sha256=Lp8FkGbpbaMftXuCYLX3yeCdNTjV67U4ov0h9GV7tCI,9276
|
16
|
+
ovld-0.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
17
|
+
ovld-0.5.1.dist-info/licenses/LICENSE,sha256=cSwNTIzd1cbI89xt3PeZZYJP2y3j8Zus4bXgo4svpX8,1066
|
18
|
+
ovld-0.5.1.dist-info/RECORD,,
|
ovld-0.4.6.dist-info/RECORD
DELETED
@@ -1,15 +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=M85EJPRGNnEpmsr3tEX1U-1di1cxQb5-0oQtLgvfRoA,10183
|
5
|
-
ovld/mro.py,sha256=D9KY3KEFnWL_SXZqHsVKNgRM1E0d8TPPILVZ3SxdUb4,4643
|
6
|
-
ovld/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
-
ovld/recode.py,sha256=bwsdM-TPpSQEmJhv8G30yDLrF8_I5wyTXpITMCC1W9k,17961
|
8
|
-
ovld/typemap.py,sha256=PH5dy8ddVCcqj2TkQbgsM7fmCdHsJT9WGXFPn4JZsCA,13131
|
9
|
-
ovld/types.py,sha256=lvOl1fldIqCJM-TvvdM2nMvAYDgcA45awfpkUB6kKpo,12984
|
10
|
-
ovld/utils.py,sha256=k-cVMbJigtdeuD5_JEYtq1a6fXhmUGqLfBu8YxYd5VY,3395
|
11
|
-
ovld/version.py,sha256=DWNNbWBv-hyqVy76bKbnHWaDUbqL0BOtdkPSV_88dx0,18
|
12
|
-
ovld-0.4.6.dist-info/METADATA,sha256=mXEwvqopBdpksDEKETVZkp-airCb48yt4AWZIQ2r2wU,7833
|
13
|
-
ovld-0.4.6.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
14
|
-
ovld-0.4.6.dist-info/licenses/LICENSE,sha256=cSwNTIzd1cbI89xt3PeZZYJP2y3j8Zus4bXgo4svpX8,1066
|
15
|
-
ovld-0.4.6.dist-info/RECORD,,
|
File without changes
|