Skip to content

Commit

Permalink
Documentation and additional checks for subtype.
Browse files Browse the repository at this point in the history
Stricter handling of iterators and literals.
  • Loading branch information
coady committed Dec 24, 2023
1 parent 4d9cd18 commit 7bf967a
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 9 deletions.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class Foo:
### overload
Overloads dispatch on annotated predicates. Each predicate is checked in the reverse order of registration.

The implementation is separate from `multimethod` due to the different performance characteristics. If an annotation is a type instead of a predicate, it will be converted into an `isinstance` check. Provisionally supports generics as well.
The implementation is separate from `multimethod` due to the different performance characteristics. If an annotation is a type instead of a predicate, it will be converted into an `isinstance` check.

```python
from multimethod import overload
Expand All @@ -120,6 +120,25 @@ def func(obj: str.isdigit):
...
```

### subtype
`subtype` provisionally provides `isinstance` and `issubclass` checks for generic types. When called on a non-generic, it will return the origin type.

```python
from multimethod import subtype

cls = subtype(int | list[int])

for obj in (0, False, [0], [False], []):
assert isinstance(obj, cls)
for obj in (0.0, [0.0], (0,)):
assert not isinstance(obj, cls)

for subclass in (int, bool, list[int], list[bool]):
assert issubclass(subclass, cls)
for subclass in (float, list, list[float], tuple[int]):
assert not issubclass(subclass, cls)
```

### multimeta

Use `metaclass=multimeta` to create a class with a special namespace which converts callables to multimethods, and registers duplicate callables with the original.
Expand Down
10 changes: 5 additions & 5 deletions multimethod/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ def __hash__(self) -> int:
def __subclasscheck__(self, subclass):
origin = getattr(subclass, '__origin__', subclass)
args = getattr(subclass, '__args__', ())
if origin is Literal:
return all(isinstance(arg, self) for arg in args)
if origin is Union:
return all(issubclass(cls, self) for cls in args)
if self.__origin__ is Literal:
return False
if self.__origin__ is Union:
return issubclass(subclass, self.__args__)
if self.__origin__ is type:
return origin is type and issubclass(args[0], self.__args__)
if origin is Literal:
return all(isinstance(arg, self) for arg in args)
if self.__origin__ is Callable:
return (
origin is Callable
Expand All @@ -92,7 +92,7 @@ def __instancecheck__(self, instance):
return issubclass(instance.__orig_class__, self)
if self.__origin__ is type: # a class argument is expected
return inspect.isclass(instance) and issubclass(instance, self.__args__)
if not isinstance(instance, self.__origin__) or issubclass(self.__origin__, Iterator):
if not isinstance(instance, self.__origin__) or isinstance(instance, Iterator):
return False
if self.__origin__ is Callable:
return issubclass(subtype(Callable, *get_type_hints(instance).values()), self)
Expand Down
4 changes: 2 additions & 2 deletions tests/test_methods.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import enum
import pytest
from collections.abc import Iterable, Iterator
from collections.abc import Iterable
from typing import Any, AnyStr, NewType, TypeVar, Union
from multimethod import DispatchError, multimeta, multimethod, signature, subtype

Expand Down Expand Up @@ -56,7 +56,7 @@ def test_subtype():
assert not isinstance((0,), subtype(tuple[int, float]))
assert isinstance((0,), subtype(tuple[int, ...]))
assert not issubclass(tuple[int], subtype(tuple[int, ...]))
assert not isinstance(iter(''), subtype(Iterator[str]))
assert not isinstance(iter('-'), subtype(Iterable[str]))
assert not issubclass(tuple[int], subtype(tuple[int, float]))
assert issubclass(Iterable[bool], subtype(Iterable[int]))
assert issubclass(subtype(Iterable[int]), subtype(Iterable))
Expand Down
4 changes: 3 additions & 1 deletion tests/test_subscripts.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
import pytest
from collections.abc import Callable, Iterable, Mapping, Sequence
from typing import Generic, Literal, Type, TypeVar
from typing import Generic, Literal, Type, TypeVar, Union
from multimethod import distance, multimethod, subtype, DispatchError


Expand All @@ -13,6 +13,8 @@ def test_literals():
assert isinstance('a', tp)
assert isinstance(0, tp)
assert not issubclass(Literal['a', 0.0], tp)
assert not issubclass(tuple[str, int], tp)
assert issubclass(tp, subtype(Union[str, int]))

@multimethod
def func(arg: Literal['a', 0]):
Expand Down

0 comments on commit 7bf967a

Please sign in to comment.