A pure-Python codified rant aspiring to a world where numbers and types can work together.

Overview

Copyright and other protections apply. Please see the accompanying LICENSE file for rights and restrictions governing use of this software. All rights not expressly waived or licensed are reserved. If that file is missing or appears to be modified from its original, then please contact the author before viewing or using this software in any capacity.

Tests Version Development Stage License Supported Python Versions Supported Python Implementations pre-commit Bear-ified™

Are you defining a numeric interface that should work with more than just ints and floats? Are you annotating that interface for documentation and type-checking? Were you excited by PEP 3141’s glitz and gloss promising a clean, straightforward number type definition mechanism, only to learn the hard way—after many hours of searching, tweaking, hacking, and testing ever more convoluted code, again and again—that you could’t actually make it work with Python’s type-checking system? Do you now wonder whether numbers were something new to computing in general because nothing else would explain such a gaping hole in a programming language so popular with the STEM crowd that has been around since the early 1990s? Does the number 3186 haunt you in your dreams? Do you find yourself shouting to no one in particular, “There has to be a better way?”

Well I’m here to tell you there isn’t. But until there is, there’s …

numerary—Now with Protocol Power™

That’s right!

For a hopefully limited time, you too can benefit from someone else’s deranged work-arounds for the enormous chasms in Python that lie between the esoteric fields of computation that are “typing” and “numbers” instead of having to roll your own out of sheer desperation from first principles! If you still have no idea what I’m talking about, this may help illustrate.

numerary is a pure-Python codified rant for signaling that your interface is usable with non-native numeric primitives1 without breaking type-checking. More simply, numerary aspires to a world where numbers and types can work together.

If you’re thinking that you shouldn’t need a 🤬 ing library for that, you’re right.

This madness should enjoy no audience. It should not exist. Yet here we are. Its author gauges its success by how quickly it can be forgotten, relegated to the annals of superfluous folly.

numerary is licensed under the MIT License. See the accompanying LICENSE file for details. It should be considered experimental for now, but should settle down quickly. See the release notes for a summary of version-to-version changes. Source code is available on GitHub.

If you find it lacking in any way, please don’t hesitate to bring it to my attention.

You had me at, “numbers and types can work together”

numerary strives to define composable, efficient protocols that one can use to construct numeric requirements. If all you deal with are integrals and reals, and what you want is broad arithmetic operator compatibility, this will probably get you where you likely want to go:

>>> from numerary import IntegralLike, RealLike

>>> def deeper_thot(arg: RealLike) -> IntegralLike:
...   assert arg != 0 and arg ** 0 == 1
...   return arg // arg + 42

Beyond default compositions for common use cases, numerary expands on the Supports pattern used in the standard library. For example, numerary.types.SupportsIntegralOps is a @typing.runtime_checkable protocol that approximates the unary and binary operators introduced by numbers.Integral.

>>> res = shift_right_one(two) ; res 1 >>> type(res) >>> from fractions import Fraction >>> shift_right_one(Fraction(1, 2)) # type: ignore [arg-type] # properly caught by Mypy Traceback (most recent call last): ... AssertionError ">
>>> from numerary.types import SupportsIntegralOps

>>> def shift_right_one(arg: SupportsIntegralOps) -> SupportsIntegralOps:
...   assert isinstance(arg, SupportsIntegralOps)
...   return arg >> 1

>>> shift_right_one(2)
1

>>> from sympy import sympify
>>> two = sympify("2") ; type(two)
<class 'sympy.core.numbers.Integer'>
>>> res = shift_right_one(two) ; res
1
>>> type(res)
<class 'sympy.core.numbers.One'>

>>> from fractions import Fraction
>>> shift_right_one(Fraction(1, 2))  # type: ignore [arg-type]  # properly caught by Mypy
Traceback (most recent call last):
  ...
AssertionError

!!! note

Until 1.9, ``sympy.core.numbers.Integer`` [lacked the requisite bitwise operators](https://github.com/sympy/sympy/issues/19311).
``numerary`` catches that!
The above properly results in both a type-checking error as well as a runtime failure for [SymPy](https://www.sympy.org/) versions prior to 1.9.

numerary’s Supports protocols can be composed to refine requirements. For example, let’s say one wanted to ensure type compatibility with primitives that support both __abs__ and __divmod__.

>> from numerary.types import ( ... CachingProtocolMeta, Protocol, runtime_checkable, ... SupportsAbs, SupportsDivmod, ... ) >>> @runtime_checkable ... class MyType( ... SupportsAbs[T_co], SupportsDivmod[T_co], ... Protocol, metaclass=CachingProtocolMeta, ... ): ... pass >>> my_type: MyType >>> my_type = 3.5 >>> isinstance(my_type, MyType) True >>> abs(my_type) 3.5 >>> divmod(my_type, 2) (1.0, 1.5) >>> from fractions import Fraction >>> my_type = Fraction(22, 7) >>> isinstance(my_type, MyType) True >>> abs(my_type) Fraction(22, 7) >>> divmod(my_type, 2) (1, Fraction(8, 7)) >>> from decimal import Decimal >>> my_type = Decimal("5.2") >>> isinstance(my_type, MyType) True >>> abs(my_type) Decimal('5.2') >>> divmod(my_type, 2) (Decimal('2'), Decimal('1.2')) >>> my_type = "nope" # type: ignore [assignment] # properly caught by Mypy >>> isinstance(my_type, MyType) False ">
>>> from typing import TypeVar
>>> T_co = TypeVar("T_co", covariant=True)
>>> from numerary.types import (
...   CachingProtocolMeta, Protocol, runtime_checkable,
...   SupportsAbs, SupportsDivmod,
... )

>>> @runtime_checkable
... class MyType(
...   SupportsAbs[T_co], SupportsDivmod[T_co],
...   Protocol, metaclass=CachingProtocolMeta,
... ):
...   pass

>>> my_type: MyType

>>> my_type = 3.5
>>> isinstance(my_type, MyType)
True
>>> abs(my_type)
3.5
>>> divmod(my_type, 2)
(1.0, 1.5)

>>> from fractions import Fraction
>>> my_type = Fraction(22, 7)
>>> isinstance(my_type, MyType)
True
>>> abs(my_type)
Fraction(22, 7)
>>> divmod(my_type, 2)
(1, Fraction(8, 7))

>>> from decimal import Decimal
>>> my_type = Decimal("5.2")
>>> isinstance(my_type, MyType)
True
>>> abs(my_type)
Decimal('5.2')
>>> divmod(my_type, 2)
(Decimal('2'), Decimal('1.2'))

>>> my_type = "nope"  # type: ignore [assignment]  # properly caught by Mypy
>>> isinstance(my_type, MyType)
False

Remember that scandal where complex defined exception-throwing comparators it wasn’t supposed to have, which confused runtime protocol checking, and then its type definitions lied about it to cover it up? Yeah, that shit ends here.

>>> from numerary.types import SupportsRealOps
>>> isinstance(1.0, SupportsRealOps)  # all good
True
>>> has_real_ops: SupportsRealOps = complex(1)  # type: ignore [assignment]  # properly caught by Mypy
>>> isinstance(complex(1), SupportsRealOps)  # you're not fooling anyone, buddy
False

numerary not only caches runtime protocol evaluations, but allows overriding those evaluations when the default machinery gets it wrong.

>> imp: MySupportsOne = Imposter() # type: ignore [assignment] # properly caught by Mypy >>> isinstance(imp, MySupportsOne) # fool me once, shame on you ... True >>> MySupportsOne.excludes(Imposter) >>> isinstance(imp, MySupportsOne) # ... can't get fooled again False ">
>>> from abc import abstractmethod
>>> from typing import Iterable, Union
>>> from numerary.types import CachingProtocolMeta, Protocol, runtime_checkable

>>> @runtime_checkable
... class MySupportsOne(Protocol, metaclass=CachingProtocolMeta):
...   __slots__: Union[str, Iterable[str]] = ()
...   @abstractmethod
...   def one(self) -> int:
...     pass

>>> class Imposter:
...   def one(self) -> str:
...     return "one"

>>> imp: MySupportsOne = Imposter()  # type: ignore [assignment]  # properly caught by Mypy
>>> isinstance(imp, MySupportsOne)  # fool me once, shame on you ...
True

>>> MySupportsOne.excludes(Imposter)
>>> isinstance(imp, MySupportsOne)  # ... can't get fooled again
False

numerary has default overrides to correct for known oddities with native types (like our old friend, complex) and with popular libraries like numpy and sympy. Others will be added as they are identified. If I’ve missed any, or if you would like numerary to support additional number implementations out of the box, please let me know.

Performance Enhanced Protocols—A different kind of “PEP” for your step

By default, protocols frustrate runtime type-checking performance.

A lot.

numerary applies two distinct, layered optimization strategies:

  1. Cached __instancecheck__ results for numerary-defined protocols; and
  2. Optional(-ish) short-circuit type enumerations.

Cached __instancecheck__ results

To understand why numerary protocols are faster for runtime checks, it helps to understand why non-numerary protocols are so slow. At runtime (i.e., via isinstance), the default Protocol implementation delegates to _ProtocolMeta.__instancecheck__ to perform a crude comparison of an instance’s callable attributes against the protocol’s. More attributes means more comparisons. Further, it performs these comparisons … Every. Single. 🤬 ing. Time.

Protocols provided by numerary use instead CachingProtocolMeta as their meta class. CachingProtocolMeta derives from _ProtocolMeta and overrides __instancecheck__ to cache results based on instance type.

Conceptually:

>>> isinstance(1, SupportsIntegralOps)  # first check for an int is delegated to _ProtcolMeta.__instancecheck__
True
>>> isinstance(2, SupportsIntegralOps)  # cached result
True
>>> isinstance(1.0, SupportsIntegralOps)  # the first check for a float is delegated to _ProtcolMeta.__instancecheck__
False
>>> isinstance(2.0, SupportsIntegralOps)  # cached result
False

These offer significant performance improvements, especially where protocols define many methods.

--8<-- "docs/perf_supports_complex.txt"
Source: perf_supports_complex.ipy
--8<-- "docs/perf_supports_complex.ipy"

Short-circuit type enumerations

If the interface is to be used most often with native types (ints, floats, bools), an additional optimization may be had at runtime by short-circuiting protocol type-checking.

…SCU objects provide Unions for compliant types. As one example, for the aforementioned SupportsIntegralOps, numerary defines an additional interface.

SupportsIntegralOpsSCU = Union[int, bool, Integral, SupportsIntegralOps]
>> shift_left_one(Fraction(1, 2)) # type: ignore [arg-type] Traceback (most recent call last): ... AssertionError ">
>>> from numerary.types import SupportsIntegralOpsSCU

>>> def shift_left_one(arg: SupportsIntegralOpsSCU) -> SupportsIntegralOpsSCU:
...   assert isinstance(arg, SupportsIntegralOps)
...   return arg << 1

>>> shift_left_one(1)
2
>>> shift_left_one(sympify("1"))
2
>>> shift_left_one(Fraction(1, 2))  # type: ignore [arg-type]
Traceback (most recent call last):
  ...
AssertionError

Where do …SCU protocols help? In a word, beartype. beartype is awesome. Its author is even awesomer.2 More generally, runtime checkers that inspect and enforce annotations may benefit from short-circuiting where protocol validation is expensive.

Unions are also useful when trying to accommodate non-compliant primitives that fail static type-checking, but will work anyway at runtime. floats in Python versions prior to 3.9 are an excellent example, because they officially lacked __floor__ and __ceil__ methods, but were registered with the numeric tower and worked just fine with math.floor and math.ceil.

How does numerary’s SupportsFloorCeil deal with this situation? Not very well, unfortunately, at least not on its own.

>>> import math, sys
>>> from numerary.types import SupportsFloorCeil

>>> def my_dumb_floor_func(arg: SupportsFloorCeil) -> int:
...   assert isinstance(arg, SupportsFloorCeil)  # will work, even for floats, thanks to default overrides
...   return math.floor(arg)  # type: ignore [arg-type]  # doesn't understand SupportsFloorCeil

>>> float_val: float = 1.6180339887
>>> # For illustration only until 
   
     is fixed
   
>>> if sys.version_info < (3, 9):
...   my_dumb_floor_func(float_val)  # type: ignore [arg-type]  # still results in a Mypy error for Python version <3.9
... else:
...   my_dumb_floor_func(float_val)  # validates
1

…SCU Unions allow a work-around for the static type-checking issue.

>>> from numerary.types import SupportsFloorCeil, SupportsFloorCeilSCU, floor
>>> SupportsFloorCeilSCU  # float is included here
typing.Union[int, float, bool, numbers.Real, numerary.types.SupportsFloorCeil]

>>> import sys
>>> def my_floor_func(arg: SupportsFloorCeilSCU) -> int:
...   assert isinstance(arg, SupportsFloorCeil)
...   return floor(arg)

>>> my_floor_func(float(1.2))  # works in 3.7+
1

This is largely a contrived example, since math.floor and math.ceil happily accept SupportsFloat, but it is useful for illustration.

Limitations

There are some downsides, though. (Aren’t there always?)

Sometimes protocols are too trusting

Protocols trust numeric tower registrations. TODO(@posita): Is this really true? But sometimes, out there in the real world, implementations lie.

Consider:

>> import sympy.core.numbers >>> pants_on_fire = sympy.core.numbers.Integer(1) >>> isinstance(pants_on_fire, Integral) 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 >>> real_imag.real Traceback (most recent call last): ... AttributeError: 'One' object has no attribute 'real' ">
>>> from numbers import Integral
>>> hasattr(Integral, "real") and hasattr(Integral, "imag")
True
>>> import sympy.core.numbers
>>> pants_on_fire = sympy.core.numbers.Integer(1)
>>> isinstance(pants_on_fire, Integral)
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
>>> 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.

>>> 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, SupportsNumeratorDenominator’s numerator and denominator properties will match sage.rings.integer.Integer’s similarly named functions. 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 may3 not be caught by Mypy.

>>> class SageLikeRational:
...   def __init__(self, numerator: int, denominator: int = 1):
...     self._numerator = numerator
...     self._denominator = denominator
...   def numerator(self) -> int:
...     return self._numerator
...   def denominator(self) -> int:
...     return self._denominator

>>> 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...>
>>> frac.numerator
29

Known warts could be cured by cache overriding as discussed above. However, to combat this particular situation, numerary provides an alternative: the SupportsNumeratorDenominatorMethods protocol and the numerator and denominator helper functions. These accommodate rational implementations like Sage’s that are mostly compliant with the exception of their respective numerator and denominator implementations.

>>> from numerary.types import numerator
>>> numerator(sage_rational1)
29
>>> numerator(frac)
29

>>> from numerary.types import SupportsNumeratorDenominatorMethods, numerator
>>> sage_rational2: SupportsNumeratorDenominatorMethods = SageLikeRational(3, 29)  # no type error
>>> numerator(sage_rational2)
3

numerary also defines:

SupportsNumeratorDenominatorMixedU = Union[
    SupportsNumeratorDenominator,
    SupportsNumeratorDenominatorMethods,
]
SupportsNumeratorDenominatorMixedT = (
    SupportsNumeratorDenominator,
    SupportsNumeratorDenominatorMethods,
)
>>> from numerary.types import SupportsNumeratorDenominatorMixedU, numerator
>>> chimera_rational: SupportsNumeratorDenominatorMixedU
>>> chimera_rational = Fraction(29, 3)  # no type error
>>> numerator(chimera_rational)
29
>>> chimera_rational = SageLikeRational(3, 29)  # still no type error
>>> numerator(chimera_rational)
3

The SupportsNumeratorDenominator* primitives provide the basis for analogous numerary.types.RationalLike* primitives, which should provide sufficient (if idiosyncratic) coverage for dealing with (seemingly mis-appropriately named) rationals.

Pass-through caching with composition is pretty sketchy

This is really getting into where the sausage is made, but full transparency is important, because CachingProtocolMeta does change how protocols are validated at runtime.

Let’s say we register an errant implementation as non-compliant using the CachingProtocolMeta.excludesmethod.

int: ... return 42 >>> float_imp = FloatImposter() >>> isinstance(float_imp, SupportsFloat) True >>> SupportsFloat.excludes(FloatImposter) >>> isinstance(float_imp, SupportsFloat) False ">
>>> from numerary.types import SupportsFloat

>>> class FloatImposter:
...   def __float__(self) -> float:
...     raise NotImplementedError("Haha! JK! @#$% you!")
...   def __int__(self) -> int:
...     return 42

>>> float_imp = FloatImposter()
>>> isinstance(float_imp, SupportsFloat)
True
>>> SupportsFloat.excludes(FloatImposter)
>>> isinstance(float_imp, SupportsFloat)
False

For composition to be ergonomic, such registration should be indelible, survive composition, and afford preference to overrides by inheritors.

>>> from numerary.types import (
...   CachingProtocolMeta, Protocol, runtime_checkable,
...   SupportsInt,
... )

>>> @runtime_checkable
... class MySupportsFloatInt(
...   SupportsFloat, SupportsInt,
...   Protocol, metaclass=CachingProtocolMeta,
... ):
...   pass

>>> isinstance(float_imp, MySupportsFloatInt)  # composition picks up override from base
False

>>> SupportsFloat.reset_for(FloatImposter)  # base resets override
>>> isinstance(float_imp, SupportsFloat)
True
>>> isinstance(float_imp, MySupportsFloatInt)  # picks up base’s changes
True

>>> MySupportsFloatInt.excludes(FloatImposter)  # composition overrides
>>> isinstance(float_imp, MySupportsFloatInt)
False
>>> SupportsFloat.includes(FloatImposter)  # base changes
>>> isinstance(float_imp, FloatImposter)
True
>>> isinstance(float_imp, MySupportsFloatInt)  # composition remains unchanged
False

>>> MySupportsFloatInt.reset_for(FloatImposter)  # removes override in composition
>>> isinstance(float_imp, MySupportsFloatInt)  # base is visible again
True
>>> SupportsFloat.excludes(FloatImposter)
>>> isinstance(float_imp, MySupportsFloatInt)  # base’s changes are visible to composition again
False

For this to work under the current implementation, we cannot rely exclusively on the standard library’s implementation of __instancecheck__, since it flattens and inspects all properties (with some proprietary exceptions) of all classes in the inheritance tree (in order of the MRO). In practical terms, this means one can’t easily delegate to an ancestor’s __instancecheck__ method and a protocol’s cache is effectively hidden from its progeny. In other words, leaning on the default behavior would require one to register exceptions with every inheritor. That would suck, so let’s not do that.

However, overriding the behavior is problematic, because the standard library uses a non-public function called _get_protocol_attrs to perform its attribute enumeration. We certainly don’t want to re-implement protocol runtime checking from scratch. (At least not yet.)

CachingProtocolMeta tries to work around this by importing _get_protocol_attrs and performing some set arithmetic to limit its evaluation to directly defined attributes, and then delegating isinstance evaluation to its __base__ classes. In doing so, it picks up its bases’ then-cached values, but at the cost of re-implementing the attribute check as well as taking a dependency on an implementation detail of the standard library, which creates a fragility. Further, for post-inheritance updates, CachingProtocolMeta implements a simplistic publish/subscribe mechanism that dirties non-overridden caches in inheritors when member protocols caches are updated. That’s completely off the beaten path and there are probably some gremlins hiding out there.

One subtlety is that the implementation deviates from performing checks in MRO order (and may perform redundant checks). This is probably fine as long as runtime comparisons remain limited to crude checks whether attributes merely exist. It would likely fail if runtime checking becomes more sophisticated, at which time, this implementation will need to be revisited. Hopefully by then, we can just delete numerary as the aspirationally unnecessary hack it is and move on with our lives.

(See the implementation for details.)

License

numerary is licensed under the MIT License. See the included LICENSE file for details. Source code is available on GitHub.

Installation

Installation can be performed via PyPI.

% pip install numerary
...

Alternately, you can download the source and install manually.

% git clone https://github.com/posita/numerary.git
...
% cd numerary
% python -m pip install .  # -or- python -c 'from setuptools import setup ; setup()' install .
...

Requirements

numerary requires a relatively modern version of Python:

It has the following runtime dependencies:

numerary will opportunistically use the following, if available at runtime:

  • beartype for yummy runtime type-checking goodness (0.8+) Bear-ified™

If you use beartype for type-checking your code that interacts with numerary, but don’t want numerary to use it internally (e.g., for some strange reason), set the NUMERARY_BEARTYPE environment variable to a falsy4 value before numerary is loaded.

See the hacking quick-start for additional development and testing dependencies.

Customers numerary-encumbered

  • dyce - a pure-Python library for modeling arbitrarily complex dice mechanics and mother birthing code base of numerary!
  • The next one could be you! 👋

Do you have a project that suffers problems made slightly less annoying by numerary? Let me know, and I’ll promote it here!

And don’t forget to do your part in perpetuating gratuitous badge-ification!


As of version 0.4.1, ``dyce`` is
[![numerary-encumbered](https://raw.githubusercontent.com/posita/numerary/master/docs/numerary-encumbered.svg)][numerary-encumbered]!
[numerary-encumbered]: https://posita.github.io/numerary/ "numerary-encumbered"
..
    reStructuredText - see https://docutils.sourceforge.io/docs/ref/rst/directives.html#image

As of version 0.4.1, ``dyce`` is |numerary-encumbered|!

.. |numerary-encumbered| image:: https://raw.githubusercontent.com/posita/numerary/master/docs/numerary-encumbered.svg
   :align: top
   :target: https://posita.github.io/numerary/
   :alt: numerary-encumbered
numerary-encumbered! ">

As of version 0.4.1, <code>dycecode> is <a href="https://posita.github.io/numerary/"><img
  src="https://raw.githubusercontent.com/posita/numerary/master/docs/numerary-encumbered.svg"
  alt="numerary-encumbered"
  style="vertical-align: middle;">a>!

Footnotes

  1. You know, super weird, off-the-wall shit, like members of the numeric tower, or standard library primitives that remain non-members for some 🤬 ed up reason, or legitimate non-members because they predate PEP 3141 and conforming would amount to breaking changes, or—I don’t know—oodles of libraries and applications that have been around for literally decades that bring huge value to vast scientific and mathematic audiences, but whose number primitives break type-checking if one abides by the ubiquitous bum steer, “I don’t have any experience trying to do what you’re doing, but just use float, bro.”

    Because, hey, 🤬 numbers! Am I right?

  2. I acknowledge that the subject of who is awesomer, beartype or the man who made it, is hotly contested.

  3. I say may because I don’t really understand how Sage’s number registrations work.

  4. I.E., one of: 0, off, f, false, and no.

Comments
  • help combining itertools' takewhile and count

    help combining itertools' takewhile and count

    testing typing some of my stuff with numerary I came across this example

    from typing import Union, Any, Iterable
    from numerary import RealLike, IntegralLike
    from itertools import takewhile, count
    
    NumberType = Union[RealLike,IntegralLike]
    
    def irange(start:NumberType, stop:NumberType, step:NumberType=1) -> Iterable[NumberType]:   
        """Simple iterador para producir valores en [start,stop)"""
        return takewhile(lambda x: x<stop, count(start,step))                 
    
    

    mypy said this

    C:\Users\ThecnoMacVZLA\Desktop\mypy_tests>mypy test3.py
    test3.py:9: error: Argument 2 to "takewhile" has incompatible type "count[SupportsFloat]"; expected "Iterable[RealLike[Any]]"
    Found 1 error in 1 file (checked 1 source file)
    
    C:\Users\ThecnoMacVZLA\Desktop\mypy_tests>
    

    I can make mypy happy by changing the result type to Iterable[Any].

    What would be a better way to type hint this?

    opened by copperfield42 13
  • Help numerary help @beartype (help numerary) by porting CachingProtocolMeta to beartype

    Help numerary help @beartype (help numerary) by porting CachingProtocolMeta to beartype

    Original proposal attributed to @leycyc. From https://github.com/posita/numerary/issues/7#issuecomment-1017219264:

    Diversionary trainwreck alert: you have implemented something suspiciously fast and dangerously beautiful with the homegrown private caching protocol API (HPCPAPI) that is the beating heart of numerary. Would incorporating that API directly into beartype itself be feasible?

    Here is what I am cogitating. typing.Protocol is slower than good ol' Southern molasses. beartype.typing.Protocol is currently just a trivial alias for typing.Protocol and thus also slower than that foodstuff. beartype.typing.Protocol doesn't need to be a trivial alias, however; in theory, beartype.typing.Protocol could be redefined to be your HPCPAPI. Every problem is now solved. Structural subtyping is now genuinely usable.

    There's a caveat. If caching dramatically increases space complexity for downstream consumers in unexpected ways, the userbase will abandon @beartype after doxxing me on 4chan. That's bad. You've probably already tackled this concern (...maybe with an internal LRU cache?). If so, I have no remaining concerns and (with your generous permission and all the glory attributed directly to you as I abase myself before your complex throne of head-spinning protocols) will shortly assimilate your HPCPAPI into beartype.typing.Protocol. That's good.

    Filing separately because #7 deals with a broader issue, and I got distracted by this shiny object.

    opened by posita 10
  • RealLike doesn't detect a class implementing numbers.Real

    RealLike doesn't detect a class implementing numbers.Real

    consider this quick and dirty example

    from numerary import RealLike
    from numbers import Real
    
    class MyNumber(Real):
        
        def __init__(self,value=0):
            self.value = value
            
        def __repr__(self):
            return f"{type(self).__name__}({self.value})"
           
        def __float__(self):
            return type(self)(self.value)
    
        def __neg__(self):
            return type(self)(self.value)
    
        def __rtruediv__(self, otro):
            return type(self)(self.value)
    
        def __pos__(self):
            return type(self)(self.value)
    
        def __abs__(self):
            return type(self)(self.value)
    
        def __rmul__(self, otro):
            return type(self)(self.value)
    
        def __floor__(self):
            return type(self)(self.value)
    
        def __eq__(self, otro):
            return type(self)(self.value)
    
        def __floordiv__(self, otro):
            return type(self)(self.value)
    
        def __le__(self, otro):
            return type(self)(self.value)
    
        def __mul__(self, otro):
            return type(self)(self.value)
    
        def __radd__(self, otro):
            return type(self)(self.value)
    
        def __add__(self, otro):
            return type(self)(self.value)
    
        def __truediv__(self, otro):
            return type(self)(self.value)
    
        def __pow__(self, otro):
            return type(self)(self.value)
    
        def __rfloordiv__(self, otro):
            return type(self)(self.value)
    
        def __round__(self, ndigits=None):
            return type(self)(self.value)
    
        def __rmod__(self, otro):
            return type(self)(self.value)
    
        def __rpow__(self, otro):
            return type(self)(self.value)
    
        def __lt__(self, otro):
            return type(self)(self.value)
    
        def __ceil__(self):
            return type(self)(self.value)
    
        def __mod__(self, otro):
            return type(self)(self.value)
    
        def __trunc__(self):
            return type(self)(self.value)
    
    n = MyNumber(23)
    
    print(n)
    
    assert isinstance(n,Real),"isn't a Real"
    assert isinstance(n,RealLike),"isn't a RealLike"
    
    

    gives

    MyNumber(23)
    Traceback (most recent call last):
      File "C:\Users\ThecnoMacVZLA\Desktop\mypy_tests\mynumber.py", line 89, in <module>
        assert isinstance(n,RealLike),"isn't a RealLike"
    AssertionError: isn't a RealLike
    
    

    I wonder why?

    opened by copperfield42 9
  • Sphinx/autodoc reports metaclass conflict

    Sphinx/autodoc reports metaclass conflict

    Hi again 👋

    When adding numerary to phantom-types I forgot to update the dependencies of the docs build, which left two sections empty 😬 I'll need to work on the CI so that it blows up earlier for that, and obviously fix the missing imports.

    However, as I'm trying to reproduce this failure locally (was a silent warning in CI), I get the traceback below. It seems to indicate that numerary has a metaclass conflict, which confuses me profoundly since both your and my test suites are passing. It's also not reproduced when I in the same environment fire up a REPL and do from phantom.sized import *.

    WARNING: autodoc: failed to import module 'sized' from module 'phantom'; the following exception was raised:
    Traceback (most recent call last):
      File "/Users/annevoab/.pyenv/versions/docs/lib/python3.10/site-packages/sphinx/ext/autodoc/importer.py", line 32, in import_module
        return importlib.import_module(modname)
      File "/Users/annevoab/.pyenv/versions/3.10.0/lib/python3.10/importlib/__init__.py", line 126, in import_module
        return _bootstrap._gcd_import(name[level:], package, level)
      File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
      File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
      File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
      File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
      File "<frozen importlib._bootstrap_external>", line 883, in exec_module
      File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
      File "/Users/annevoab/projects/phantom-types/src/phantom/sized.py", line 27, in <module>
        from numerary.types import RealLike
      File "/Users/annevoab/.pyenv/versions/docs/lib/python3.10/site-packages/numerary/__init__.py", line 13, in <module>
        from .types import *  # noqa: F401,F403
      File "/Users/annevoab/.pyenv/versions/docs/lib/python3.10/site-packages/numerary/types.py", line 353, in <module>
        class SupportsAbs(
    TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
    

    It seems very likely that isn't really an issue with numerary but rather with my sphinx/autodoc setup, but I it seemed worth reaching out here in case someone has ran into similar issues. Feel free to instantly close this though as I doubt it's actionable within numerary.

    opened by antonagestam 9
  • AttributeError: module 'numpy' has no attribute 'float128'. Did you mean: 'float16'?

    AttributeError: module 'numpy' has no attribute 'float128'. Did you mean: 'float16'?

    after just coming across this library, I just pip installed it and the first thing I come across is

    >>> import numerary
    Traceback (most recent call last):
      File "<pyshell#35>", line 1, in <module>
        import numerary
      File "C:\Python3.10.5\lib\site-packages\numerary\__init__.py", line 13, in <module>
        from .types import *  # noqa: F401,F403
      File "C:\Python3.10.5\lib\site-packages\numerary\types.py", line 1712, in <module>
        numpy.float128,
      File "C:\Python3.10.5\lib\site-packages\numpy\__init__.py", line 315, in __getattr__
        raise AttributeError("module {!r} has no attribute "
    AttributeError: module 'numpy' has no attribute 'float128'. Did you mean: 'float16'?
    >>> 
    
    

    versions: python: 3.10.5 numpy: 1.22.4

    opened by copperfield42 7
  • Feature proposal: Expose CachingProtocolMeta

    Feature proposal: Expose CachingProtocolMeta

    Hi posita 👋

    I'm about to start using CachingProtocolMeta in phantom-types, to define narrower parameter types for comparison et cetera. Would you be willing to consider a PR that exposes (with __all__) and documents CachingProtocolMeta?

    opened by antonagestam 4
  • `TypeVar(…, bound=RealLike)` creates problems

    `TypeVar(…, bound=RealLike)` creates problems

    First hinted at by @antonagestam in https://github.com/antonagestam/phantom-types/pull/179#issue-1111676009, which led to an interesting work-around that numerary probably shouldn't impose, if it can avoid it.

    opened by posita 3
  • Separate `SupportsIntegralOps` into `SupportsBitLogic` and `SupportsBitShifting` (or something)

    Separate `SupportsIntegralOps` into `SupportsBitLogic` and `SupportsBitShifting` (or something)

    We probably want to tease out &, |, ^, ~ into their own thing. There are enough primitives that support logical operators without supporting bit-shifting (e.g., enum.Flag).

    invalid 
    opened by posita 1
  • False positive on Sympy primitives and `SupportsRealImag`

    False positive on Sympy primitives and `SupportsRealImag`

    Sympy primitives register with the numeric tower, but they do not provide real or imag, but they do provide as_real_imag. Is there a way we can deal with that effectively (perhaps similarly to the numerator and denominator helper functions)?

    opened by posita 0
  • Merge `SupportsFloor` and `SupportsCeil` into `SupportsFloorCeil`

    Merge `SupportsFloor` and `SupportsCeil` into `SupportsFloorCeil`

    I think keeping these separate these makes about as sense as splitting up SupportsNumeratorDenominator* or SupportsRealImag. I can't find a single instance of something supporting __floor__, but not __ceil__ (or vice versa).

    opened by posita 0
  • `numpy` primitives no longer validate on assignment

    `numpy` primitives no longer validate on assignment

    Apparently, numpy upped its typing game, which is cool, but it makes it very hard to validate using something as rudimentary as numerary. Currently, the work-around I've found is something like the following:

    # workaround.py
    from numerary.types import SupportsFloorCeil
    import numpy
    
    uint8_val_typed: SupportsFloorCeil = numpy.uint8(2)  # fails
    uint8_val_asserted = numpy.uint8(2)
    assert isinstance(uint8_val_asserted, SupportsFloorCeil)  # works
    reveal_type(uint8_val_asserted)  # workaround.<subclass of "unsignedinteger" and "SupportsFloorCeil">
    
    opened by posita 1
  • Help @beartype help numerary

    Help @beartype help numerary

    Greetings and salacious salutations, fellow wayward traveller on the congested superhighway of questionable human experience. Per beartype/beartype#84, I'd love to promote numeric health in downstream code by elevating numerary above lesser and baser alternatives for type-checking numbers with @beartype.

    Python numbers are a fathomless well of squalid darkness. I need you to man the lonely walls with me. I have no idea what I do. You do. Together, let us bring light to the dank codebases that think accepting a bool as an int is good API practice.

    Consider this hypothetical FAQ entry with your keen hawk eye:

    How do I type-check numbers...?

    tl;dr: Prefer the third-party numerary package unless you have a compelling reason not to (e.g., you either don't want to add an additional mandatory runtime dependency to your Jenga-like app stack or you do but one of the use cases below already provides a tighter fit).

    "Numbers" means many things to many people. Some people think booleans are numbers. Technically, these people are not wrong. Pragmatically, these people are wrong. Don't hire these people.

    Stop! Disambiguate and listen. You really want to type-check...

    ...builtin numeric types covariantly?

    You want to type-check int, float, or complex numbers including instances of subclasses of those types? O frabjous day! This is the trivial case, because beartype already does exactly what you want out-of-the-box.

    Just annotate with any builtin numeric type as is: e.g.,

    @beartype
    def fawlty_towers(rare_diseases: int) -> int:
        return rare_diseases ^ 0xDEADBEEF
    

    Be forewarned that any bool is also an int and thus satisfies the above API. If you don't want that, spoiler alert: you don't want that the next question is your codebase's BFFL.

    ...builtin numeric types invariantly?

    Something, something, something. I got exhausted and fell down onto the bed in a dishevelled heap here. This is the toll that type-checking numbers takes on the unprepared body, @posita.

    Don't let this happen to you.

    it happened

    opened by leycec 10
  • Bring Sage primitives into the testing fold, if we can

    Bring Sage primitives into the testing fold, if we can

    We already opportunistically test (and coerce) numpy and sympy. Sage is currently modularizing its monolith. Is there a package that falls out of that small enough for us to include sage.rings.integer.Integer et al.? That way we could use those directly in testing, including accommodating numerator/denominator quirkiness.

    opened by posita 0
A redesign of our previous Python World Cup, aiming to simulate the 2022 World Cup all the way from the qualifiers

A redesign of our previous Python World Cup, aiming to simulate the 2022 World Cup all the way from the qualifiers. This new version is designed to be more compact and more efficient and will reflect the improvements in our programming ability.

Sam Counsell 1 Jan 7, 2022
Tie together `drf-spectacular` and `djangorestframework-dataclasses` for easy-to-use apis and openapi schemas.

Speccify Tie together drf-spectacular and djangorestframework-dataclasses for easy-to-use apis and openapi schemas. Usage @dataclass class MyQ

Lyst 4 Sep 26, 2022
Secret santa is a fun and easy way to get together with your friends and/or family with a gift for them.

Secret Santa What is Secret Santa? Secret santa is a fun and easy way to get together with your friends and/or family with a gift for them. The idea i

null 2 Dec 6, 2021
Ikaros is a free financial library built in pure python that can be used to get information for single stocks, generate signals and build prortfolios

Ikaros is a free financial library built in pure python that can be used to get information for single stocks, generate signals and build prortfolios

Salma Saidane 64 Sep 28, 2022
A Python script to parse Fortinet products serial numbers, and detect the associated model and version.

ParseFortinetSerialNumber A Python script to parse Fortinet products serial numbers, and detect the associated model and version. Example $ ./ParseFor

Podalirius 10 Oct 28, 2022
Team Curie is a group of people working together to achieve a common aim

Team Curie is a group of people working together to achieve a common aim. We are enthusiasts!.... We are setting the pace!.... We offer encouragement and motivation....And we believe TeamWork makes the DreamWork.

null 4 Aug 7, 2021
An OBS script to fuze files together

OBS TEXT FUZE Fuze text files and inject the output into a text source. The Index file directory should be a list of file directorys for the text file

SuperZooper3 1 Dec 27, 2021
Table (Finnish Taulukko) glued together to transform into hands-free living.

taulukko Table (Finnish Taulukko) glued together to transform into hands-free living. Installation Preferred way to install is as usual (for testing o

Stefan Hagen 2 Dec 14, 2022
A complete python calculator with 2 modes Float and Int numbers.

Python Calculator This program is made for learning purpose. Getting started This Program runs using python, install it via terminal or from thier ofi

Felix Sanchez 1 Jan 18, 2022
Process RunGap output file of a workout and load data into Apple Numbers Spreadsheet and my website with API calls

BSD 3-Clause License Copyright (c) 2020, Mike Bromberek All rights reserved. ProcessWorkout Exercise data is exported in JSON format to iCloud using

Mike Bromberek 1 Jan 3, 2022
A ULauncher/Albert extension that supports currency, units and date time conversion, as well as a calculator that supports complex numbers and functions.

Ulauncher/Albert Calculate Anything Ulauncher/Albert Calculate Anything is an extension for Ulauncher and Albert to calculate things like currency, ti

tchar 67 Jan 1, 2023
A practice program to find the LCM i.e Lowest Common Multiplication of two numbers using python without library.

Finding-LCM-using-python-from-scratch Here, I write a practice program to find the LCM i.e Lowest Common Multiplication of two numbers using python wi

Sachin Vinayak Dabhade 4 Sep 24, 2021
A check numbers python module

Made with Python3 (C) @FayasNoushad Copyright permission under MIT License License -> https://github.com/FayasNoushad/Numbers/blob/main/LICENSE Deplo

Fayas Noushad 3 Nov 28, 2021
A numbers check python package

A numbers check python package

Fayas Noushad 3 Nov 28, 2021
A numbers extract from string python package

Made with Python3 (C) @FayasNoushad Copyright permission under MIT License License -> https://github.com/FayasNoushad/Numbers-Extract/blob/main/LICENS

Fayas Noushad 4 Nov 28, 2021
Validate UC alumni identifier numbers with Python 3.

UC number validator Validate UC alumni identifier numbers with Python 3. Getting started Install the library with: pip install -U ucnumber Usage from

Open Source eUC 1 Jul 7, 2021
A python library what works with numbers.

pynum A python library what works with numbers. Prime Prime class have everithing you want about prime numbers. check_prime The check_prime method is

Mohammad Mahdi Paydar Puya 1 Jan 7, 2022
Find all solutions to SUBSET-SUM, including negative, positive, and repeating numbers

subsetsum The subsetsum Python module can enumerate all combinations within a list of integers which sums to a specific value. It works for both negat

Trevor Phillips 9 May 27, 2022
Class and mathematical functions for quaternion numbers.

Quaternions Class and mathematical functions for quaternion numbers. Installation Python This is a Python 3 module. If you don't have Python installed

null 3 Nov 8, 2022