diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 156f18c..efc7752 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,10 +7,24 @@ repos: rev: v4.0.1 hooks: - id: end-of-file-fixer - exclude: ^docs/numerary-encumbered\.svg$ + # See + exclude: | + (?x)^( + docs/( + numerary-encumbered\.svg| + perf_.*\.txt + ) + )$ - id: mixed-line-ending - id: trailing-whitespace - exclude: ^docs/numerary-encumbered\.svg$ + # See + exclude: | + (?x)^( + docs/( + numerary-encumbered\.svg| + perf_.*\.txt + ) + )$ - id: check-added-large-files - id: check-case-conflict - id: check-merge-conflict diff --git a/README.md b/README.md index f00b6c7..f73e2f0 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,7 @@ False These offer significant performance improvements, especially where protocols define many methods. ``` python ---8<-- "docs/perf_supports_complex.out" +--8<-- "docs/perf_supports_complex.txt" ```
@@ -370,14 +370,30 @@ True False >>> from numerary.types import SupportsRealImag >>> real_imag: SupportsRealImag = 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 ``` #### Protocols loses fidelity during runtime checking At runtime, protocols match *names*, not *signatures*. -For example, [``SupportsNumeratorDenominatorProperties``](https://posita.github.io/numerary/latest/numerary.types/#numerary.types.SupportsNumeratorDenominatorProperties)’s ``numerator`` and ``denominator`` *properties* will match [``sage.rings.integer.Integer``](https://doc.sagemath.org/html/en/reference/rings_standard/sage/rings/integer.html#sage.rings.integer.Integer)’s similarly named *[functions](https://trac.sagemath.org/ticket/28234)*. -In other words, ``isinstance(sage_integer, SupportsNumeratorDenominatorProperties)`` will return ``True``. +For example, [``SupportsNumeratorDenominator``](https://posita.github.io/numerary/latest/numerary.types/#numerary.types.SupportsNumeratorDenominator)’s ``numerator`` and ``denominator`` *properties* will match [``sage.rings.integer.Integer``](https://doc.sagemath.org/html/en/reference/rings_standard/sage/rings/integer.html#sage.rings.integer.Integer)’s similarly named *[functions](https://trac.sagemath.org/ticket/28234)*. +In other words, ``isinstance(sage_integer, SupportsNumeratorDenominator)`` will return ``True``. Further, if the short-circuiting approach is used, because ``sage.rings.integer.Integer`` registers itself with the numeric tower, this *may*[^5] not be caught by Mypy. [^5]: @@ -394,10 +410,10 @@ Further, if the short-circuiting approach is used, because ``sage.rings.integer. ... def denominator(self) -> int: ... return self._denominator ->>> from numerary.types import SupportsNumeratorDenominatorProperties ->>> frac: SupportsNumeratorDenominatorProperties = Fraction(29, 3) # no typing error ->>> sage_rational1: SupportsNumeratorDenominatorProperties = SageLikeRational(29, 3) # type: ignore [assignment] # Mypy catches this ->>> isinstance(sage_rational1, SupportsNumeratorDenominatorProperties) # isinstance does not +>>> from numerary.types import SupportsNumeratorDenominator +>>> frac: SupportsNumeratorDenominator = Fraction(29, 3) # no typing error +>>> sage_rational1: SupportsNumeratorDenominator = SageLikeRational(29, 3) # type: ignore [assignment] # Mypy catches this +>>> isinstance(sage_rational1, SupportsNumeratorDenominator) # isinstance does not True >>> sage_rational1.numerator <...method...numerator...> @@ -428,11 +444,11 @@ These accommodate rational implementations like Sage’s that are mostly complia ``` python SupportsNumeratorDenominatorMixedU = Union[ - SupportsNumeratorDenominatorProperties, + SupportsNumeratorDenominator, SupportsNumeratorDenominatorMethods, ] SupportsNumeratorDenominatorMixedT = ( - SupportsNumeratorDenominatorProperties, + SupportsNumeratorDenominator, SupportsNumeratorDenominatorMethods, ) ``` diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..cf58d13 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,12 @@ +all : \ + perf_rational_baseline.txt \ + perf_rational_big_protocol.txt \ + perf_rational_protocol.txt \ + perf_supports_complex.txt + +perf_%.txt : perf_%.ipy Makefile ../numerary/types.py + ipython --no-banner --quick --LoggingMagics.quiet=True perf_$*.ipy \ + | cut >perf_$*.txt -f 2 -d $$'\a' + +../numerary-encumbered.svg : Makefile + curl --output $@ 'https://img.shields.io/badge/%F0%9F%98%A3-%F0%9D%9A%97%F0%9D%9A%9E%F0%9D%9A%96%F0%9D%9A%8E%F0%9D%9A%9B%F0%9D%9A%8A%F0%9D%9A%9B%F0%9D%9A%A2--encumbered-yellowgreen' diff --git a/docs/notes.md b/docs/notes.md index c17d1cc..04f0265 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -17,6 +17,9 @@ ## [0.1.2](https://github.com/posita/numerary/releases/tag/v0.1.2) +* Splits [``SupportsRealImagAsMethod``][numerary.types.SupportsRealImagAsMethod] out of [``SupportsRealImag``][numerary.types.SupportsRealImag] and provides the [``real``][numerary.types.real] and [``imag``][numerary.types.imag] helper functions for better support of ``sympy.core.numbers`` primitives. +* Renames ``SupportsNumeratorDenominatorProperties`` to [``SupportsNumeratorDenominator``][numerary.types.SupportsNumeratorDenominator] to mirror [``SupportsRealImag``][numerary.types.SupportsRealImag] and reflect that it captures the numeric tower interface. + ## [0.1.1](https://github.com/posita/numerary/releases/tag/v0.1.1) * Removes obsoleted ``…SCT`` aliases. diff --git a/docs/numerary.types.md b/docs/numerary.types.md index 3310069..2709e1d 100644 --- a/docs/numerary.types.md +++ b/docs/numerary.types.md @@ -57,7 +57,7 @@ from numerary.bt import beartype # will resolve to the identity decorator if be selection: members: - "CachingProtocolMeta" - - "RationalLikeProperties" + - "RationalLike" - "RationalLikeMethods" - "SupportsAbs" - "SupportsComplex" @@ -67,16 +67,19 @@ from numerary.bt import beartype # will resolve to the identity decorator if be - "SupportsRound" - "SupportsConjugate" - "SupportsRealImag" + - "SupportsRealImagAsMethod" - "SupportsTrunc" - "SupportsFloorCeil" - "SupportsDivmod" - - "SupportsNumeratorDenominatorProperties" + - "SupportsNumeratorDenominator" - "SupportsNumeratorDenominatorMethods" - "SupportsComplexOps" - "SupportsComplexPow" - "SupportsRealOps" - "SupportsIntegralOps" - "SupportsIntegralPow" + - "real" + - "imag" - "trunc" - "floor" - "ceil" @@ -103,10 +106,13 @@ from numerary.bt import beartype # will resolve to the identity decorator if be - "_SupportsRound" - "_SupportsConjugate" - "_SupportsRealImag" + - "_SupportsRealImagAsMethod" + - "SupportsRealImagMixedT" + - "SupportsRealImagMixedU" - "_SupportsTrunc" - "_SupportsFloorCeil" - "_SupportsDivmod" - - "_SupportsNumeratorDenominatorProperties" + - "_SupportsNumeratorDenominator" - "_SupportsNumeratorDenominatorMethods" - "SupportsNumeratorDenominatorMixedT" - "SupportsNumeratorDenominatorMixedU" diff --git a/docs/perf_rational_baseline.out b/docs/perf_rational_baseline.out deleted file mode 100644 index 65d02b9..0000000 --- a/docs/perf_rational_baseline.out +++ /dev/null @@ -1,6 +0,0 @@ -%timeit isinstance(builtins.int(1), Rational) -322 ns ± 1.08 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) -%timeit isinstance(fractions.Fraction(2), Rational) -324 ns ± 1.57 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) -%timeit isinstance(builtins.float(3.0), Rational) -346 ns ± 1.18 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) diff --git a/docs/perf_rational_baseline.txt b/docs/perf_rational_baseline.txt new file mode 100644 index 0000000..1a0c4e2 --- /dev/null +++ b/docs/perf_rational_baseline.txt @@ -0,0 +1,6 @@ +%timeit isinstance(builtins.int(1), Rational) +356 ns ± 15.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) +%timeit isinstance(fractions.Fraction(2), Rational) +350 ns ± 14.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) +%timeit isinstance(builtins.float(3.0), Rational) +359 ns ± 3.21 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) diff --git a/docs/perf_rational_big_protocol.out b/docs/perf_rational_big_protocol.txt similarity index 52% rename from docs/perf_rational_big_protocol.out rename to docs/perf_rational_big_protocol.txt index 0c5c77e..7b83113 100644 --- a/docs/perf_rational_big_protocol.out +++ b/docs/perf_rational_big_protocol.txt @@ -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) +136 µs ± 3.3 µ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) +154 µs ± 5.94 µ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) +149 µs ± 8.33 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) diff --git a/docs/perf_rational_protocol.out b/docs/perf_rational_protocol.txt similarity index 50% rename from docs/perf_rational_protocol.out rename to docs/perf_rational_protocol.txt index a9e7d51..5a2bdfa 100644 --- a/docs/perf_rational_protocol.out +++ b/docs/perf_rational_protocol.txt @@ -1,6 +1,6 @@ %timeit isinstance(builtins.int(1), SupportsNumeratorDenominator) -12.3 µs ± 95.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) +13 µs ± 270 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit isinstance(fractions.Fraction(2), SupportsNumeratorDenominator) -12.4 µs ± 91.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) +14 µs ± 761 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit isinstance(builtins.float(3.0), SupportsNumeratorDenominator) -12.5 µs ± 167 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) +15.4 µs ± 323 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) diff --git a/docs/perf_supports_complex.out b/docs/perf_supports_complex.txt similarity index 51% rename from docs/perf_supports_complex.out rename to docs/perf_supports_complex.txt index a2e7c86..1641f3b 100644 --- a/docs/perf_supports_complex.out +++ b/docs/perf_supports_complex.txt @@ -1,24 +1,25 @@ %timeit isinstance(builtins.int(1), _SupportsComplexOps) -11.8 µs ± 314 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) +11.9 µs ± 279 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit isinstance(builtins.int(1), SupportsComplexOps) -307 ns ± 2.97 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) +312 ns ± 1.56 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) %timeit isinstance(builtins.float(2.0), _SupportsComplexOps) -11.6 µs ± 104 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) +13.1 µs ± 385 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit isinstance(builtins.float(2.0), SupportsComplexOps) -312 ns ± 6.29 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) +380 ns ± 23 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) %timeit isinstance(decimal.Decimal(3), _SupportsComplexOps) -11.7 µs ± 68.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) +13.3 µs ± 897 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit isinstance(decimal.Decimal(3), SupportsComplexOps) -306 ns ± 3.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) +322 ns ± 12.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) %timeit isinstance(fractions.Fraction(4), _SupportsComplexOps) -11.7 µs ± 100 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) +11.9 µs ± 178 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit isinstance(fractions.Fraction(4), SupportsComplexOps) -308 ns ± 4.24 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) +314 ns ± 1.54 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) %timeit isinstance(sympy.core.numbers.Integer(5), _SupportsComplexOps) -11.9 µs ± 251 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) +12 µs ± 110 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit isinstance(sympy.core.numbers.Integer(5), SupportsComplexOps) -306 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) +315 ns ± 4.39 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) + diff --git a/docs/whytho.md b/docs/whytho.md index 86e16e5..bd7e3cc 100644 --- a/docs/whytho.md +++ b/docs/whytho.md @@ -219,7 +219,7 @@ Let’s see how they perform. First, let’s get a baseline. ``` python ---8<-- "docs/perf_rational_baseline.out" +--8<-- "docs/perf_rational_baseline.txt" ```
@@ -233,7 +233,7 @@ First, let’s get a baseline. Now let’s compare that with our two-property protocol. ``` python ---8<-- "docs/perf_rational_protocol.out" +--8<-- "docs/perf_rational_protocol.txt" ```
@@ -249,7 +249,7 @@ And that’s just with a two-property protocol. How much worse would it be if we had enumerated all the dunder methods? 😰 ``` python ---8<-- "docs/perf_rational_big_protocol.out" +--8<-- "docs/perf_rational_big_protocol.txt" ```
diff --git a/numerary/types.py b/numerary/types.py index 2a94dc7..3ad4414 100644 --- a/numerary/types.py +++ b/numerary/types.py @@ -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.) + ([``_SupportsRealImag``][numerary.types._SupportsRealImag] is + the raw, non-caching version that defines the actual methods.) ``` python >>> from typing import Any, Tuple, TypeVar - >>> from numerary.types import SupportsRealImag + >>> from numerary.types import SupportsRealImag, real, imag >>> MyRealImagT = TypeVar("MyRealImagT", bound=SupportsRealImag) >>> def real_imag_my_thing(arg: MyRealImagT) -> Tuple[Any, Any]: ... assert isinstance(arg, SupportsRealImag) - ... return (arg.real, arg.imag) + ... return (real(arg), imag(arg)) >>> real_imag_my_thing(3) (3, 0) @@ -582,6 +582,82 @@ class SupportsRealImag( SupportsRealImagSCU = Union[int, float, bool, Complex, SupportsRealImag] +@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.) + + See also the [``real``][numerary.types.real] and [``imag``][numerary.types.imag] + helper functions. + + ``` 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 _) + (, ) + + >>> # 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 +SupportsRealImagMixedU = Union[ + SupportsRealImag, + SupportsRealImagAsMethod, +] +fr""" +{SupportsRealImagMixedU!r} +""" +SupportsRealImagMixedT = ( + SupportsRealImag, + SupportsRealImagAsMethod, +) +fr""" +{SupportsRealImagMixedT!r} +""" +assert SupportsRealImagMixedU.__args__ == SupportsRealImagMixedT # type: ignore [attr-defined] + +SupportsRealImagMixedSCU = Union[ + SupportsRealImagSCU, + SupportsRealImagAsMethod, +] + + @runtime_checkable class _SupportsTrunc(Protocol): r""" @@ -804,10 +880,10 @@ class SupportsDivmod( @runtime_checkable -class _SupportsNumeratorDenominatorProperties(Protocol): +class _SupportsNumeratorDenominator(Protocol): r""" The raw, non-caching version of - [``SupportsNumeratorDenominatorProperties``][numerary.types.SupportsNumeratorDenominatorProperties]. + [``SupportsNumeratorDenominator``][numerary.types.SupportsNumeratorDenominator]. """ __slots__: Union[str, Iterable[str]] = () @@ -821,8 +897,8 @@ def denominator(self) -> int: @runtime_checkable -class SupportsNumeratorDenominatorProperties( - _SupportsNumeratorDenominatorProperties, +class SupportsNumeratorDenominator( + _SupportsNumeratorDenominator, Protocol, metaclass=CachingProtocolMeta, ): @@ -833,16 +909,16 @@ class SupportsNumeratorDenominatorProperties( [``denominator``](https://docs.python.org/3/library/numbers.html#numbers.Rational.denominator) properties. - ([``_SupportsNumeratorDenominatorProperties``][numerary.types._SupportsNumeratorDenominatorProperties] + ([``_SupportsNumeratorDenominator``][numerary.types._SupportsNumeratorDenominator] is the raw, non-caching version that defines the actual properties.) ``` python >>> from typing import Any, Tuple, TypeVar - >>> from numerary.types import SupportsNumeratorDenominatorProperties, denominator, numerator - >>> MyNumDenomT = TypeVar("MyNumDenomT", bound=SupportsNumeratorDenominatorProperties) + >>> from numerary.types import SupportsNumeratorDenominator, denominator, numerator + >>> MyNumDenomT = TypeVar("MyNumDenomT", bound=SupportsNumeratorDenominator) >>> def num_denom_my_thing(arg: MyNumDenomT) -> Tuple[int, int]: - ... assert isinstance(arg, SupportsNumeratorDenominatorProperties) + ... assert isinstance(arg, SupportsNumeratorDenominator) ... return numerator(arg), denominator(arg) >>> num_denom_my_thing(3) @@ -869,12 +945,12 @@ class SupportsNumeratorDenominatorProperties( __slots__: Union[str, Iterable[str]] = () -_assert_isinstance(int, bool, Fraction, target_t=SupportsNumeratorDenominatorProperties) -SupportsNumeratorDenominatorPropertiesSCU = Union[ +_assert_isinstance(int, bool, Fraction, target_t=SupportsNumeratorDenominator) +SupportsNumeratorDenominatorSCU = Union[ int, bool, Rational, - SupportsNumeratorDenominatorProperties, + SupportsNumeratorDenominator, ] @@ -907,20 +983,23 @@ class SupportsNumeratorDenominatorMethods( ([``_SupportsNumeratorDenominatorMethods``][numerary.types._SupportsNumeratorDenominatorMethods] is the raw, non-caching version that defines the actual methods.) + + See also the [``numerator``][numerary.types.numerator] and + [``denominator``][numerary.types.denominator] helper functions. """ __slots__: Union[str, Iterable[str]] = () # See SupportsNumeratorDenominatorMixedU = Union[ - SupportsNumeratorDenominatorProperties, + SupportsNumeratorDenominator, SupportsNumeratorDenominatorMethods, ] fr""" {SupportsNumeratorDenominatorMixedU!r} """ SupportsNumeratorDenominatorMixedT = ( - SupportsNumeratorDenominatorProperties, + SupportsNumeratorDenominator, SupportsNumeratorDenominatorMethods, ) fr""" @@ -929,7 +1008,7 @@ class SupportsNumeratorDenominatorMethods( assert SupportsNumeratorDenominatorMixedU.__args__ == SupportsNumeratorDenominatorMixedT # type: ignore [attr-defined] SupportsNumeratorDenominatorMixedSCU = Union[ - SupportsNumeratorDenominatorPropertiesSCU, + SupportsNumeratorDenominatorSCU, SupportsNumeratorDenominatorMethods, ] @@ -1429,10 +1508,10 @@ def __hash__(self) -> int: @runtime_checkable -class RationalLikeProperties( +class RationalLike( SupportsAbs[_T_co], SupportsFloat, - SupportsNumeratorDenominatorProperties, + SupportsNumeratorDenominator, SupportsRealOps[_T_co], SupportsComplexOps[_T_co], SupportsComplexPow[_T_co], @@ -1445,7 +1524,7 @@ class RationalLikeProperties( * [``SupportsAbs``][numerary.types.SupportsAbs] * [``SupportsFloat``][numerary.types.SupportsFloat] - * [``SupportsNumeratorDenominatorProperties``][numerary.types.SupportsNumeratorDenominatorProperties] + * [``SupportsNumeratorDenominator``][numerary.types.SupportsNumeratorDenominator] * [``SupportsRealOps``][numerary.types.SupportsRealOps] * [``SupportsComplexOps``][numerary.types.SupportsComplexOps] * [``SupportsComplexPow``][numerary.types.SupportsComplexPow] @@ -1457,10 +1536,10 @@ class RationalLikeProperties( from numerary.types import CachingProtocolMeta, Protocol, Supports… T_co = TypeVar("T_co", covariant=True) - class RationalLikeProperties( + class RationalLike( SupportsAbs[T_co], SupportsFloat, - SupportsNumeratorDenominatorProperties, + SupportsNumeratorDenominator, SupportsRealOps[T_co], SupportsComplexOps[T_co], SupportsComplexPow[T_co], @@ -1493,12 +1572,12 @@ def __hash__(self) -> int: pass -_assert_isinstance(int, bool, Fraction, target_t=RationalLikeProperties) -RationalLikePropertiesSCU = Union[ +_assert_isinstance(int, bool, Fraction, target_t=RationalLike) +RationalLikeSCU = Union[ int, bool, Rational, - RationalLikeProperties, + RationalLike, ] @@ -1515,10 +1594,9 @@ class RationalLikeMethods( ): r""" A caching ABC that defines a core set of operations for interacting with rationals. - It is identical to - [``RationalLikeProperties``][numerary.types.RationalLikeProperties] with one + It is identical to [``RationalLike``][numerary.types.RationalLike] with one important exception. Instead of - [``SupportsNumeratorDenominatorProperties``][numerary.types.SupportsNumeratorDenominatorProperties], + [``SupportsNumeratorDenominator``][numerary.types.SupportsNumeratorDenominator], this protocol provides [``SupportsNumeratorDenominatorMethods``][numerary.types.SupportsNumeratorDenominatorMethods]. @@ -1545,6 +1623,9 @@ class RationalLikeMethods( This is probably not very useful on its own, but is important to the construction of [``RationalLikeMixedU``][numerary.types.RationalLikeMixedU] and [``RationalLikeMixedT``][numerary.types.RationalLikeMixedT]. + + See also the [``numerator``][numerary.types.numerator] and + [``denominator``][numerary.types.denominator] helper functions. """ __slots__: Union[str, Iterable[str]] = () @@ -1559,11 +1640,11 @@ def __hash__(self) -> int: # See -RationalLikeMixedU = Union[RationalLikeProperties, RationalLikeMethods] +RationalLikeMixedU = Union[RationalLike, RationalLikeMethods] fr""" {RationalLikeMixedU!r} """ -RationalLikeMixedT = (RationalLikeProperties, RationalLikeMethods) +RationalLikeMixedT = (RationalLike, RationalLikeMethods) fr""" {RationalLikeMixedT!r} """ @@ -1578,7 +1659,7 @@ def __hash__(self) -> int: """ RationalLikeMixedSCU = Union[ - RationalLikePropertiesSCU, + RationalLikeSCU, RationalLikeMethods, ] @@ -1641,7 +1722,7 @@ class IntegralLike( * [``SupportsTrunc``][numerary.types.SupportsTrunc] * [``SupportsFloorCeil``][numerary.types.SupportsFloorCeil] * [``SupportsDivmod``][numerary.types.SupportsDivmod] - * [``SupportsNumeratorDenominatorProperties``][numerary.types.SupportsNumeratorDenominatorProperties] + * [``SupportsNumeratorDenominator``][numerary.types.SupportsNumeratorDenominator] """ __slots__: Union[str, Iterable[str]] = () @@ -1663,27 +1744,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 + [SupportsRealImag][numerary.types.SupportsRealImag] + 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 + [SupportsRealImag][numerary.types.SupportsRealImag] + 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 @@ -1711,27 +1852,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 @@ -1750,7 +1891,7 @@ def numerator(operand: SupportsNumeratorDenominatorMixedU): ``` See - [SupportsNumeratorDenominatorProperties][numerary.types.SupportsNumeratorDenominatorProperties] + [SupportsNumeratorDenominator][numerary.types.SupportsNumeratorDenominator] and [SupportsNumeratorDenominatorMethods][numerary.types.SupportsNumeratorDenominatorMethods]. """ @@ -1779,7 +1920,7 @@ def denominator(operand: SupportsNumeratorDenominatorMixedU): ``` See - [SupportsNumeratorDenominatorProperties][numerary.types.SupportsNumeratorDenominatorProperties] + [SupportsNumeratorDenominator][numerary.types.SupportsNumeratorDenominator] and [SupportsNumeratorDenominatorMethods][numerary.types.SupportsNumeratorDenominatorMethods]. """ diff --git a/tests/test_rational_like.py b/tests/test_rational_like.py index de99fc7..33d571c 100644 --- a/tests/test_rational_like.py +++ b/tests/test_rational_like.py @@ -17,10 +17,10 @@ from numerary.bt import beartype from numerary.types import ( + RationalLike, RationalLikeMixedSCU, RationalLikeMixedT, RationalLikeMixedU, - RationalLikeProperties, ) from .numberwang import ( @@ -55,14 +55,14 @@ def rational_like_func_t(arg: RationalLikeMixedSCU): def test_rational_like() -> None: - bool_val: RationalLikeProperties = True - int_val: RationalLikeProperties = -273 - frac_val: RationalLikeProperties = Fraction(-27315, 100) - test_int_enum: RationalLikeProperties = TestIntEnum.ZERO - test_int_flag: RationalLikeProperties = TestIntFlag.B - nw_val: RationalLikeProperties = Numberwang(-273) - nwd_val: RationalLikeProperties = NumberwangDerived(-273) - nwr_val: RationalLikeProperties = NumberwangRegistered(-273) + bool_val: RationalLike = True + int_val: RationalLike = -273 + frac_val: RationalLike = Fraction(-27315, 100) + test_int_enum: RationalLike = TestIntEnum.ZERO + test_int_flag: RationalLike = TestIntFlag.B + nw_val: RationalLike = Numberwang(-273) + nwd_val: RationalLike = NumberwangDerived(-273) + nwr_val: RationalLike = NumberwangRegistered(-273) for good_val in ( bool_val, @@ -91,13 +91,13 @@ def test_rational_like() -> None: assert good_val.numerator, f"{good_val!r}" assert good_val.denominator, f"{good_val!r}" - float_bad_val: RationalLikeProperties = -273.15 # type: ignore [assignment] - complex_bad_val: RationalLikeProperties = complex(-273.15) # type: ignore [assignment] - dec_bad_val: RationalLikeProperties = Decimal("-273.15") # type: ignore [assignment] - test_flag_bad_val: RationalLikeProperties = TestFlag.B # type: ignore [assignment] - wn_bad_val: RationalLikeProperties = Wangernumb(-273.15) # type: ignore [assignment] - wnd_bad_val: RationalLikeProperties = WangernumbDerived(-273.15) # type: ignore [assignment] - wnr_bad_val: RationalLikeProperties = WangernumbRegistered(-273.15) # type: ignore [assignment] + float_bad_val: RationalLike = -273.15 # type: ignore [assignment] + complex_bad_val: RationalLike = complex(-273.15) # type: ignore [assignment] + dec_bad_val: RationalLike = Decimal("-273.15") # type: ignore [assignment] + test_flag_bad_val: RationalLike = TestFlag.B # type: ignore [assignment] + wn_bad_val: RationalLike = Wangernumb(-273.15) # type: ignore [assignment] + wnd_bad_val: RationalLike = WangernumbDerived(-273.15) # type: ignore [assignment] + wnr_bad_val: RationalLike = WangernumbRegistered(-273.15) # type: ignore [assignment] for bad_val in ( float_bad_val, @@ -147,14 +147,14 @@ def test_rational_like_beartype() -> None: def test_rational_like_numpy() -> None: numpy = pytest.importorskip("numpy", reason="requires numpy") - uint8_val: RationalLikeProperties = numpy.uint8(2) - uint16_val: RationalLikeProperties = numpy.uint16(273) - uint32_val: RationalLikeProperties = numpy.uint32(273) - uint64_val: RationalLikeProperties = numpy.uint64(273) - int8_val: RationalLikeProperties = numpy.int8(-2) - int16_val: RationalLikeProperties = numpy.int16(-273) - int32_val: RationalLikeProperties = numpy.int32(-273) - int64_val: RationalLikeProperties = numpy.int64(-273) + uint8_val: RationalLike = numpy.uint8(2) + uint16_val: RationalLike = numpy.uint16(273) + uint32_val: RationalLike = numpy.uint32(273) + uint64_val: RationalLike = numpy.uint64(273) + int8_val: RationalLike = numpy.int8(-2) + int16_val: RationalLike = numpy.int16(-273) + int32_val: RationalLike = numpy.int32(-273) + int64_val: RationalLike = numpy.int64(-273) for good_val in ( uint8_val, @@ -180,13 +180,13 @@ def test_rational_like_numpy() -> None: assert good_val.denominator, f"{good_val!r}" # TODO(posita): These should not validate - float16_val: RationalLikeProperties = numpy.float16(-1.8) - float32_val: RationalLikeProperties = numpy.float32(-273.15) - float64_val: RationalLikeProperties = numpy.float64(-273.15) - float128_val: RationalLikeProperties = numpy.float128(-273.15) - csingle_val: RationalLikeProperties = numpy.csingle(-273.15) - cdouble_val: RationalLikeProperties = numpy.cdouble(-273.15) - clongdouble_val: RationalLikeProperties = numpy.clongdouble(-273.15) + float16_val: RationalLike = numpy.float16(-1.8) + float32_val: RationalLike = numpy.float32(-273.15) + float64_val: RationalLike = numpy.float64(-273.15) + float128_val: RationalLike = numpy.float128(-273.15) + csingle_val: RationalLike = numpy.csingle(-273.15) + cdouble_val: RationalLike = numpy.cdouble(-273.15) + clongdouble_val: RationalLike = numpy.clongdouble(-273.15) for bad_val in ( float16_val, @@ -235,8 +235,8 @@ def test_rational_like_numpy_beartype() -> None: def test_rational_like_sympy() -> None: sympy = pytest.importorskip("sympy", reason="requires sympy") - integral_val: RationalLikeProperties = sympy.Integer(-273) - rational_val: RationalLikeProperties = sympy.Rational(-27315, 100) + integral_val: RationalLike = sympy.Integer(-273) + rational_val: RationalLike = sympy.Rational(-27315, 100) for good_val in ( integral_val, @@ -260,8 +260,8 @@ def test_rational_like_sympy() -> None: assert good_val.denominator, f"{good_val!r}" # TODO(posita): These should not validate - sym_float: RationalLikeProperties = sympy.Float(-273.15) - sym_sym: RationalLikeProperties = sympy.symbols("x") + sym_float: RationalLike = sympy.Float(-273.15) + sym_sym: RationalLike = sympy.symbols("x") for bad_val in ( sym_float, diff --git a/tests/test_real_like.py b/tests/test_real_like.py index b0d59a2..d9bd7f5 100644 --- a/tests/test_real_like.py +++ b/tests/test_real_like.py @@ -233,7 +233,7 @@ def test_real_like_numpy_beartype() -> None: def test_real_like_sympy() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") integer_val: RealLike = sympy.Integer(-273) rational_val: RealLike = sympy.Rational(-27315, 100) float_val: RealLike = sympy.Float(-273.15) @@ -270,7 +270,7 @@ def test_real_like_sympy() -> None: def test_real_like_sympy_beartype() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") pytest.importorskip("beartype.roar", reason="requires beartype") for good_val in ( diff --git a/tests/test_supports_complex_ops_pow.py b/tests/test_supports_complex_ops_pow.py index 7602c8a..a8f10e8 100644 --- a/tests/test_supports_complex_ops_pow.py +++ b/tests/test_supports_complex_ops_pow.py @@ -266,7 +266,7 @@ def test_supports_complex_ops_pow_numpy_beartype() -> None: def test_supports_complex_ops_pow_sympy() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") integer_val: SupportsComplexOps = sympy.Integer(-273) rational_val: SupportsComplexOps = sympy.Rational(-27315, 100) float_val: SupportsComplexOps = sympy.Float(-273.15) @@ -295,7 +295,7 @@ def test_supports_complex_ops_pow_sympy() -> None: def test_supports_complex_ops_pow_sympy_beartype() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") pytest.importorskip("beartype.roar", reason="requires beartype") for good_val in ( diff --git a/tests/test_supports_conjugate.py b/tests/test_supports_conjugate.py index e3fdcb3..642ada3 100644 --- a/tests/test_supports_conjugate.py +++ b/tests/test_supports_conjugate.py @@ -112,18 +112,6 @@ def test_supports_conjugate_beartype() -> None: supports_conjugate_func(cast(SupportsConjugate, good_val)) supports_conjugate_func_t(cast(SupportsConjugateSCU, good_val)) - for lying_val in ( - # These have lied about supporting this interface when they registered - # themselves in the number tower - NumberwangRegistered(-273), - WangernumbRegistered(-273.15), - ): - with pytest.raises(roar.BeartypeException): - supports_conjugate_func(cast(SupportsConjugate, lying_val)) - - with pytest.raises(AssertionError): # gets past beartype - supports_conjugate_func_t(cast(SupportsConjugateSCU, lying_val)) - for bad_val in ( TestFlag.B, Numberwang(-273), @@ -136,6 +124,18 @@ def test_supports_conjugate_beartype() -> None: with pytest.raises(roar.BeartypeException): supports_conjugate_func_t(cast(SupportsConjugateSCU, bad_val)) + for lying_val in ( + # These have lied about supporting this interface when they registered + # themselves in the number tower + NumberwangRegistered(-273), + WangernumbRegistered(-273.15), + ): + with pytest.raises(roar.BeartypeException): + supports_conjugate_func(cast(SupportsConjugate, lying_val)) + + with pytest.raises(AssertionError): # gets past beartype + supports_conjugate_func_t(cast(SupportsConjugateSCU, lying_val)) + def test_supports_conjugate_numpy() -> None: numpy = pytest.importorskip("numpy", reason="requires numpy") @@ -202,7 +202,7 @@ def test_supports_conjugate_numpy_beartype() -> None: def test_supports_conjugate_sympy() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") integer_val: SupportsConjugate = sympy.Integer(-273) rational_val: SupportsConjugate = sympy.Rational(-27315, 100) float_val: SupportsConjugate = sympy.Float(-273.15) @@ -219,7 +219,7 @@ def test_supports_conjugate_sympy() -> None: def test_supports_conjugate_sympy_beartype() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") pytest.importorskip("beartype.roar", reason="requires beartype") for good_val in ( diff --git a/tests/test_supports_divmod.py b/tests/test_supports_divmod.py index d57feca..f11fb84 100644 --- a/tests/test_supports_divmod.py +++ b/tests/test_supports_divmod.py @@ -74,17 +74,6 @@ def test_supports_divmod() -> None: assert isinstance(good_val, SupportsDivmod), f"{good_val!r}" assert divmod(good_val, good_val), f"{good_val!r}" - nwr_bad_val: SupportsDivmod = NumberwangRegistered(-273) # type: ignore [assignment] - wnr_bad_val: SupportsDivmod = WangernumbRegistered(-273.15) # type: ignore [assignment] - - for lying_val in ( - # These have lied about supporting this interface when they registered - # themselves in the number tower - nwr_bad_val, - wnr_bad_val, - ): - assert not isinstance(lying_val, SupportsDivmod), f"{lying_val!r}" - complex_bad_val: SupportsDivmod = complex(-273.15) # type: ignore [assignment] test_flag_bad_val: SupportsDivmod = TestFlag.B # type: ignore [assignment] nw_bad_val: SupportsDivmod = Numberwang(-273) # type: ignore [assignment] @@ -99,6 +88,17 @@ def test_supports_divmod() -> None: ): assert not isinstance(bad_val, SupportsDivmod), f"{bad_val!r}" + nwr_bad_val: SupportsDivmod = NumberwangRegistered(-273) # type: ignore [assignment] + wnr_bad_val: SupportsDivmod = WangernumbRegistered(-273.15) # type: ignore [assignment] + + for lying_val in ( + # These have lied about supporting this interface when they registered + # themselves in the number tower + nwr_bad_val, + wnr_bad_val, + ): + assert not isinstance(lying_val, SupportsDivmod), f"{lying_val!r}" + def test_supports_divmod_beartype() -> None: roar = pytest.importorskip("beartype.roar", reason="requires beartype") @@ -118,18 +118,6 @@ def test_supports_divmod_beartype() -> None: supports_divmod_func(cast(SupportsDivmod, good_val)) supports_divmod_func_t(cast(SupportsDivmodSCU, good_val)) - for lying_val in ( - # These have lied about supporting this interface when they registered - # themselves in the number tower - NumberwangRegistered(-273), - WangernumbRegistered(-273.15), - ): - with pytest.raises(roar.BeartypeException): - supports_divmod_func(cast(SupportsDivmod, lying_val)) - - with pytest.raises(AssertionError): # gets past beartype - supports_divmod_func_t(cast(SupportsDivmodSCU, lying_val)) - for bad_val in ( complex(-273.15), TestFlag.B, @@ -143,6 +131,18 @@ def test_supports_divmod_beartype() -> None: with pytest.raises(roar.BeartypeException): supports_divmod_func_t(cast(SupportsDivmodSCU, bad_val)) + for lying_val in ( + # These have lied about supporting this interface when they registered + # themselves in the number tower + NumberwangRegistered(-273), + WangernumbRegistered(-273.15), + ): + with pytest.raises(roar.BeartypeException): + supports_divmod_func(cast(SupportsDivmod, lying_val)) + + with pytest.raises(AssertionError): # gets past beartype + supports_divmod_func_t(cast(SupportsDivmodSCU, lying_val)) + def test_supports_divmod_numpy() -> None: numpy = pytest.importorskip("numpy", reason="requires numpy") @@ -223,7 +223,7 @@ def test_supports_divmod_numpy_beartype() -> None: def test_supports_divmod_sympy() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") integer_val: SupportsDivmod = sympy.Integer(-273) rational_val: SupportsDivmod = sympy.Rational(-27315, 100) float_val: SupportsDivmod = sympy.Float(-273.15) @@ -240,7 +240,7 @@ def test_supports_divmod_sympy() -> None: def test_supports_divmod_sympy_beartype() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") pytest.importorskip("beartype.roar", reason="requires beartype") for good_val in ( diff --git a/tests/test_supports_floor_ceil.py b/tests/test_supports_floor_ceil.py index 21f861e..3861773 100644 --- a/tests/test_supports_floor_ceil.py +++ b/tests/test_supports_floor_ceil.py @@ -215,7 +215,7 @@ def test_floor_ceil_numpy_beartype() -> None: def test_floor_ceil_sympy() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") integer_val: SupportsFloorCeil = sympy.Integer(-273) rational_val: SupportsFloorCeil = sympy.Rational(-27315, 100) float_val: SupportsFloorCeil = sympy.Float(-273.15) @@ -237,7 +237,7 @@ def test_floor_ceil_sympy() -> None: def test_floor_ceil_sympy_beartype() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") roar = pytest.importorskip("beartype.roar", reason="requires beartype") for good_val in ( diff --git a/tests/test_supports_integral_ops_pow.py b/tests/test_supports_integral_ops_pow.py index 9a229ab..41220dd 100644 --- a/tests/test_supports_integral_ops_pow.py +++ b/tests/test_supports_integral_ops_pow.py @@ -290,7 +290,7 @@ def test_supports_integral_ops_pow_numpy_beartype() -> None: def test_supports_integral_ops_pow_sympy() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") integral_val: SupportsIntegralOps = sympy.Integer(-273) _: SupportsIntegralPow _ = sympy.Integer(-273) @@ -327,7 +327,7 @@ def test_supports_integral_ops_pow_sympy() -> None: def test_supports_integral_ops_pow_sympy_beartype() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") roar = pytest.importorskip("beartype.roar", reason="requires beartype") for good_val in (sympy.Integer(-273),): diff --git a/tests/test_supports_numerator_denominator.py b/tests/test_supports_numerator_denominator.py index 8d47584..24a6b6e 100644 --- a/tests/test_supports_numerator_denominator.py +++ b/tests/test_supports_numerator_denominator.py @@ -16,11 +16,11 @@ from numerary.bt import beartype from numerary.types import ( + SupportsNumeratorDenominator, SupportsNumeratorDenominatorMethods, SupportsNumeratorDenominatorMixedSCU, SupportsNumeratorDenominatorMixedT, SupportsNumeratorDenominatorMixedU, - SupportsNumeratorDenominatorProperties, denominator, numerator, ) @@ -81,14 +81,14 @@ def supports_numerator_denominator_func_t(arg: SupportsNumeratorDenominatorMixed def test_numerator_denominator() -> None: - bool_val: SupportsNumeratorDenominatorProperties = True - int_val: SupportsNumeratorDenominatorProperties = -273 - frac_val: SupportsNumeratorDenominatorProperties = Fraction(-27315, 100) - test_int_enum: SupportsNumeratorDenominatorProperties = TestIntEnum.ZERO - test_int_flag: SupportsNumeratorDenominatorProperties = TestIntFlag.B - nw_val: SupportsNumeratorDenominatorProperties = Numberwang(-273) - nwd_val: SupportsNumeratorDenominatorProperties = NumberwangDerived(-273) - nwr_val: SupportsNumeratorDenominatorProperties = NumberwangRegistered(-273) + bool_val: SupportsNumeratorDenominator = True + int_val: SupportsNumeratorDenominator = -273 + frac_val: SupportsNumeratorDenominator = Fraction(-27315, 100) + test_int_enum: SupportsNumeratorDenominator = TestIntEnum.ZERO + test_int_flag: SupportsNumeratorDenominator = TestIntFlag.B + nw_val: SupportsNumeratorDenominator = Numberwang(-273) + nwd_val: SupportsNumeratorDenominator = NumberwangDerived(-273) + nwr_val: SupportsNumeratorDenominator = NumberwangRegistered(-273) sage_val: SupportsNumeratorDenominatorMethods = SageLikeRational(-27315, 100) for good_val in ( @@ -173,14 +173,14 @@ def test_numerator_denominator_beartype() -> None: def test_numerator_denominator_numpy() -> None: numpy = pytest.importorskip("numpy", reason="requires numpy") - uint8_val: SupportsNumeratorDenominatorProperties = numpy.uint8(2) - uint16_val: SupportsNumeratorDenominatorProperties = numpy.uint16(273) - uint32_val: SupportsNumeratorDenominatorProperties = numpy.uint32(273) - uint64_val: SupportsNumeratorDenominatorProperties = numpy.uint64(273) - int8_val: SupportsNumeratorDenominatorProperties = numpy.int8(-2) - int16_val: SupportsNumeratorDenominatorProperties = numpy.int16(-273) - int32_val: SupportsNumeratorDenominatorProperties = numpy.int32(-273) - int64_val: SupportsNumeratorDenominatorProperties = numpy.int64(-273) + uint8_val: SupportsNumeratorDenominator = numpy.uint8(2) + uint16_val: SupportsNumeratorDenominator = numpy.uint16(273) + uint32_val: SupportsNumeratorDenominator = numpy.uint32(273) + uint64_val: SupportsNumeratorDenominator = numpy.uint64(273) + int8_val: SupportsNumeratorDenominator = numpy.int8(-2) + int16_val: SupportsNumeratorDenominator = numpy.int16(-273) + int32_val: SupportsNumeratorDenominator = numpy.int32(-273) + int64_val: SupportsNumeratorDenominator = numpy.int64(-273) for good_val in ( uint8_val, @@ -262,8 +262,8 @@ def test_numerator_denominator_numpy_beartype() -> None: def test_numerator_denominator_sympy() -> None: sympy = pytest.importorskip("sympy", reason="requires sympy") - integral_val: SupportsNumeratorDenominatorProperties = sympy.Integer(-273) - rational_val: SupportsNumeratorDenominatorProperties = sympy.Rational(-27315, 100) + integral_val: SupportsNumeratorDenominator = sympy.Integer(-273) + rational_val: SupportsNumeratorDenominator = sympy.Rational(-27315, 100) for good_val in ( integral_val, diff --git a/tests/test_supports_real_imag.py b/tests/test_supports_real_imag.py index bf2ab9a..1d4977a 100644 --- a/tests/test_supports_real_imag.py +++ b/tests/test_supports_real_imag.py @@ -15,7 +15,16 @@ import pytest from numerary.bt import beartype -from numerary.types import SupportsRealImag, SupportsRealImagSCU +from numerary.types import ( + SupportsRealImag, + SupportsRealImagAsMethod, + SupportsRealImagMixedSCU, + SupportsRealImagMixedT, + SupportsRealImagMixedU, + SupportsRealImagSCU, + imag, + real, +) from .numberwang import ( Numberwang, @@ -36,12 +45,22 @@ @beartype -def supports_real_imag_func(arg: SupportsRealImag): +def supports_real_imag_func(arg: SupportsRealImagMixedU): + assert isinstance(arg, SupportsRealImagMixedT), f"{arg!r}" + + +@beartype +def supports_real_imag_func_t(arg: SupportsRealImagMixedSCU): + assert isinstance(arg, SupportsRealImagMixedT), f"{arg!r}" + + +@beartype +def supports_real_imag_properties_func(arg: SupportsRealImag): assert isinstance(arg, SupportsRealImag), f"{arg!r}" @beartype -def supports_real_imag_func_t(arg: SupportsRealImagSCU): +def supports_real_imag_properties_func_t(arg: SupportsRealImagSCU): assert isinstance(arg, SupportsRealImag), f"{arg!r}" @@ -71,15 +90,15 @@ def test_supports_real_imag() -> None: nwd_val, wnd_val, ): - assert isinstance(good_val, SupportsRealImag), f"{good_val!r}" - assert hasattr(good_val, "real"), f"{good_val!r}" - assert hasattr(good_val, "imag"), f"{good_val!r}" + assert isinstance(good_val, SupportsRealImagMixedT), f"{good_val!r}" + assert real(good_val) is not None, f"{good_val!r}" + assert imag(good_val) is not None, f"{good_val!r}" - test_flag_bad_val: SupportsRealImag = TestFlag.B # type: ignore [assignment] - nw_bad_val: SupportsRealImag = Numberwang(-273) # type: ignore [assignment] - nwr_bad_val: SupportsRealImag = NumberwangRegistered(-273) # type: ignore [assignment] - wn_bad_val: SupportsRealImag = Wangernumb(-273.15) # type: ignore [assignment] - wnr_bad_val: SupportsRealImag = WangernumbRegistered(-273.15) # type: ignore [assignment] + test_flag_bad_val: SupportsRealImagMixedU = TestFlag.B # type: ignore [assignment] + nw_bad_val: SupportsRealImagMixedU = Numberwang(-273) # type: ignore [assignment] + nwr_bad_val: SupportsRealImagMixedU = NumberwangRegistered(-273) # type: ignore [assignment] + wn_bad_val: SupportsRealImagMixedU = Wangernumb(-273.15) # type: ignore [assignment] + wnr_bad_val: SupportsRealImagMixedU = WangernumbRegistered(-273.15) # type: ignore [assignment] for bad_val in ( test_flag_bad_val, @@ -89,7 +108,7 @@ def test_supports_real_imag() -> None: wnr_bad_val, "-273.15", ): - assert not isinstance(bad_val, SupportsRealImag), f"{bad_val!r}" + assert not isinstance(bad_val, SupportsRealImagMixedT), f"{bad_val!r}" def test_supports_real_imag_beartype() -> None: @@ -108,20 +127,8 @@ def test_supports_real_imag_beartype() -> None: NumberwangDerived(-273), WangernumbDerived(-273.15), ): - supports_real_imag_func(cast(SupportsRealImag, good_val)) - supports_real_imag_func_t(cast(SupportsRealImagSCU, good_val)) - - for lying_val in ( - # These have lied about supporting this interface when they registered - # themselves in the number tower - NumberwangRegistered(-273), - WangernumbRegistered(-273.15), - ): - with pytest.raises(roar.BeartypeException): - supports_real_imag_func(cast(SupportsRealImag, lying_val)) - - with pytest.raises(AssertionError): # gets past beartype - supports_real_imag_func_t(cast(SupportsRealImagSCU, lying_val)) + supports_real_imag_func(cast(SupportsRealImagMixedU, good_val)) + supports_real_imag_func_t(cast(SupportsRealImagMixedSCU, good_val)) for bad_val in ( TestFlag.B, @@ -130,10 +137,22 @@ def test_supports_real_imag_beartype() -> None: "-273.15", ): with pytest.raises(roar.BeartypeException): - supports_real_imag_func(cast(SupportsRealImag, bad_val)) + supports_real_imag_func(cast(SupportsRealImagMixedU, bad_val)) with pytest.raises(roar.BeartypeException): - supports_real_imag_func_t(cast(SupportsRealImagSCU, bad_val)) + supports_real_imag_func_t(cast(SupportsRealImagMixedSCU, bad_val)) + + for lying_val in ( + # These have lied about supporting this interface when they registered + # themselves in the number tower + NumberwangRegistered(-273), + WangernumbRegistered(-273.15), + ): + with pytest.raises(roar.BeartypeException): + supports_real_imag_func(cast(SupportsRealImagMixedU, lying_val)) + + with pytest.raises(AssertionError): # gets past beartype + supports_real_imag_func_t(cast(SupportsRealImagMixedSCU, lying_val)) def test_supports_real_imag_numpy() -> None: @@ -171,9 +190,9 @@ def test_supports_real_imag_numpy() -> None: cdouble_val, clongdouble_val, ): - assert isinstance(good_val, SupportsRealImag), f"{good_val!r}" - assert hasattr(good_val, "real"), f"{good_val!r}" - assert hasattr(good_val, "imag"), f"{good_val!r}" + assert isinstance(good_val, SupportsRealImagMixedT), f"{good_val!r}" + assert real(good_val) is not None, f"{good_val!r}" + assert imag(good_val) is not None, f"{good_val!r}" def test_supports_real_imag_numpy_beartype() -> None: @@ -197,12 +216,44 @@ def test_supports_real_imag_numpy_beartype() -> None: numpy.cdouble(-273.15), numpy.clongdouble(-273.15), ): - supports_real_imag_func(cast(SupportsRealImag, good_val)) - supports_real_imag_func_t(cast(SupportsRealImagSCU, good_val)) + supports_real_imag_func(cast(SupportsRealImagMixedU, good_val)) + supports_real_imag_func_t(cast(SupportsRealImagMixedSCU, good_val)) def test_supports_real_imag_sympy() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") + integer_val: SupportsRealImagAsMethod = sympy.Integer(-273) + rational_val: SupportsRealImagAsMethod = sympy.Rational(-27315, 100) + float_val: SupportsRealImagAsMethod = sympy.Float(-273.15) + sym_val: SupportsRealImagAsMethod = sympy.symbols("x") + + for good_val in ( + integer_val, + rational_val, + float_val, + sym_val, + ): + assert isinstance(good_val, SupportsRealImagMixedT), f"{good_val!r}" + assert real(good_val) is not None, f"{good_val!r}" + assert imag(good_val) is not None, f"{good_val!r}" + + +def test_supports_real_imag_sympy_beartype() -> None: + sympy = pytest.importorskip("sympy", reason="requires sympy") + pytest.importorskip("beartype.roar", reason="requires beartype") + + for good_val in ( + sympy.Integer(-273), + sympy.Rational(-27315, 100), + sympy.Float(-273.15), + sympy.symbols("x"), + ): + supports_real_imag_func(cast(SupportsRealImagMixedU, good_val)) + supports_real_imag_func_t(cast(SupportsRealImagMixedSCU, good_val)) + + +def test_supports_real_imag_sympy_false_positives() -> None: + sympy = pytest.importorskip("sympy", reason="requires sympy") # TODO(posita): These should not validate integer_val: SupportsRealImag = sympy.Integer(-273) rational_val: SupportsRealImag = sympy.Rational(-27315, 100) @@ -218,8 +269,8 @@ def test_supports_real_imag_sympy() -> None: assert not isinstance(bad_val, SupportsRealImag), f"{bad_val!r}" -def test_supports_real_imag_sympy_beartype() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") +def test_supports_real_imag_sympy_beartype_false_positives() -> None: + sympy = pytest.importorskip("sympy", reason="requires sympy") roar = pytest.importorskip("beartype.roar", reason="requires beartype") for lying_val in ( @@ -230,14 +281,14 @@ def test_supports_real_imag_sympy_beartype() -> None: sympy.Float(-273.15), ): with pytest.raises(roar.BeartypeException): - supports_real_imag_func(cast(SupportsRealImag, lying_val)) + supports_real_imag_properties_func(cast(SupportsRealImag, lying_val)) with pytest.raises(AssertionError): # gets past beartype - supports_real_imag_func_t(cast(SupportsRealImagSCU, lying_val)) + supports_real_imag_properties_func_t(cast(SupportsRealImagSCU, lying_val)) for bad_val in (sympy.symbols("x"),): with pytest.raises(roar.BeartypeException): - supports_real_imag_func(cast(SupportsRealImag, bad_val)) + supports_real_imag_properties_func(cast(SupportsRealImag, bad_val)) with pytest.raises(roar.BeartypeException): - supports_real_imag_func_t(cast(SupportsRealImagSCU, bad_val)) + supports_real_imag_properties_func_t(cast(SupportsRealImagSCU, bad_val)) diff --git a/tests/test_supports_real_ops.py b/tests/test_supports_real_ops.py index 6a8cf57..6719f72 100644 --- a/tests/test_supports_real_ops.py +++ b/tests/test_supports_real_ops.py @@ -206,7 +206,7 @@ def test_supports_real_ops_numpy_beartype() -> None: def test_supports_real_ops_sympy() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") integer_val: SupportsRealOps = sympy.Integer(-273) rational_val: SupportsRealOps = sympy.Rational(-27315, 100) float_val: SupportsRealOps = sympy.Float(-273.15) @@ -230,7 +230,7 @@ def test_supports_real_ops_sympy() -> None: def test_supports_real_ops_sympy_beartype() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") pytest.importorskip("beartype.roar", reason="requires beartype") for good_val in ( diff --git a/tests/test_supports_trunc.py b/tests/test_supports_trunc.py index ecee698..2dcd7b5 100644 --- a/tests/test_supports_trunc.py +++ b/tests/test_supports_trunc.py @@ -212,7 +212,7 @@ def test_trunc_numpy_beartype() -> None: def test_trunc_sympy() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") integer_val: SupportsTrunc = sympy.Integer(-273) rational_val: SupportsTrunc = sympy.Rational(-27315, 100) float_val: SupportsTrunc = sympy.Float(-273.15) @@ -237,7 +237,7 @@ def test_trunc_sympy() -> None: def test_trunc_sympy_beartype() -> None: - sympy = pytest.importorskip("sympy", reason="requires numpy") + sympy = pytest.importorskip("sympy", reason="requires sympy") pytest.importorskip("beartype.roar", reason="requires beartype") for good_val in (