mypy icon indicating copy to clipboard operation
mypy copied to clipboard

int is not a Number?

Open tjltjl opened this issue 7 years ago • 83 comments

n : Number = 5 produces

test_compiler.py:18: error: Incompatible types in assignment (expression has type "int", variable has type "Number")

Which it probably shouldn't because isinstance(n, Number) == True

tjltjl avatar Apr 18 '17 08:04 tjltjl

(Talking about numbers.Number, naturally)

tjltjl avatar Apr 18 '17 08:04 tjltjl

Thank you for reporting this!

It looks like this is mostly a typeshed issue. Structursl subtyping (see PR #3132) will help, but it looks like still some changes will be necessary in typeshed, since we can't declare Number a protocol (it is too trivial currently).

@JelleZijlstra what do you think?

ilevkivskyi avatar Apr 18 '17 09:04 ilevkivskyi

Not sure we need to keep this open, if it can be fixed in typeshed once protocols land. (Maybe there should be an omnibus issue for things we can change once it lands?)

Also see PEP 484, which has this to say:

PEP 3141 defines Python's numeric tower, and the stdlib module numbers implements the corresponding ABCs ( Number , Complex , Real , Rational and Integral ). There are some issues with these ABCs, but the built-in concrete numeric classes complex , float and int are ubiquitous (especially the latter two :-).

Rather than requiring that users write import numbers and then use numbers.Float etc., this PEP proposes a straightforward shortcut that is almost as effective: when an argument is annotated as having type float , an argument of type int is acceptable; similar, for an argument annotated as having type complex , arguments of type float or int are acceptable. This does not handle classes implementing the corresponding ABCs or the fractions.Fraction class, but we believe those use cases are exceedingly rare.

gvanrossum avatar Apr 18 '17 15:04 gvanrossum

I am not too familiar with the numeric tower, but maybe we should just go with PEP 484's recommendation and avoid the numbers ABCs.

numbers.Number in particular doesn't seem very useful since it declares no members, so logically mypy shouldn't allow you to do anything with a variable declared as a Number. It looks like the others can be made into Protocols easily when that lands.

What are concrete use cases where one would use the numbers ABCs instead of int/float/complex? Maybe numpy numeric types?

JelleZijlstra avatar Apr 18 '17 15:04 JelleZijlstra

What are concrete use cases where one would use the numbers ABCs instead of int/float/complex? Maybe numpy numeric types?

It's come up a few times before, the only places I found that were using it were just trying to be hyper-correct. E.g. numpy supports it but barely mentions it in its docs.

[Warning: Luddite mode on] IMO PEP 3141 was a flight of fancy and has never produced much useful effect. Pythoneers are generally to close to practice to care about the issue, most of the time the code just works through the magic of duck typing, and when it doesn't people typically say "well that's too fancy anyways". :-)

gvanrossum avatar Apr 18 '17 15:04 gvanrossum

I'm not sure if the other ABCs beyond Number are very straightforward either. It would be an interesting exercise for somebody to write a prototype stub (using the protocol PR) that only supports one operation (such as addition) for Complex, Real and friends. It would have to work with int, float and user-defined classes -- and probably also with mixtures of those when it makes sense.

However, I don't believe that the numeric tower is going to be very useful for type checking. The current types seem to be good enough for the vast majority of use cases, and just providing another set of types for essentially the same purpose seems like unnecessary complexity. In my opinion, a much more promising (and somewhat related) project would be figuring out how to type check code using numpy.

JukkaL avatar Apr 18 '17 18:04 JukkaL

In my opinion, a much more promising (and somewhat related) project would be figuring out how to type check code using numpy.

I agree this looks very interesting. This would require some simple dependent types, since fixed size arrays/matrices are everywhere in mypy. The rules for those are non-trivial, e.g. matrix[n, m] * matrix[m, k] == matrix[n, k]. But I think fixed size arrays will be quite useful on its own even outside numpy.

ilevkivskyi avatar Apr 18 '17 18:04 ilevkivskyi

It would be really cool at least to document it a bit more clearly than the PEP 484 quote, which I read as

"You don't need to use the numbers.* classes but you can"

when it appears that the real situation is

"The numbers.* classes do not work with typing".

I (quite reasonably?) thought that isinstance(x, cls) implies that you can have the

 x : cls

type declaration.

Are there other places where this is not the case?

tjltjl avatar Apr 19 '17 04:04 tjltjl

(or even just a warning in mypy if one of the numbers.* types is used?)

tjltjl avatar Apr 19 '17 04:04 tjltjl

Just noting that I seem to have a use-case affected by this; I'm adding annotations for use w/ mypy to some code that takes latitude/longitude values; so,

def foo(lat, lng):
    # type: (?, ?) -> None

I started with float, but passing an integer is okay. That led me to Union[float, int], and subsequently #2128; however, Decimal is also okay — ideally, I just work in whatever types get input to the function and let duck typing do its thing, but Union[float, int, Decimal…] is not what I really want to say. So, numbers.Real, and that brings one to this bug.

(I agree with tjltjl's reasoning that if the specification is x: Cls, then passing a foo, where isinstance(foo, Cls), should work.)

thanatos avatar Jun 22 '17 21:06 thanatos

numbers.Real doesn't require any special actions, it will be fixed automatically when protocols PR lands (unlike numbers.Number that also might require some changes in typeshed).

ilevkivskyi avatar Jun 22 '17 21:06 ilevkivskyi

@ilevkivskyi, has that PR landed? Because I have just encountered the same problem with Mypy 0.590:

def bug(n: Real = 1) -> Real:
    return n + 1
# error: Incompatible default for argument "n" (default has type "int", argument has type "Real")

Motivating example is HypothesisWorks/hypothesis#200; we have several test strategies for some type of number within a range, and the bounding values can be specified as int/float/fraction - so it would be really nice to show Real instead of that union!

Zac-HD avatar Apr 17 '18 07:04 Zac-HD

Yes, but those types are still not labeled as protocols. I personally don't see any harm in labelling them as protocols. Note that the other type is Any for all methods, but I don't think this will introduce any additional unsafety as compared to current situation. The problem however is that Number can't be made a protocol (because it is empty), but it is a base class for all other numbers and protocols can't inherit from non-protocols, so at least for now they stay as ABCs, i.e. this will need a .register() support, see https://github.com/python/mypy/issues/2922

ilevkivskyi avatar Apr 17 '18 11:04 ilevkivskyi

One consideration around the numeric tower is that when a function returns numpy.float, it would be nice to mark it as returning numbers.Real, since

(1) I don't want to force people to fix type annotations because they happened to return a float in the future (2) You could see both numpy.float and python float returned from the same function for convenience.

pkch avatar Feb 14 '19 23:02 pkch

Raised priority to high since this is constantly coming.

ilevkivskyi avatar Aug 12 '19 13:08 ilevkivskyi

What solution do you propose?

gvanrossum avatar Aug 12 '19 13:08 gvanrossum

One possible option is to add a bit of lie to typeshed, see https://github.com/python/typeshed/pull/3108.

ilevkivskyi avatar Aug 12 '19 13:08 ilevkivskyi

Another option is to reject the use of Number etc. as types (and maybe give a link to documentation that explains why this isn't supported and what to do instead).

JukkaL avatar Aug 12 '19 14:08 JukkaL

Another option is to reject the use of Number etc. as types (and maybe give a link to documentation that explains why this isn't supported and what to do instead).

This would be a bit sad, especially taking into account this is a 12-th most liked issue on the tracker. TBH I forgot the details of the discussion, but if the typeshed attempt will work out are there any reasons not to do this?

ilevkivskyi avatar Aug 12 '19 15:08 ilevkivskyi

but if the typeshed attempt will work out are there any reasons not to do this?

I'm wondering if the types will be useful for their intended purpose. If not, the effort may be wasted.

JukkaL avatar Aug 12 '19 16:08 JukkaL

Doesn't seem like this will ever get fixed. Can you at least add a note to the user doc somewhere?

analog-cbarber avatar May 01 '20 20:05 analog-cbarber

Personally I'm not fussed that (mypy thinks) int is not a Number, because I don't have any use for Number. I am inconvenienced by the fact that (mypy thinks) float and int are not Real, though, since I now have a case where I need to use Union[Real, float] to satisfy mypy.

sjjessop avatar Jul 13 '20 17:07 sjjessop

Doesn't seem like this will ever get fixed. Can you at least add a note to the user doc somewhere?

Perhaps somebody would like to contribute an item about this to the common issues section in the documentation?

JukkaL avatar Jul 22 '20 14:07 JukkaL

Is it possible, one day, to make a MyPy plugin that can answer the question: "is float a subtype of Real?" Is it possible for plugins to be aware of ABC subtype registration?

This pattern of registering subtypes of ABCs comes up every now and then. In JAX, they have a similar registration mechanism. It would be amazing to have a JAX PyTree type that understood all of the subtypes that are registered.

For now, I have to choose between using an alias to All and using casts.

NeilGirdhar avatar Jul 22 '20 17:07 NeilGirdhar

I assume that this is also the same root issue:

import numbers
from typing import Union
FixedRational = Union[int, numbers.Rational]
def broken(n: numbers.Rational) -> int:
    return n.numerator * n.denominator
def fixed(n: FixedRational) -> int:
    return n.numerator * n.denominator
print(broken(3))
print(fixed(3))

Which produces: error: Argument 1 to "broken" has incompatible type "int"; expected "Rational" [arg-type]

I was trying to create a modified Fraction class customized to my needs, and the numbers module is fairly important to that effort. I suppose I can manually create the required Unions, but I'm not sure I understand why this is hard to fix...

cfcohen avatar Jul 26 '20 05:07 cfcohen

Stumbled into this right now, annotating a dataclass that can perfectly hold a float or a Fraction: a musical interval which can be both: a rational when we’re lucky—and then it should show to the user as a fraction—and a float when we’re not as lucky, as with almost every regular temperament. Both cases arise more or less equally often.

I suppose I can alias Union[int, Fraction, float] for a while but it would be really nice if this gets resolved in some manner. :sun_with_face:

arseniiv avatar Sep 18 '20 14:09 arseniiv

This could be solved in typeshed. Right now, types like int and float don't inherit from numbers.Integral and numbers.Real -- I don't recall if there's a technicality involved or whether it's just work (or possibly fear of slowing down everything). At runtime we "register" these virtual subclassing relationships, but static type checkers don't handle ABC.register().

PS. PEP 484 and mypy do treat int as a virtual subclass of float, so you don't need Union[int, float].

gvanrossum avatar Sep 18 '20 16:09 gvanrossum

The technicality I remember being discussed was https://github.com/python/typeshed/issues/3195 We could also make numbers.Real and numbers.Integral (but not numbers.Number) protocols.

hauntsaninja avatar Sep 18 '20 17:09 hauntsaninja

Hm, would it be so terrible if numbers.Number in typeshed didn't have __floor__ and __ceil__? Or to pretend float has them? Also, probably few would care if we made Number a protocol with the same methods as Complex -- or at least the intersection of Complex and Decimal -- it seems Decimal is the one numeric type that inherits from Number but not from Complex.

gvanrossum avatar Sep 18 '20 22:09 gvanrossum

PS. PEP 484 and mypy do treat int as a virtual subclass of float, so you don't need Union[int, float].

(Oh. Right, thanks! I even read about this thing in this same issue yesterday before posting, but for some reason forgot that entirely when started writing. :thinking:)

arseniiv avatar Sep 19 '20 16:09 arseniiv