Created
November 18, 2021 01:50
-
-
Save rhettinger/beb2f4a1d6d2ada70a468f51592e58cd to your computer and use it in GitHub Desktop.
Type annotated pure python implementation of the builtin max() function
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'Emulate max() as fully as possible in pure Python.' | |
# https://stackoverflow.com/questions/69997857/implementation-of-max-function-in-python/69997876#69997876 | |
# https://github.com/python/mypy/issues/7231 | |
from typing import TypeVar, Any, Iterator, Iterable, Optional | |
from typing import Union, Protocol, Callable, cast, Tuple, overload | |
class SupportsGT(Protocol): | |
def __gt__(self, other: Any) -> bool: | |
... | |
T = TypeVar('T', bound=SupportsGT) | |
D = TypeVar('D') | |
class Sentinel(object): | |
pass | |
sentinel = Sentinel() | |
def mymax_backend( | |
first_value: T, | |
it: Iterator[T], | |
key: Optional[Callable[[T], Any]], | |
) -> T: | |
largest = first_value | |
if key is None: | |
for x in it: | |
if x > largest: | |
largest = x | |
return largest | |
largest_key = key(largest) | |
for x in it: | |
kx = key(x) | |
if kx > largest_key: | |
largest = x | |
largest_key = kx | |
return largest | |
def mymax_argsonly(*args: T, key: Optional[Callable[[T], Any]] = None) -> T: | |
if not args: | |
raise TypeError('max expected at least 1 argument, got 0') | |
it = iter(args) | |
largest = next(it) | |
return mymax_backend(largest, it, key) | |
def mymax_iterable_only( | |
iterable: Iterable[T], | |
/, | |
*, | |
default: Union[Sentinel, D] = sentinel, | |
key: Optional[Callable[[T], Any]] = None, | |
) -> Union[D, T]: | |
it = iter(iterable) | |
largest = next(it, sentinel) | |
if isinstance(largest, Sentinel): | |
if not isinstance(default, Sentinel): | |
return default | |
raise ValueError('max() arg is an empty sequence') | |
return mymax_backend(largest, it, key) | |
@overload | |
def mymax( | |
iterable: Iterable[T], | |
/, | |
*, | |
default: Union[Sentinel, D] = sentinel, | |
key: Optional[Callable[[T], Any]] = None, | |
) -> Union[D, T]: | |
... | |
@overload | |
def mymax( | |
arg1: T, | |
arg2: T, | |
/, | |
*args: T, | |
default: Sentinel = sentinel, | |
key: Optional[Callable[[T], Any]] = None, | |
) -> T: | |
... | |
def mymax( | |
*args: Union[T, Iterable[T]], | |
default: Union[Sentinel, D] = sentinel, | |
key: Optional[Callable[[T], Any]] = None, | |
) -> Union[D, T]: | |
"""max(iterable, *[, default=obj, key=func]) -> value | |
max(arg1, arg2, *args, *[, key=func]) -> value | |
With a single iterable argument, return its biggest item. The | |
default keyword-only argument specifies an object to return if | |
the provided iterable is empty. | |
With two or more arguments, return the largest argument. | |
""" | |
if not args: | |
raise TypeError('max expected at least 1 argument, got 0') | |
if len(args) == 1: | |
iterable = cast(Iterable[T], args[0]) | |
return mymax_iterable_only(iterable, default=default, key=key) | |
if default is not sentinel: | |
raise TypeError( | |
'Cannot specify a default for max() with multiple positional arguments' | |
) | |
values = cast(Tuple[T, ...], args) | |
return mymax_argsonly(*values, key=key) | |
assert mymax_argsonly('bob', 'adam') == 'bob' | |
assert mymax_argsonly('adam', 'bob') == 'bob' | |
assert mymax_argsonly('bob', 'adam', key=len) == 'adam' | |
assert mymax_argsonly('adam', 'bob', key=len) == 'adam' | |
assert mymax_iterable_only(['bob', 'adam']) == 'bob' | |
assert mymax_iterable_only(['adam', 'bob']) == 'bob' | |
assert mymax_iterable_only([], default=10) == 10 | |
assert mymax_iterable_only(['bob', 'adam'], key=len) == 'adam' | |
assert mymax_iterable_only(['adam', 'bob'], key=len) == 'adam' | |
assert mymax('bob', 'adam') == 'bob' | |
assert mymax('adam', 'bob') == 'bob' | |
assert mymax('bob', 'adam', key=len) == 'adam' | |
assert mymax('adam', 'bob', key=len) == 'adam' | |
assert mymax(['bob', 'adam']) == 'bob' | |
assert mymax(['adam', 'bob']) == 'bob' | |
assert mymax([], default=10) == 10 | |
assert mymax(['bob', 'adam'], key=len) == 'adam' | |
assert mymax(['adam', 'bob'], key=len) == 'adam' | |
assert mymax([10, 20]) == 20 | |
assert mymax([20, 10]) == 20 | |
assert mymax(10, 20) == 20 | |
assert mymax(20, 10) == 20 | |
assert mymax([10, 20, 30]) == 30 | |
assert mymax([30, 20, 10]) == 30 | |
assert mymax(10, 30, 20) == 30 | |
assert mymax(20, 10, 30) == 30 |
Possible issue on line 29
key: Optional[Callable[[T], Any]]
We would want the key to support __gt__
so the type should probably be something like following unless I'm missing something (very likely)
key: Optional[Callable[[T], T]]
Can this work be merged into typeshed?
How is this comparable to https://github.com/python/typeshed/blob/f6702e38711391c86a3bf69fc593edd5db105486/stdlib/builtins.pyi#L1142 ?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Alternative to line 32-36
Looks cleaner but probably has some performance impact which could be alleviated if python had a identity function built-in or in standard library.