complete draft: update from Atlas

This commit is contained in:
Luciano Ramalho
2021-06-09 00:13:02 -03:00
parent 08a4001b43
commit 135eca25d9
43 changed files with 4705 additions and 3240 deletions

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env python3
import math
from typing import NamedTuple, SupportsAbs
class Vector2d(NamedTuple):
x: float
y: float
def __abs__(self) -> float: # <1>
return math.hypot(self.x, self.y)
def is_unit(v: SupportsAbs[float]) -> bool: # <2>
"""'True' if the magnitude of 'v' is close to 1."""
return math.isclose(abs(v), 1.0) # <3>
assert issubclass(Vector2d, SupportsAbs) # <4>
v0 = Vector2d(0, 1) # <5>
sqrt2 = math.sqrt(2)
v1 = Vector2d(sqrt2 / 2, sqrt2 / 2)
v2 = Vector2d(1, 1)
v3 = complex(.5, math.sqrt(3) / 2)
v4 = 1 # <6>
assert is_unit(v0)
assert is_unit(v1)
assert not is_unit(v2)
assert is_unit(v3)
assert is_unit(v4)
print('OK')

View File

@@ -0,0 +1,60 @@
# tag::MYMAX_TYPES[]
from typing import Protocol, Any, TypeVar, overload, Callable, Iterable, Union
class SupportsLessThan(Protocol):
def __lt__(self, other: Any) -> bool: ...
T = TypeVar('T')
LT = TypeVar('LT', bound=SupportsLessThan)
DT = TypeVar('DT')
MISSING = object()
EMPTY_MSG = 'max() arg is an empty sequence'
@overload
def max(__arg1: LT, __arg2: LT, *args: LT, key: None = ...) -> LT:
...
@overload
def max(__arg1: T, __arg2: T, *args: T, key: Callable[[T], LT]) -> T:
...
@overload
def max(__iterable: Iterable[LT], *, key: None = ...) -> LT:
...
@overload
def max(__iterable: Iterable[T], *, key: Callable[[T], LT]) -> T:
...
@overload
def max(__iterable: Iterable[LT], *, key: None = ...,
default: DT) -> Union[LT, DT]:
...
@overload
def max(__iterable: Iterable[T], *, key: Callable[[T], LT],
default: DT) -> Union[T, DT]:
...
# end::MYMAX_TYPES[]
# tag::MYMAX[]
def max(first, *args, key=None, default=MISSING):
if args:
series = args
candidate = first
else:
series = iter(first)
try:
candidate = next(series)
except StopIteration:
if default is not MISSING:
return default
raise ValueError(EMPTY_MSG) from None
if key is None:
for current in series:
if candidate < current:
candidate = current
else:
candidate_key = key(candidate)
for current in series:
current_key = key(current)
if candidate_key < current_key:
candidate = current
candidate_key = current_key
return candidate
# end::MYMAX[]

View File

@@ -0,0 +1,128 @@
from typing import TYPE_CHECKING, List, Optional
import mymax as my
def demo_args_list_float() -> None:
args = [2.5, 3.5, 1.5]
expected = 3.5
result = my.max(*args)
print(args, expected, result, sep='\n')
assert result == expected
if TYPE_CHECKING:
reveal_type(args)
reveal_type(expected)
reveal_type(result)
def demo_args_iter_int() -> None:
args = [30, 10, 20]
expected = 30
result = my.max(args)
print(args, expected, result, sep='\n')
assert result == expected
if TYPE_CHECKING:
reveal_type(args)
reveal_type(expected)
reveal_type(result)
def demo_args_iter_str() -> None:
args = iter('banana kiwi mango apple'.split())
expected = 'mango'
result = my.max(args)
print(args, expected, result, sep='\n')
assert result == expected
if TYPE_CHECKING:
reveal_type(args)
reveal_type(expected)
reveal_type(result)
def demo_args_iter_not_comparable_with_key() -> None:
args = [object(), object(), object()]
key = id
expected = max(args, key=id)
result = my.max(args, key=key)
print(args, key, expected, result, sep='\n')
assert result == expected
if TYPE_CHECKING:
reveal_type(args)
reveal_type(key)
reveal_type(expected)
reveal_type(result)
def demo_empty_iterable_with_default() -> None:
args: List[float] = []
default = None
expected = None
result = my.max(args, default=default)
print(args, default, expected, result, sep='\n')
assert result == expected
if TYPE_CHECKING:
reveal_type(args)
reveal_type(default)
reveal_type(expected)
reveal_type(result)
def demo_different_key_return_type() -> None:
args = iter('banana kiwi mango apple'.split())
key = len
expected = 'banana'
result = my.max(args, key=key)
print(args, key, expected, result, sep='\n')
assert result == expected
if TYPE_CHECKING:
reveal_type(args)
reveal_type(key)
reveal_type(expected)
reveal_type(result)
def demo_different_key_none() -> None:
args = iter('banana kiwi mango apple'.split())
key = None
expected = 'mango'
result = my.max(args, key=key)
print(args, key, expected, result, sep='\n')
assert result == expected
if TYPE_CHECKING:
reveal_type(args)
reveal_type(key)
reveal_type(expected)
reveal_type(result)
###################################### intentional type errors
def error_reported_bug() -> None:
# example from https://github.com/python/typeshed/issues/4051
top: Optional[int] = None
try:
my.max(5, top)
except TypeError as exc:
print(exc)
def error_args_iter_not_comparable() -> None:
try:
my.max([None, None])
except TypeError as exc:
print(exc)
def error_single_arg_not_iterable() -> None:
try:
my.max(1)
except TypeError as exc:
print(exc)
###################################### run demo and error functions
def main():
for name, val in globals().items():
if name.startswith('demo') or name.startswith('error'):
print('_' * 20, name)
val()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,69 @@
from typing import List, Callable
import pytest # type: ignore
import mymax as my
@pytest.fixture
def fruits():
return 'banana kiwi mango apple'.split()
@pytest.mark.parametrize('args, expected', [
([1, 3], 3),
([3, 1], 3),
([30, 10, 20], 30),
])
def test_max_args(args, expected):
result = my.max(*args)
assert result == expected
@pytest.mark.parametrize('iterable, expected', [
([7], 7),
([1, 3], 3),
([3, 1], 3),
([30, 10, 20], 30),
])
def test_max_iterable(iterable, expected):
result = my.max(iterable)
assert result == expected
def test_max_single_arg_not_iterable():
msg = "'int' object is not iterable"
with pytest.raises(TypeError) as exc:
my.max(1)
assert exc.value.args[0] == msg
def test_max_empty_iterable_no_default():
with pytest.raises(ValueError) as exc:
my.max([])
assert exc.value.args[0] == my.EMPTY_MSG
@pytest.mark.parametrize('iterable, default, expected', [
([7], -1, 7),
([], -1, -1),
([], None, None),
])
def test_max_empty_iterable_with_default(iterable, default, expected):
result = my.max(iterable, default=default)
assert result == expected
@pytest.mark.parametrize('key, expected', [
(None, 'mango'),
(lambda x: x, 'mango'),
(len, 'banana'),
(lambda s: -len(s), 'kiwi'),
(lambda s: -ord(s[0]), 'apple'),
(lambda s: ord(s[-1]), 'mango'),
])
def test_max_iterable_with_key(
fruits: List[str],
key: Callable[[str], str],
expected: str
) -> None:
result = my.max(fruits, key=key)
assert result == expected

View File

@@ -0,0 +1,14 @@
import random
from typing import TypeVar, Generic, List, Iterable
T = TypeVar('T')
class EnterpriserRandomPopper(Generic[T]):
def __init__(self, items: Iterable[T]) -> None:
self._items: List[T] = list(items)
random.shuffle(self._items)
def pop_random(self) -> T:
return self._items.pop()

View File

@@ -0,0 +1,36 @@
from typing import TYPE_CHECKING
from erp import EnterpriserRandomPopper
import randompop
def test_issubclass() -> None:
assert issubclass(EnterpriserRandomPopper, randompop.RandomPopper)
def test_isinstance_untyped_items_argument() -> None:
items = [1, 2, 3]
popper = EnterpriserRandomPopper(items) # [int] is not required
if TYPE_CHECKING:
reveal_type(popper)
# Revealed type is 'erp.EnterpriserRandomPopper[builtins.int*]'
assert isinstance(popper, randompop.RandomPopper)
def test_isinstance_untyped_items_in_var_type() -> None:
items = [1, 2, 3]
popper: EnterpriserRandomPopper = EnterpriserRandomPopper[int](items)
if TYPE_CHECKING:
reveal_type(popper)
# Revealed type is 'erp.EnterpriserRandomPopper[Any]'
assert isinstance(popper, randompop.RandomPopper)
def test_isinstance_item() -> None:
items = [1, 2, 3]
popper = EnterpriserRandomPopper[int](items) # [int] is not required
popped = popper.pop_random()
if TYPE_CHECKING:
reveal_type(popped)
# Revealed type is 'builtins.int*'
assert isinstance(popped, int)

View File

@@ -0,0 +1,7 @@
from typing import Protocol, runtime_checkable, TypeVar
T_co = TypeVar('T_co', covariant=True) # <1>
@runtime_checkable
class RandomPicker(Protocol[T_co]): # <2>
def pick(self) -> T_co: ... # <3>

View File

@@ -0,0 +1,37 @@
import random
from typing import Iterable, Generic, TypeVar, TYPE_CHECKING
T_co = TypeVar('T_co', covariant=True)
from generic_randompick import RandomPicker
class LottoPicker(Generic[T_co]):
def __init__(self, items: Iterable[T_co]) -> None:
self._items = list(items)
random.shuffle(self._items)
def pick(self) -> T_co:
return self._items.pop()
def test_issubclass() -> None:
assert issubclass(LottoPicker, RandomPicker)
def test_isinstance() -> None:
popper: RandomPicker = LottoPicker[int]([1])
if TYPE_CHECKING:
reveal_type(popper)
# Revealed type is '???'
assert isinstance(popper, LottoPicker)
def test_pick_type() -> None:
balls = [1, 2, 3]
popper = LottoPicker(balls)
pick = popper.pick()
assert pick in balls
if TYPE_CHECKING:
reveal_type(pick)
# Revealed type is '???'

View File

@@ -0,0 +1,6 @@
from typing import Protocol, runtime_checkable, Any
@runtime_checkable
class RandomPopper(Protocol):
def pop_random(self) -> Any: ...

View File

@@ -0,0 +1,24 @@
from randompop import RandomPopper
import random
from typing import Any, Iterable, TYPE_CHECKING
class SimplePopper:
def __init__(self, items: Iterable) -> None:
self._items = list(items)
random.shuffle(self._items)
def pop_random(self) -> Any:
return self._items.pop()
def test_issubclass() -> None:
assert issubclass(SimplePopper, RandomPopper)
def test_isinstance() -> None:
popper: RandomPopper = SimplePopper([1])
if TYPE_CHECKING:
reveal_type(popper)
# Revealed type is 'randompop.RandomPopper'
assert isinstance(popper, RandomPopper)