Skip to content

Commit

Permalink
Split SupportsRealImag into SupportsRealImagProperties and ``…
Browse files Browse the repository at this point in the history
…SupportsRealImagAsMethod``

Now ``sympy.core.numbers`` primitives are adequately supported (excepting general false positives; see #5).

Fixes #4.
  • Loading branch information
posita committed Nov 14, 2021
1 parent 3600886 commit bc031a7
Show file tree
Hide file tree
Showing 14 changed files with 388 additions and 172 deletions.
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,24 @@ True
True
>>> hasattr(pants_on_fire, "real") or hasattr(pants_on_fire, "imag") # somebody's tellin' stories
False
>>> from numerary.types import SupportsRealImag
>>> real_imag: SupportsRealImag = pants_on_fire # fails to detect the lie
>>> from numerary.types import SupportsRealImagProperties
>>> real_imag: SupportsRealImagProperties = pants_on_fire # fails to detect the lie
>>> real_imag.real
Traceback (most recent call last):
...
AttributeError: 'One' object has no attribute 'real'

```

In this particular case, ``numerary`` provides us with a defensive mechanism.

``` python
>>> from numerary.types import SupportsRealImagMixedU, real, imag
>>> real_imag_defense: SupportsRealImagMixedU = pants_on_fire
>>> real(real_imag_defense)
1
>>> imag(real_imag)
0

```

Expand Down
8 changes: 6 additions & 2 deletions docs/numerary.types.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ from numerary.bt import beartype # will resolve to the identity decorator if be
- "SupportsIndex"
- "SupportsRound"
- "SupportsConjugate"
- "SupportsRealImag"
- "SupportsRealImagProperties"
- "SupportsRealImagAsMethod"
- "SupportsTrunc"
- "SupportsFloorCeil"
- "SupportsDivmod"
Expand Down Expand Up @@ -102,7 +103,10 @@ from numerary.bt import beartype # will resolve to the identity decorator if be
- "_SupportsIndex"
- "_SupportsRound"
- "_SupportsConjugate"
- "_SupportsRealImag"
- "_SupportsRealImagProperties"
- "_SupportsRealImagAsMethod"
- "SupportsRealImagMixedT"
- "SupportsRealImagMixedU"
- "_SupportsTrunc"
- "_SupportsFloorCeil"
- "_SupportsDivmod"
Expand Down
4 changes: 2 additions & 2 deletions docs/perf_rational_big_protocol.ipy
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ from numerary.types import ( # "raw" (non-caching) versions
_SupportsConjugate,
_SupportsFloorCeil,
_SupportsDivmod,
_SupportsRealImag,
_SupportsRealImagProperties,
_SupportsRealOps,
_SupportsTrunc,
)
Expand Down Expand Up @@ -33,7 +33,7 @@ class SupportsLotsOfNumberStuff(
_SupportsTrunc,
_SupportsFloorCeil,
_SupportsConjugate,
_SupportsRealImag,
_SupportsRealImagProperties,
SupportsAbs,
SupportsFloat,
SupportsComplex,
Expand Down
6 changes: 3 additions & 3 deletions docs/perf_rational_big_protocol.out
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
%timeit isinstance(builtins.int(1), SupportsLotsOfNumberStuff)
132 µs ± 1.15 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
135 µs ± 1.02 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit isinstance(fractions.Fraction(2), SupportsLotsOfNumberStuff)
139 µs ± 2.01 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
146 µs ± 14.4 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit isinstance(builtins.float(3.0), SupportsLotsOfNumberStuff)
131 µs ± 1.13 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
134 µs ± 1.84 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
219 changes: 178 additions & 41 deletions numerary/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,10 +520,10 @@ class SupportsConjugate(


@runtime_checkable
class _SupportsRealImag(Protocol):
class _SupportsRealImagProperties(Protocol):
r"""
The raw, non-caching version of
[``SupportsRealImag``][numerary.types.SupportsRealImag].
[``SupportsRealImagProperties``][numerary.types.SupportsRealImagProperties].
"""
__slots__: Union[str, Iterable[str]] = ()

Expand All @@ -537,8 +537,8 @@ def imag(self) -> Any:


@runtime_checkable
class SupportsRealImag(
_SupportsRealImag,
class SupportsRealImagProperties(
_SupportsRealImagProperties,
Protocol,
metaclass=CachingProtocolMeta,
):
Expand All @@ -548,17 +548,17 @@ class SupportsRealImag(
[``imag``](https://docs.python.org/3/library/numbers.html#numbers.Complex.imag)
properties.
([``_SupportsRealImag``][numerary.types._SupportsRealImag] is the raw, non-caching
version that defines the actual methods.)
([``_SupportsRealImagProperties``][numerary.types._SupportsRealImagProperties] is
the raw, non-caching version that defines the actual methods.)
``` python
>>> from typing import Any, Tuple, TypeVar
>>> from numerary.types import SupportsRealImag
>>> MyRealImagT = TypeVar("MyRealImagT", bound=SupportsRealImag)
>>> from numerary.types import SupportsRealImagProperties, real, imag
>>> MyRealImagPropertiesT = TypeVar("MyRealImagPropertiesT", bound=SupportsRealImagProperties)
>>> def real_imag_my_thing(arg: MyRealImagT) -> Tuple[Any, Any]:
... assert isinstance(arg, SupportsRealImag)
... return (arg.real, arg.imag)
>>> def real_imag_my_thing(arg: MyRealImagPropertiesT) -> Tuple[Any, Any]:
... assert isinstance(arg, SupportsRealImagProperties)
... return (real(arg), imag(arg))
>>> real_imag_my_thing(3)
(3, 0)
Expand All @@ -567,7 +567,7 @@ class SupportsRealImag(
>>> real_imag_my_thing(Decimal(2.5))
(Decimal('2.5'), Decimal('0'))
>>> # error: Value of type variable "MyRealImagT" of "real_imag_my_thing" cannot be "str"
>>> # error: Value of type variable "MyRealImagPropertiesT" of "real_imag_my_thing" cannot be "str"
>>> real_imag_my_thing("not-a-number") # type: ignore [type-var]
Traceback (most recent call last):
...
Expand All @@ -578,8 +578,85 @@ class SupportsRealImag(
__slots__: Union[str, Iterable[str]] = ()


_assert_isinstance(int, float, bool, Decimal, Fraction, target_t=SupportsRealImag)
SupportsRealImagSCU = Union[int, float, bool, Complex, SupportsRealImag]
_assert_isinstance(
int, float, bool, Decimal, Fraction, target_t=SupportsRealImagProperties
)
SupportsRealImagPropertiesSCU = Union[
int, float, bool, Complex, SupportsRealImagProperties
]


@runtime_checkable
class _SupportsRealImagAsMethod(Protocol):
r"""
The raw, non-caching version of
[``SupportsRealImagAsMethod``][numerary.types.SupportsRealImagAsMethod].
"""
__slots__: Union[str, Iterable[str]] = ()

@abstractmethod
def as_real_imag(self) -> Tuple[Any, Any]:
pass


@runtime_checkable
class SupportsRealImagAsMethod(
_SupportsRealImagAsMethod,
Protocol,
metaclass=CachingProtocolMeta,
):
r"""
A caching ABC defining the ``#!python as_real_imag`` method that returns a 2-tuple.
([``_SupportsRealImagAsMethod``][numerary.types._SupportsRealImagAsMethod]
is the raw, non-caching version that defines the actual methods.)
``` python
>>> from typing import Any, Tuple, TypeVar
>>> from numerary.types import SupportsRealImagAsMethod, real, imag
>>> MyRealImagAsMethodT = TypeVar("MyRealImagAsMethodT", bound=SupportsRealImagAsMethod)
>>> def as_real_imag_my_thing(arg: MyRealImagAsMethodT) -> Tuple[Any, Any]:
... assert isinstance(arg, SupportsRealImagAsMethod)
... return (real(arg), imag(arg))
>>> as_real_imag_my_thing(sympy.core.numbers.Float(3.5))
(3.5, 0)
>>> tuple(type(i) for i in _)
(<class 'sympy.core.numbers.Float'>, <class 'sympy.core.numbers.Zero'>)
>>> # error: Value of type variable "MyRealImagAsMethodT" of "as_real_imag_my_thing" cannot be "str"
>>> as_real_imag_my_thing("not-a-number") # type: ignore [type-var]
Traceback (most recent call last):
...
AssertionError
```
"""
__slots__: Union[str, Iterable[str]] = ()


# See <https://github.com/mkdocstrings/mkdocstrings/issues/333>
SupportsRealImagMixedU = Union[
SupportsRealImagProperties,
SupportsRealImagAsMethod,
]
fr"""
{SupportsRealImagMixedU!r}
"""
SupportsRealImagMixedT = (
SupportsRealImagProperties,
SupportsRealImagAsMethod,
)
fr"""
{SupportsRealImagMixedT!r}
"""
assert SupportsRealImagMixedU.__args__ == SupportsRealImagMixedT # type: ignore [attr-defined]

SupportsRealImagMixedSCU = Union[
SupportsRealImagPropertiesSCU,
SupportsRealImagAsMethod,
]


@runtime_checkable
Expand Down Expand Up @@ -1406,7 +1483,7 @@ class RealLike(
* [``SupportsComplex``][numerary.types.SupportsComplex]
* [``SupportsConjugate``][numerary.types.SupportsConjugate]
* [``SupportsRealImag``][numerary.types.SupportsRealImag]
* [``SupportsRealImagProperties``][numerary.types.SupportsRealImagProperties]
* [``SupportsRound``][numerary.types.SupportsRound]
* [``SupportsTrunc``][numerary.types.SupportsTrunc]
* [``SupportsFloorCeil``][numerary.types.SupportsFloorCeil]
Expand Down Expand Up @@ -1475,7 +1552,7 @@ class RationalLikeProperties(
* [``SupportsComplex``][numerary.types.SupportsComplex]
* [``SupportsConjugate``][numerary.types.SupportsConjugate]
* [``SupportsRealImag``][numerary.types.SupportsRealImag]
* [``SupportsRealImagProperties``][numerary.types.SupportsRealImagProperties]
* [``SupportsRound``][numerary.types.SupportsRound]
* [``SupportsTrunc``][numerary.types.SupportsTrunc]
* [``SupportsFloorCeil``][numerary.types.SupportsFloorCeil]
Expand Down Expand Up @@ -1636,7 +1713,7 @@ class IntegralLike(
* [``SupportsComplex``][numerary.types.SupportsComplex]
* [``SupportsConjugate``][numerary.types.SupportsConjugate]
* [``SupportsRealImag``][numerary.types.SupportsRealImag]
* [``SupportsRealImagProperties``][numerary.types.SupportsRealImagProperties]
* [``SupportsRound``][numerary.types.SupportsRound]
* [``SupportsTrunc``][numerary.types.SupportsTrunc]
* [``SupportsFloorCeil``][numerary.types.SupportsFloorCeil]
Expand All @@ -1663,27 +1740,87 @@ def __hash__(self) -> int:


@beartype
def ceil(operand: Union[SupportsFloat, SupportsFloorCeil]):
def real(operand: SupportsRealImagMixedU):
r"""
Helper function that wraps ``math.ceil``.
Helper function that extracts the real part from *operand* including resolving
non-compliant implementations that implement such extraction via a ``as_real_imag``
method rather than as properties.
``` python
>>> from numerary.types import SupportsFloat, SupportsFloorCeil, ceil
>>> my_ceil: SupportsFloorCeil
>>> my_ceil = 1
>>> ceil(my_ceil)
>>> import sympy
>>> from numerary.types import real
>>> real(sympy.core.numbers.Float(3.5))
3.5
```
See
[SupportsRealImagProperties][numerary.types.SupportsRealImagProperties]
and
[SupportsRealImagAsMethod][numerary.types.SupportsRealImagAsMethod].
"""
if callable(getattr(operand, "as_real_imag", None)):
real_part, _ = operand.as_real_imag() # type: ignore [union-attr]

return real_part
elif hasattr(operand, "real"):
return operand.real # type: ignore [union-attr]
else:
raise TypeError(f"{operand!r} has no real or as_real_imag")


@beartype
def imag(operand: SupportsRealImagMixedU):
r"""
Helper function that extracts the imaginary part from *operand* including resolving
non-compliant implementations that implement such extraction via a ``as_real_imag``
method rather than as properties.
``` python
>>> import sympy
>>> from numerary.types import real
>>> imag(sympy.core.numbers.Float(3.5))
0
```
See
[SupportsRealImagProperties][numerary.types.SupportsRealImagProperties]
and
[SupportsRealImagAsMethod][numerary.types.SupportsRealImagAsMethod].
"""
if callable(getattr(operand, "as_real_imag", None)):
_, imag_part = operand.as_real_imag() # type: ignore [union-attr]

return imag_part
elif hasattr(operand, "imag"):
return operand.imag # type: ignore [union-attr]
else:
raise TypeError(f"{operand!r} has no real or as_real_imag")


@beartype
def trunc(operand: Union[SupportsFloat, SupportsTrunc]):
r"""
Helper function that wraps ``math.trunc``.
``` python
>>> from numerary.types import SupportsFloat, SupportsTrunc, trunc
>>> my_trunc: SupportsTrunc
>>> my_trunc = 1
>>> trunc(my_trunc)
1
>>> from fractions import Fraction
>>> my_ceil = Fraction(1, 2)
>>> ceil(my_ceil)
>>> my_trunc = Fraction(1, 2)
>>> trunc(my_trunc)
0
>>> my_trunc_float: SupportsFloat = 1.2
>>> trunc(my_trunc_float)
1
>>> my_ceil_float: SupportsFloat = 1.2
>>> ceil(my_ceil_float)
2
```
"""
return math.ceil(operand) # type: ignore [arg-type]
return math.trunc(operand) # type: ignore [arg-type]


@beartype
Expand Down Expand Up @@ -1711,27 +1848,27 @@ def floor(operand: Union[SupportsFloat, SupportsFloorCeil]):


@beartype
def trunc(operand: Union[SupportsFloat, SupportsTrunc]):
def ceil(operand: Union[SupportsFloat, SupportsFloorCeil]):
r"""
Helper function that wraps ``math.trunc``.
Helper function that wraps ``math.ceil``.
``` python
>>> from numerary.types import SupportsFloat, SupportsTrunc, trunc
>>> my_trunc: SupportsTrunc
>>> my_trunc = 1
>>> trunc(my_trunc)
>>> from numerary.types import SupportsFloat, SupportsFloorCeil, ceil
>>> my_ceil: SupportsFloorCeil
>>> my_ceil = 1
>>> ceil(my_ceil)
1
>>> from fractions import Fraction
>>> my_trunc = Fraction(1, 2)
>>> trunc(my_trunc)
0
>>> my_trunc_float: SupportsFloat = 1.2
>>> trunc(my_trunc_float)
>>> my_ceil = Fraction(1, 2)
>>> ceil(my_ceil)
1
>>> my_ceil_float: SupportsFloat = 1.2
>>> ceil(my_ceil_float)
2
```
"""
return math.trunc(operand) # type: ignore [arg-type]
return math.ceil(operand) # type: ignore [arg-type]


@beartype
Expand Down
Loading

0 comments on commit bc031a7

Please sign in to comment.