Consider a function that takes number of retries as a parameter. Number of retries should type check as int. But maybe you want to allow an infinite number of retries. You could accomplish this by annotating retries: int | float and passing retries=float("inf") or retries=math.inf, but it would be nice if you could just pass int("inf") and not need to change the type annotation. This is punctuated by the fact that int | float is actually too broad, you don’t want to accept 3.5 (though you don’t want to accept -3 either which is indeed an int…). You really want something like int | Literal[float("inf")] or Literal[math.inf], but these are not actually valid Literal constructions. I’m not sure if there is a literal inf in the language?
I wonder if this is a bad idea because infinities are not supported by the underlying integer data structure, but I’m not sure, and I figured this would be quick to litigate either way and I’m curious to see what people think. Maybe this could somehow be handled with existing typing features or a new typing feature.
Perhaps I’m missing a better way to type annotate a parameter that accepts positive integer or positive infinite numbers. One alternative would be to introduce an infinite sentinel so that you have retries = int | InfSentinel or retries = int | type(InfSentinel) depending on how the sentinel is implemented. Or you could use None as the sentinel with the major downside that readers would interpret retries=None to be the same as retries=0.
I think integer infinity has been suggested a few times for various situations (IIRC also for min/max) but never made it into the language, because it’s just too esoteric, and there’s no good way to represent it except as a magical singleton that is treated special by both int and float objects.
Pragmatically, for your example, a max retries of 999_999_999 (or 1_000_000_000 if you’re into base-one probably works just fine.
There’s been float inf and -inf for a long while, and their semantics are as good as it gets.
The OP is asking for unlimited bounds for cases like min() and max(), which probably involves only comparisons.
The esoteric parts of floating point, that involve INF,-INF, NAN, 0, and -0, should be easier to be skipped for int.
Constants like MAXINT were present in Pascal and other languages. Python improved it all with integers not constrained by a number of bytes. But MAXINT is still missing.
Using 999_999_999 is not good enough for current computing power and dataset sizes (999_999_999 is less than 8_100_000_000, the current world polulation).
EDIT:
Another problem with 999_999_999 (or any larger int) is that it takes a considerable number of bytes for a comparison that would be simple if there was a MAXINT.
The following are provisionally disallowed for simplicity. We can consider allowing them in future extensions of this PEP.
Floats: e.g. Literal[3.14]. Representing Literals of infinity or NaN in a clean way is tricky; real-world APIs are unlikely to vary their behavior based on a float parameter.
MAXINT is equal to the highest possible integer. It is still a finite number. In Python, there is no such maximum (although as an implementation detail, the GMP library is limited to something of the order of 2**2**37 - but that number would require a stupid amount of RAM to represent), so it wouldn’t make sense to have a MAXINT.
A value of “infinity” (which, side note, doesn’t actually make sense mathematically; infinity is not a value, it’s a concept, and generally only shows up in places like limits) is one that compares greater than ANY finite integer or float. I’ve often used float(“inf”) for this purpose, though, and it’s usually not a problem to have that in a context where you otherwise are working with integers. In my opinion, this is really a typing question (“how can I annotate that this is either an integer or float(‘inf’)”) rather than a need for an actual integer that compares greater than every other integer.
I personally always used None for this purpose, i.e. retry_limit: int | None. This does ofcourse require extra code to handle, for example internally replacing None with float("inf"), but IMO it does read quite nicely.
You may be interested in my implementation of “extended integers”. If you were to use this, you would annotate your parameters ExtendedIntegral and then you can:
simply compare elements to int_inf and -int_inf or do ordinary comparisons (counter < value), and
pass integers as before and have them support instance checks.
In my opinion, your sentinel idea is roughly the same, but has slightly poorer separation of concerns.
My personal feeling is that using a sentinel is better for readers than using a really large magic number.
I’m not sure this would be a great addition to Python since it’s a fairly rare problem. Nevertheless, if it were added to Python, I think it would have to be a new class that sits between Real and Integral. I don’t think it’s fair to expect every function that accepts an int to have to support a new “infinity” object.
Thanks for the context. Yeah, I don’t claim this isn’t esoteric. The 999_999_999 strategy would be a little annoying because it forces the caller to think: “I want this to retry so many times that it will never matter for my caller, what is the biggest number I can pick here?” when the intent (and code) is really: “I want to bypass the logic for breaking out of the retry loop”.
Yes, I think this is the right take. I don’t necessarily need integers to be extended in such a way that they can handle comparisons with infinity (though that would be nice), I’m still happy to special case if the caller passes in retries=int_inf within the function. I mostly just want a way to annotate like you said.
Renaming the parameter retry_limit instead of retries makes the None semantics better for this.
Nice. I think this does accomplish everything I’m looking for.
This seems reasonable to me. I suspect extending the regular integers to include infinity would possibly break existing code that doesn’t currently worry what would become a new type of edge case.
If you want a clean API, just use None and write the two extra lines of code in your implementation. That’s what everything taking a float timeout uses — not math.inf.
It’s fair. retries=None meaning infinite retries is confusing, you would interpret it as retries=0 so that approach didn’t immediately sound good. But renaming the parameter to max_retries=None is better because it’s kind of like saying “there is no limit to the number of retries”.
I figured the vetting on this would be quick. I think the linked extended integers pypi package does precisely what is being request, so if I or anyone really wants something with the semantics of finite integers + an infinite integer then they can use that.
Like a lot of the ideas here, I think the existence of the extended integers package shows that it is a useful feature that people would use, it is only a question of if the usefulness warrants inclusion, and the associated costs of that inclusion, in the standard library.
Not sure what you mean. Is 2**N-1 a magic number? Or do you mean there’s some magic number that behaves like an infinity, which is completely different from MAXINT in concept? Or that there’s a magic number that, in the API in question, indicates “no limit”?
It makes very good sense as a comparison sentinel though, since it compares greater than any finite value.
Thank you for quote-mining me. That is NOT WHAT I SAID. I said:
You split off the “mathematically”, and that completely changed the meaning of the quote. In mathematics, “infinity” most certainly is NOT a value, it is a concept, one which shows up in limits.
But other numbers really ARE values. Do you see the problem when you try to take my words out of context?
Is it that hard to read one entire sentence and respond to it? By breaking it into four pieces like this, you managed to completely miss everything I was saying.
At least you had the decency to do it all in one post, so people can see exactly what happened, But I still very much do not appreciate this.
While this is not the case in the integers, there definitely are number systems where infinity is a number like all the others. But they are kinda esoteric and pythons integers do not model them.
If an “integer infinity” value was added it would just be a singleton like any other, so you might as well use None IMO.
Both the concept of value as member of a type, or the common-language notion (not a mathematical concept) of value as output of a function, or particularization of a quantified variable, etc, infinity can be both.
Either you have a special notion of “value”, or what you are saying is devoid of meaning. What do you call “value”? Note that I don’t think you have a special notion of value, only that I think you haven’t thought carefully whether it applies and are repeating mechanically an idea that have heard others say, but that is actually incorrectly expressed.
You shouldn’t get irritated. You are really saying nonsense. As a mathematician, my intention is to educate about why that often repeated phrase “infinity is not a number” (or in this case “value”) is meaningless. So, it doesn’t bring any weight into a reasoning.