ovld 0.4.0__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ovld-0.4.2/.bsync-snap-20240916114026.355340 +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/PKG-INFO +4 -1
- {ovld-0.4.0 → ovld-0.4.2}/pyproject.toml +6 -6
- ovld-0.4.2/reddit.md +54 -0
- ovld-0.4.2/reddit.py +24 -0
- {ovld-0.4.0 → ovld-0.4.2}/src/ovld/__init__.py +1 -0
- {ovld-0.4.0 → ovld-0.4.2}/src/ovld/core.py +12 -9
- {ovld-0.4.0 → ovld-0.4.2}/src/ovld/mro.py +27 -6
- {ovld-0.4.0 → ovld-0.4.2}/src/ovld/recode.py +7 -5
- {ovld-0.4.0 → ovld-0.4.2}/src/ovld/typemap.py +48 -21
- ovld-0.4.2/src/ovld/version.py +1 -0
- ovld-0.4.2/tests/test_mro.py +51 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/test_ovld.py +67 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/test_typemap.py +1 -1
- {ovld-0.4.0 → ovld-0.4.2}/todo.q +1 -0
- {ovld-0.4.0 → ovld-0.4.2}/uv.lock +1 -1
- ovld-0.4.0/src/ovld/version.py +0 -1
- {ovld-0.4.0 → ovld-0.4.2}/.bsync-snap-20220324145902.852076 +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/.envrc +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/.github/workflows/python-package.yml +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/.gitignore +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/.python-version +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/.readthedocs.yaml +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/LICENSE +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/README.md +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/add.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/anal.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/bench/requirements.txt +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/bench.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/benchd.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/benchmarks/__init__.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/benchmarks/common.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/benchmarks/test_add.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/benchmarks/test_ast.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/benchmarks/test_calc.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/benchmarks/test_fib.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/benchmarks/test_multer.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/benchmarks/test_trivial.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/benchmarks/test_tweaknum.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/cloz.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/data.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/didi.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/docs/compare.md +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/docs/dependent.md +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/docs/features.md +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/docs/index.md +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/docs/types.md +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/docs/usage.md +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/doo.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/edges.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/explore.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/facto.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/gen_comparison_table.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/gentest.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/hello.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/hntest.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/klos.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/mkdocs.yml +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/nxt.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.0-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.0.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.1-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.1.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.2-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.2.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.3-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.3.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.4-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.4.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.5-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.5.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.6-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.6.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.7-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.7.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.8-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.8.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.9-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.1.9.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.0-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.0.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.1-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.1.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.10-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.10.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.11-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.11.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.2-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.2.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.3-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.3.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.4-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.4.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.5-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.5.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.6-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.6.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.7-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.7.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.8-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.8.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.9-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.2.9.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.0-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.0.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.1-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.1.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.2-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.2.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.3-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.3.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.4-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.4.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.5-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.5.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.8-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.8.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.9-py3-none-any.whl +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/old-dist/ovld-0.3.9.tar.gz +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/one_two_three.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/outoftheway.toml +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/src/ovld/dependent.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/src/ovld/types.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/src/ovld/utils.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/stuff.md +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tb.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tensor.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/__init__.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/modules/gingerbread.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/test_dependent.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/test_examples.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/test_global.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/test_ovld/test_display.txt +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/test_ovld/test_doc.txt +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/test_ovld/test_doc2.txt +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/test_ovld/test_method_doc.txt +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/test_types.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/tests/test_utils.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/toot.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/typo.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/world.yaml +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/x.py +0 -0
- {ovld-0.4.0 → ovld-0.4.2}/zaggo +0 -0
Binary file
|
@@ -1,7 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: ovld
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.2
|
4
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
|
5
8
|
Author-email: Olivier Breuleux <breuleux@gmail.com>
|
6
9
|
License-Expression: MIT
|
7
10
|
License-File: LICENSE
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "ovld"
|
3
|
-
version = "0.4.
|
3
|
+
version = "0.4.2"
|
4
4
|
description = "Overloading Python functions"
|
5
5
|
authors = [
|
6
6
|
{ name = "Olivier Breuleux", email = "breuleux@gmail.com" }
|
@@ -8,10 +8,13 @@ authors = [
|
|
8
8
|
dependencies = []
|
9
9
|
readme = "README.md"
|
10
10
|
license = "MIT"
|
11
|
-
homepage = "https://github.com/breuleux/ovld"
|
12
|
-
repository = "https://github.com/breuleux/ovld"
|
13
11
|
requires-python = ">= 3.9"
|
14
12
|
|
13
|
+
[project.urls]
|
14
|
+
Homepage = "https://ovld.readthedocs.io/en/latest/"
|
15
|
+
Documentation = "https://ovld.readthedocs.io/en/latest/"
|
16
|
+
Repository = "https://github.com/breuleux/ovld"
|
17
|
+
|
15
18
|
[build-system]
|
16
19
|
requires = ["hatchling"]
|
17
20
|
build-backend = "hatchling.build"
|
@@ -41,9 +44,6 @@ line-length = 80
|
|
41
44
|
extend-select = ["I"]
|
42
45
|
ignore = ["E241", "F722", "E501", "E203", "F811", "F821"]
|
43
46
|
|
44
|
-
[tool.ruff.lint.per-file-ignores]
|
45
|
-
"__init__.py" = ["F401", "F403"]
|
46
|
-
|
47
47
|
[tool.pytest.ini_options]
|
48
48
|
minversion = "6.0"
|
49
49
|
addopts = "--benchmark-columns=median,min,max"
|
ovld-0.4.2/reddit.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
## What My Project Does
|
2
|
+
|
3
|
+
`ovld` implements multiple dispatch in Python. This lets you define multiple versions of the same function with different type signatures.
|
4
|
+
|
5
|
+
For example:
|
6
|
+
|
7
|
+
```python
|
8
|
+
import math
|
9
|
+
from typing import Literal
|
10
|
+
from ovld import ovld
|
11
|
+
|
12
|
+
@ovld
|
13
|
+
def div(x: int, y: int):
|
14
|
+
return x / y
|
15
|
+
|
16
|
+
@ovld
|
17
|
+
def div(x: str, y: str):
|
18
|
+
return f"{x}/{y}"
|
19
|
+
|
20
|
+
@ovld
|
21
|
+
def div(x: int, y: Literal[0]):
|
22
|
+
return math.inf
|
23
|
+
|
24
|
+
assert div(8, 2) == 4
|
25
|
+
assert div("/home", "user") == "/home/user"
|
26
|
+
assert div(10, 0) == math.inf
|
27
|
+
```
|
28
|
+
|
29
|
+
|
30
|
+
## Target Audience
|
31
|
+
|
32
|
+
Ovld is pretty generally applicable: multiple dispatch is a central feature of several programming languages, e.g. Julia. I find it particularly useful when doing work on complex heterogeneous data structures, for instance walking an AST, serializing/deserializing data, generating HTML representations of data, etc.
|
33
|
+
|
34
|
+
|
35
|
+
## Features
|
36
|
+
|
37
|
+
* Wide range of supported annotations: normal types, protocols, Union, Literal, [custom types](https://ovld.readthedocs.io/en/latest/types/#defining-new-types) such as HasMethod, Intersection and arbitrary user-defined types.
|
38
|
+
* Support for [dependent types](https://ovld.readthedocs.io/en/latest/dependent/), by which I mean "types" that depend on the values of the arguments. For example you can easily implement a `Regexp[regex]` type that matches string arguments based on regular expressions, or a type that only matches 2x2 torch.Tensor with int8 dtype.
|
39
|
+
* Dispatch on keyword arguments (with a few limitations).
|
40
|
+
* Define numeric priority levels for disambiguation.
|
41
|
+
* Define [variants](https://ovld.readthedocs.io/en/latest/usage/#variants) of existing functions (copies of existing overloads with additional functionality)
|
42
|
+
* Special `recurse()` function for recursive calls that also work with variants.
|
43
|
+
* Special `call_next()` function to call the next dispatch.
|
44
|
+
|
45
|
+
|
46
|
+
## Comparison
|
47
|
+
|
48
|
+
There already exist a few multiple dispatch libraries: plum, multimethod, multipledispatch, runtype, fastcore, and the builtin functools.singledispatch (single argument).
|
49
|
+
|
50
|
+
Ovld is faster than all of them. From 1.5x to 100x faster depending on use case, and in the ballpark of isinstance/match. It is also generally more featureful: no other library supports dispatch on keyword arguments, and only a few support `Literal` annotations, but with massive performance penalties.
|
51
|
+
|
52
|
+
You can also do many things ovld does with the `match` statement, except you cannot extend existing `match` statements with more signatures.
|
53
|
+
|
54
|
+
[Whole comparison section, with benchmarks, can be found here.](https://ovld.readthedocs.io/en/latest/compare/)
|
ovld-0.4.2/reddit.py
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
import math
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
from ovld import ovld
|
5
|
+
|
6
|
+
|
7
|
+
@ovld
|
8
|
+
def div(x: int, y: int):
|
9
|
+
return x / y
|
10
|
+
|
11
|
+
|
12
|
+
@ovld
|
13
|
+
def div(x: str, y: str):
|
14
|
+
return f"{x}/{y}"
|
15
|
+
|
16
|
+
|
17
|
+
@ovld
|
18
|
+
def div(x: int, y: Literal[0]):
|
19
|
+
return math.inf
|
20
|
+
|
21
|
+
|
22
|
+
assert div(8, 2) == 4
|
23
|
+
assert div("/home", "user") == "/home/user"
|
24
|
+
assert div(10, 0) == math.inf
|
@@ -20,12 +20,6 @@ from .typemap import MultiTypeMap, is_type_of_type
|
|
20
20
|
from .types import normalize_type
|
21
21
|
from .utils import UsageError, keyword_decorator
|
22
22
|
|
23
|
-
try:
|
24
|
-
from types import UnionType
|
25
|
-
except ImportError: # pragma: no cover
|
26
|
-
UnionType = None
|
27
|
-
|
28
|
-
|
29
23
|
_current_id = itertools.count()
|
30
24
|
|
31
25
|
|
@@ -91,6 +85,7 @@ class Signature:
|
|
91
85
|
req_names: frozenset
|
92
86
|
vararg: bool
|
93
87
|
priority: float
|
88
|
+
tiebreak: int = 0
|
94
89
|
is_method: bool = False
|
95
90
|
arginfo: list[Arginfo] = field(
|
96
91
|
default_factory=list, hash=False, compare=False
|
@@ -399,8 +394,8 @@ class _Ovld:
|
|
399
394
|
)
|
400
395
|
else:
|
401
396
|
hlp = ""
|
402
|
-
for
|
403
|
-
hlp += f"* {
|
397
|
+
for c in possibilities:
|
398
|
+
hlp += f"* {c.handler.__name__} (priority: {c.priority}, specificity: {list(c.specificity)})\n"
|
404
399
|
return TypeError(
|
405
400
|
f"Ambiguous resolution in {self} for"
|
406
401
|
f" argument types [{typenames}]\n"
|
@@ -509,7 +504,15 @@ class _Ovld:
|
|
509
504
|
raise TypeError(
|
510
505
|
f"There is already a method for {sigstring(sig.types)}"
|
511
506
|
)
|
512
|
-
|
507
|
+
|
508
|
+
def _set(sig, fn):
|
509
|
+
if sig in self._defns:
|
510
|
+
# Push down the existing handler with a lower tiebreak
|
511
|
+
msig = replace(sig, tiebreak=sig.tiebreak - 1)
|
512
|
+
_set(msig, self._defns[sig])
|
513
|
+
self._defns[sig] = fn
|
514
|
+
|
515
|
+
_set(sig, fn)
|
513
516
|
|
514
517
|
self._update()
|
515
518
|
return self
|
@@ -25,6 +25,16 @@ class TypeRelationship:
|
|
25
25
|
matches: bool = None
|
26
26
|
|
27
27
|
|
28
|
+
def _issubclass(t1, t2):
|
29
|
+
try:
|
30
|
+
return issubclass(t1, t2)
|
31
|
+
except TypeError:
|
32
|
+
try:
|
33
|
+
return isinstance(t1, t2)
|
34
|
+
except TypeError: # pragma: no cover
|
35
|
+
return False
|
36
|
+
|
37
|
+
|
28
38
|
def typeorder(t1, t2):
|
29
39
|
"""Order relation between two types.
|
30
40
|
|
@@ -56,6 +66,8 @@ def typeorder(t1, t2):
|
|
56
66
|
o2 = getattr(t2, "__origin__", None)
|
57
67
|
|
58
68
|
if o2 is typing.Union:
|
69
|
+
if t1 is typing.Union:
|
70
|
+
return Order.MORE
|
59
71
|
compare = [
|
60
72
|
x for t in t2.__args__ if (x := typeorder(t1, t)) is not Order.NONE
|
61
73
|
]
|
@@ -105,8 +117,8 @@ def typeorder(t1, t2):
|
|
105
117
|
# Not sure when t1 != t2 and that happens
|
106
118
|
return Order.SAME
|
107
119
|
|
108
|
-
sx =
|
109
|
-
sy =
|
120
|
+
sx = _issubclass(t1, t2)
|
121
|
+
sy = _issubclass(t2, t1)
|
110
122
|
if sx and sy: # pragma: no cover
|
111
123
|
# Not sure when t1 != t2 and that happens
|
112
124
|
return Order.SAME
|
@@ -136,14 +148,23 @@ def subclasscheck(t1, t2):
|
|
136
148
|
o2 = getattr(t2, "__origin__", None)
|
137
149
|
|
138
150
|
if o2 is typing.Union:
|
139
|
-
return
|
151
|
+
return t1 is typing.Union or any(
|
152
|
+
subclasscheck(t1, t) for t in t2.__args__
|
153
|
+
)
|
140
154
|
elif o1 is typing.Union:
|
141
|
-
return
|
155
|
+
return t2 is typing.Union or all(
|
156
|
+
subclasscheck(t, t2) for t in t1.__args__
|
157
|
+
)
|
158
|
+
|
159
|
+
if not isinstance(o1, type):
|
160
|
+
o1 = None
|
161
|
+
if not isinstance(o2, type):
|
162
|
+
o2 = None
|
142
163
|
|
143
164
|
if o1 or o2:
|
144
165
|
o1 = o1 or t1
|
145
166
|
o2 = o2 or t2
|
146
|
-
if
|
167
|
+
if _issubclass(o1, o2):
|
147
168
|
if o2 is t2: # pragma: no cover
|
148
169
|
return True
|
149
170
|
else:
|
@@ -157,7 +178,7 @@ def subclasscheck(t1, t2):
|
|
157
178
|
else:
|
158
179
|
return False
|
159
180
|
else:
|
160
|
-
return
|
181
|
+
return _issubclass(t1, t2)
|
161
182
|
|
162
183
|
|
163
184
|
def sort_types(cls, avail):
|
@@ -373,6 +373,7 @@ class NameConverter(ast.NodeTransformer):
|
|
373
373
|
self.call_next_sym = call_next_sym
|
374
374
|
self.ovld_mangled = ovld_mangled
|
375
375
|
self.code_mangled = code_mangled
|
376
|
+
self.count = count()
|
376
377
|
|
377
378
|
def visit_Name(self, node):
|
378
379
|
if node.id == self.recurse_sym:
|
@@ -396,15 +397,16 @@ class NameConverter(ast.NodeTransformer):
|
|
396
397
|
return self.generic_visit(node)
|
397
398
|
|
398
399
|
cn = node.func.id == self.call_next_sym
|
400
|
+
tmp = f"__TMP{next(self.count)}_"
|
399
401
|
|
400
402
|
def _make_lookup_call(key, arg):
|
401
403
|
value = ast.NamedExpr(
|
402
|
-
target=ast.Name(id=f"
|
404
|
+
target=ast.Name(id=f"{tmp}{key}", ctx=ast.Store()),
|
403
405
|
value=self.visit(arg),
|
404
406
|
)
|
405
407
|
if self.analysis.lookup_for(key) == "self.map.transform":
|
406
408
|
func = ast.Attribute(
|
407
|
-
value=ast.Name(id="
|
409
|
+
value=ast.Name(id=f"{tmp}M", ctx=ast.Load()),
|
408
410
|
attr="transform",
|
409
411
|
ctx=ast.Load(),
|
410
412
|
)
|
@@ -437,7 +439,7 @@ class NameConverter(ast.NodeTransformer):
|
|
437
439
|
type_parts.insert(0, ast.Name(id=self.code_mangled, ctx=ast.Load()))
|
438
440
|
method = ast.Subscript(
|
439
441
|
value=ast.NamedExpr(
|
440
|
-
target=ast.Name(id="
|
442
|
+
target=ast.Name(id=f"{tmp}M", ctx=ast.Store()),
|
441
443
|
value=ast.Attribute(
|
442
444
|
value=ast.Name(id=self.ovld_mangled, ctx=ast.Load()),
|
443
445
|
attr="map",
|
@@ -464,13 +466,13 @@ class NameConverter(ast.NodeTransformer):
|
|
464
466
|
new_node = ast.Call(
|
465
467
|
func=method,
|
466
468
|
args=[
|
467
|
-
ast.Name(id=f"
|
469
|
+
ast.Name(id=f"{tmp}{i}", ctx=ast.Load())
|
468
470
|
for i, arg in enumerate(node.args)
|
469
471
|
],
|
470
472
|
keywords=[
|
471
473
|
ast.keyword(
|
472
474
|
arg=kw.arg,
|
473
|
-
value=ast.Name(id=f"
|
475
|
+
value=ast.Name(id=f"{tmp}{kw.arg}", ctx=ast.Load()),
|
474
476
|
)
|
475
477
|
for kw in node.keywords
|
476
478
|
],
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import inspect
|
2
2
|
import math
|
3
3
|
import typing
|
4
|
+
from dataclasses import dataclass
|
4
5
|
from itertools import count
|
5
6
|
from types import CodeType
|
6
7
|
|
@@ -69,6 +70,27 @@ class TypeMap(dict):
|
|
69
70
|
raise KeyError(obj_t)
|
70
71
|
|
71
72
|
|
73
|
+
@dataclass
|
74
|
+
class Candidate:
|
75
|
+
handler: object
|
76
|
+
priority: float
|
77
|
+
specificity: tuple
|
78
|
+
tiebreak: int
|
79
|
+
|
80
|
+
def sort_key(self):
|
81
|
+
return self.priority, sum(self.specificity), self.tiebreak
|
82
|
+
|
83
|
+
def dominates(self, other):
|
84
|
+
if self.priority > other.priority:
|
85
|
+
return True
|
86
|
+
elif self.specificity != other.specificity:
|
87
|
+
return all(
|
88
|
+
s1 >= s2 for s1, s2 in zip(self.specificity, other.specificity)
|
89
|
+
)
|
90
|
+
else:
|
91
|
+
return self.tiebreak > other.tiebreak
|
92
|
+
|
93
|
+
|
72
94
|
class MultiTypeMap(dict):
|
73
95
|
"""Represents a mapping from tuples of types to handlers.
|
74
96
|
|
@@ -91,6 +113,7 @@ class MultiTypeMap(dict):
|
|
91
113
|
def __init__(self, name="_ovld", key_error=KeyError):
|
92
114
|
self.maps = {}
|
93
115
|
self.priorities = {}
|
116
|
+
self.tiebreaks = {}
|
94
117
|
self.dependent = {}
|
95
118
|
self.type_tuples = {}
|
96
119
|
self.empty = MISSING
|
@@ -155,7 +178,12 @@ class MultiTypeMap(dict):
|
|
155
178
|
specificities.setdefault(c, []).append(results[c])
|
156
179
|
|
157
180
|
candidates = [
|
158
|
-
(
|
181
|
+
Candidate(
|
182
|
+
handler=c,
|
183
|
+
priority=self.priorities.get(c, 0),
|
184
|
+
specificity=tuple(specificities[c]),
|
185
|
+
tiebreak=self.tiebreaks.get(c, 0),
|
186
|
+
)
|
159
187
|
for c in candidates
|
160
188
|
]
|
161
189
|
|
@@ -164,33 +192,30 @@ class MultiTypeMap(dict):
|
|
164
192
|
# other possibilities on all arguments, so the sum of all specificities
|
165
193
|
# has to be greater.
|
166
194
|
# Note: priority is always more important than specificity
|
167
|
-
|
195
|
+
|
196
|
+
candidates.sort(key=Candidate.sort_key, reverse=True)
|
168
197
|
|
169
198
|
self.all[obj_t_tup] = {
|
170
|
-
getattr(c
|
199
|
+
getattr(c.handler, "__code__", None) for c in candidates
|
171
200
|
}
|
172
201
|
|
173
202
|
processed = set()
|
174
203
|
|
175
204
|
def _pull(candidates):
|
176
|
-
candidates = [
|
177
|
-
(c, a, b) for (c, a, b) in candidates if c not in processed
|
178
|
-
]
|
205
|
+
candidates = [c for c in candidates if c.handler not in processed]
|
179
206
|
if not candidates:
|
180
207
|
return
|
181
208
|
rval = [candidates[0]]
|
182
|
-
c1
|
183
|
-
for c2
|
184
|
-
if
|
185
|
-
spc1 != spc2 and all(s1 >= s2 for s1, s2 in zip(spc1, spc2))
|
186
|
-
):
|
209
|
+
c1 = candidates[0]
|
210
|
+
for c2 in candidates[1:]:
|
211
|
+
if c1.dominates(c2):
|
187
212
|
# Candidate 1 dominates candidate 2
|
188
213
|
continue
|
189
214
|
else:
|
190
|
-
processed.add(c2)
|
215
|
+
processed.add(c2.handler)
|
191
216
|
# Candidate 1 does not dominate candidate 2, so we add it
|
192
217
|
# to the list.
|
193
|
-
rval.append(
|
218
|
+
rval.append(c2)
|
194
219
|
yield rval
|
195
220
|
if len(rval) >= 1:
|
196
221
|
yield from _pull(candidates[1:])
|
@@ -212,6 +237,7 @@ class MultiTypeMap(dict):
|
|
212
237
|
self.empty = entry
|
213
238
|
|
214
239
|
self.priorities[handler] = sig.priority
|
240
|
+
self.tiebreaks[handler] = sig.tiebreak
|
215
241
|
self.type_tuples[handler] = obj_t_tup
|
216
242
|
self.dependent[handler] = any(
|
217
243
|
isinstance(t[1] if isinstance(t, tuple) else t, DependentType)
|
@@ -257,15 +283,16 @@ class MultiTypeMap(dict):
|
|
257
283
|
finished = False
|
258
284
|
rank = 1
|
259
285
|
for grp in self.mro(tuple(argt)):
|
260
|
-
grp.sort(key=lambda x: x
|
286
|
+
grp.sort(key=lambda x: x.handler.__name__)
|
261
287
|
match = [
|
262
288
|
dependent_match(
|
263
|
-
self.type_tuples[handler], [*args, *kwargs.items()]
|
289
|
+
self.type_tuples[c.handler], [*args, *kwargs.items()]
|
264
290
|
)
|
265
|
-
for
|
291
|
+
for c in grp
|
266
292
|
]
|
267
293
|
ambiguous = len([m for m in match if m]) > 1
|
268
|
-
for m,
|
294
|
+
for m, c in zip(match, grp):
|
295
|
+
handler = c.handler
|
269
296
|
color = "\033[0m"
|
270
297
|
if finished:
|
271
298
|
bullet = "--"
|
@@ -282,8 +309,8 @@ class MultiTypeMap(dict):
|
|
282
309
|
message = f"{handler.__name__} will be called first."
|
283
310
|
color = "\033[1;32m"
|
284
311
|
rank += 1
|
285
|
-
spec = ".".join(map(str,
|
286
|
-
lvl = f"[{
|
312
|
+
spec = ".".join(map(str, c.specificity))
|
313
|
+
lvl = f"[{c.priority}:{spec}]"
|
287
314
|
width = 2 * len(args) + 6
|
288
315
|
print(f"{color}{bullet} {lvl:{width}} {handler.__name__}")
|
289
316
|
co = handler.__code__
|
@@ -320,8 +347,8 @@ class MultiTypeMap(dict):
|
|
320
347
|
|
321
348
|
funcs = []
|
322
349
|
for group in reversed(results):
|
323
|
-
handlers = [
|
324
|
-
dependent = any(self.dependent[
|
350
|
+
handlers = [c.handler for c in group]
|
351
|
+
dependent = any(self.dependent[c.handler] for c in group)
|
325
352
|
if dependent:
|
326
353
|
nxt = self.wrap_dependent(
|
327
354
|
obj_t_tup, handlers, group, funcs[-1] if funcs else None
|
@@ -0,0 +1 @@
|
|
1
|
+
version = "0.4.2"
|
@@ -0,0 +1,51 @@
|
|
1
|
+
try:
|
2
|
+
from types import UnionType
|
3
|
+
except ImportError: # pragma: no cover
|
4
|
+
UnionType = None
|
5
|
+
|
6
|
+
import sys
|
7
|
+
from typing import Union
|
8
|
+
|
9
|
+
from ovld.mro import Order, subclasscheck, typeorder
|
10
|
+
|
11
|
+
|
12
|
+
class A:
|
13
|
+
pass
|
14
|
+
|
15
|
+
|
16
|
+
class B(A):
|
17
|
+
pass
|
18
|
+
|
19
|
+
|
20
|
+
def test_subclasscheck():
|
21
|
+
assert subclasscheck(B, A)
|
22
|
+
assert not subclasscheck(A, B)
|
23
|
+
assert subclasscheck(A, A)
|
24
|
+
assert subclasscheck(A, object)
|
25
|
+
assert subclasscheck(A, Union[A, int])
|
26
|
+
assert subclasscheck(int, Union[A, int])
|
27
|
+
|
28
|
+
|
29
|
+
def test_subclasscheck_generic():
|
30
|
+
assert subclasscheck(list[int], list[int])
|
31
|
+
assert subclasscheck(list[int], list[object])
|
32
|
+
assert not subclasscheck(list[object], list[int])
|
33
|
+
|
34
|
+
|
35
|
+
def test_subclasscheck_type():
|
36
|
+
assert subclasscheck(type[int], type[object])
|
37
|
+
|
38
|
+
|
39
|
+
def test_subclasscheck_type_union():
|
40
|
+
if sys.version_info >= (3, 10):
|
41
|
+
assert subclasscheck(type[int | str], type[UnionType])
|
42
|
+
assert subclasscheck(type[Union[int, str]], type[Union])
|
43
|
+
assert subclasscheck(type[Union], object)
|
44
|
+
assert not subclasscheck(object, type[Union])
|
45
|
+
|
46
|
+
|
47
|
+
def test_typeorder_type_union():
|
48
|
+
if sys.version_info >= (3, 10):
|
49
|
+
assert typeorder(type[int | str], type[UnionType]) is Order.LESS
|
50
|
+
assert typeorder(type[Union[int, str]], type[Union]) is Order.LESS
|
51
|
+
assert typeorder(object, type[Union]) is Order.MORE
|
@@ -559,6 +559,22 @@ def test_recurse_method():
|
|
559
559
|
assert C().f([1, 2, 3]) == [2, 3, 4]
|
560
560
|
|
561
561
|
|
562
|
+
def test_recurse_nested():
|
563
|
+
@ovld
|
564
|
+
def f(xs: list):
|
565
|
+
return recurse(sum(recurse(x) for x in xs))
|
566
|
+
|
567
|
+
@ovld
|
568
|
+
def f(x: str):
|
569
|
+
return len(x)
|
570
|
+
|
571
|
+
@ovld
|
572
|
+
def f(x: int):
|
573
|
+
return x * x
|
574
|
+
|
575
|
+
assert f(["a", "bbb", "cc"]) == 36
|
576
|
+
|
577
|
+
|
562
578
|
def test_call_next():
|
563
579
|
f = Ovld()
|
564
580
|
|
@@ -573,6 +589,44 @@ def test_call_next():
|
|
573
589
|
assert f(3) == 8
|
574
590
|
|
575
591
|
|
592
|
+
def test_call_next_unrelated():
|
593
|
+
f = Ovld()
|
594
|
+
|
595
|
+
@f.register
|
596
|
+
def f(x: int):
|
597
|
+
return f.next(str(x))
|
598
|
+
|
599
|
+
@f.register
|
600
|
+
def f(x: str):
|
601
|
+
return x * 2
|
602
|
+
|
603
|
+
@f.register
|
604
|
+
def f(x: object):
|
605
|
+
return "no"
|
606
|
+
|
607
|
+
assert f(3) == "33"
|
608
|
+
for k, v in f.map.items():
|
609
|
+
print(k, v)
|
610
|
+
|
611
|
+
|
612
|
+
def test_call_next_same_priority():
|
613
|
+
f = Ovld()
|
614
|
+
|
615
|
+
@f.register
|
616
|
+
def f(x: int):
|
617
|
+
return x * 2
|
618
|
+
|
619
|
+
@f.register
|
620
|
+
def f(x: int):
|
621
|
+
return call_next(x + 1)
|
622
|
+
|
623
|
+
@f.register
|
624
|
+
def f(x: int):
|
625
|
+
return call_next(-x)
|
626
|
+
|
627
|
+
assert f(5) == -8
|
628
|
+
|
629
|
+
|
576
630
|
def test_recurse_renamed():
|
577
631
|
f = Ovld()
|
578
632
|
|
@@ -1540,11 +1594,16 @@ def test_keywords():
|
|
1540
1594
|
def f(name: str, *, hello: int):
|
1541
1595
|
return "hello" * hello + " " + name
|
1542
1596
|
|
1597
|
+
@ovld
|
1598
|
+
def f(name: str, *, hello: str):
|
1599
|
+
return hello + " " + name
|
1600
|
+
|
1543
1601
|
@f.register
|
1544
1602
|
def f(name: str, *, goodbye: int):
|
1545
1603
|
return "goodbye" * goodbye + " " + name
|
1546
1604
|
|
1547
1605
|
assert f("Helena", hello=3) == "hellohellohello Helena"
|
1606
|
+
assert f("Helena", hello="Bonjour") == "Bonjour Helena"
|
1548
1607
|
assert f("Gertrude", goodbye=2) == "goodbyegoodbye Gertrude"
|
1549
1608
|
|
1550
1609
|
|
@@ -1560,6 +1619,14 @@ def test_keywords_recurse():
|
|
1560
1619
|
assert f([1, 2, 3], factor=3) == [3, 6, 9]
|
1561
1620
|
|
1562
1621
|
|
1622
|
+
def test_passing_types_to_normal_func():
|
1623
|
+
@ovld
|
1624
|
+
def f(x):
|
1625
|
+
return x
|
1626
|
+
|
1627
|
+
assert f(list[int]) == list[int]
|
1628
|
+
|
1629
|
+
|
1563
1630
|
def test_doc(file_regression):
|
1564
1631
|
@ovld
|
1565
1632
|
def mushroom(x: int):
|
{ovld-0.4.0 → ovld-0.4.2}/todo.q
RENAMED
ovld-0.4.0/src/ovld/version.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
version = "0.4.0"
|
File without changes
|
{ovld-0.4.0 → ovld-0.4.2}/.envrc
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{ovld-0.4.0 → ovld-0.4.2}/add.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{ovld-0.4.0 → ovld-0.4.2}/doo.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{ovld-0.4.0 → ovld-0.4.2}/nxt.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{ovld-0.4.0 → ovld-0.4.2}/tb.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{ovld-0.4.0 → ovld-0.4.2}/x.py
RENAMED
File without changes
|
{ovld-0.4.0 → ovld-0.4.2}/zaggo
RENAMED
File without changes
|