Code Monkey home page Code Monkey logo

Comments (22)

gvanrossum avatar gvanrossum commented on July 20, 2024 1

I don't really have anything to add to this conversation, except that I believe that historically when we wrote and accepted PEP 484, I personally believed that we were specifying that int was a subtype of float (and float a subtype of complex). Until today I wasn't aware that the spec actually says that e.g. the type float should be interpreted, in certain contexts, as float | int. This is neither here nor there, it's just an admission of my flawed understanding of the subtleties here at the time.

from typing.

gvanrossum avatar gvanrossum commented on July 20, 2024 1

Why allow passing 42 to an API which requires a float?

Because Python users have been doing that for the last 30 years.

from typing.

gvanrossum avatar gvanrossum commented on July 20, 2024 1

It could be also worth calling out that even though consistent subtyping/compatibility does allow int where float is expected, it is in fact unsound:

def f(x: float) -> bool:
  return x.is_integer()

f(42)  # type checks, but fails at runtime

This passes for me, BTW. (Apparently this was a CPython bug and it was fixed in 3.12.)

The same applies to Eric's example using f.hex() -- when that was added to float it should also have been added to int. Same for other examples you may find of methods that are supported by float but not by int.

Could you give an example which stops working if 42 is statically modelled as just int with no subtyping/consistent subtyping connection to float?

This sounds like a trick question. I'd say that in 3.12 the above example works at runtime, but it seems you would like it to fail the static check? Why? Because it fails at runtime in 3.11?

from typing.

carljm avatar carljm commented on July 20, 2024 1

Perhaps it is orthogonal to the core concern of this issue, but if we amend this paragraph of the spec, I think it would also be better to use clearer terms for the relation that this special case applies to (e.g. "consistent with"), rather than discussing only argument annotations, as the current text does, which leaves underspecified whether the described behavior is also supposed to apply to non-argument annotations (e.g. x: int = 1; y: float = x).

from typing.

superbobry avatar superbobry commented on July 20, 2024

It could be also worth calling out that even though consistent subtyping/compatibility does allow int where float is expected, it is in fact unsound:

def f(x: float) -> bool:
  return x.is_integer()

f(42)  # type checks, but fails at runtime

from typing.

AlexWaygood avatar AlexWaygood commented on July 20, 2024

It could be also worth calling out that even though consistent subtyping/compatibility does allow int where float is expected, it is in fact unsound:

def f(x: float) -> bool:
  return x.is_integer()

f(42)  # type checks, but fails at runtime

I think that's already touched on where the spec says:

Python’s numeric types complex, float and int are not subtypes of each other

But perhaps it could be more explicitly stated, yes

from typing.

superbobry avatar superbobry commented on July 20, 2024

They are not subtypes, but they are part of the consistency (or rather the consistent subtyping) relation, which is what is normally used by type checkers for checking function calls. Am I interpreting the spec wording in the wrong way?

from typing.

AlexWaygood avatar AlexWaygood commented on July 20, 2024

I believe the int/float/complex special-casing that type checkers perform is unique in that the special-casing only applies to parameter annotations; they are not seen by type checkers as subtypes or consistent subtypes in contexts outside of parameter annotations. I understood the spec to be referring to this unique aspect of the way these types are understood by type checkers when it said that they "are not subtypes of each other". But you're right it could be clearer; I'm not confident that my interpretation is correct.

from typing.

superbobry avatar superbobry commented on July 20, 2024

Note that regardless of the terminology, this feature is unsound since it is not type safe generally to pass an int where a float is expected (as my admittedly artificial) snippet above demonstrates.

from typing.

AlexWaygood avatar AlexWaygood commented on July 20, 2024

A discussion on the merits and demerits of the current behaviour of type checkers in this regard is outside of the scope of this issue.

from typing.

erictraut avatar erictraut commented on July 20, 2024

The typing spec is not meant to be a tutorial or reference for users of the type system. It's a specification that clarifies how type checkers should behave. As such, I think it's unnecessary (and arguably inappropriate) for it to discuss PEP 3141, since it has no bearing on the type system.

This topic could fit within the Typing Reference section of the docs. Contributions are welcome.

even though consistent subtyping/compatibility does allow int where float is expected...

As @AlexWaygood said above, that statement is subtly incorrect. The type int is not a subtype of float in the Python type system; the spec is clear that this is the case. Instead, the spec indicates that when you use float within a type expression, a type checker should implicitly interpret that expression as though the user had specified float | int. This is an important distinction.

this feature is unsound

That's not necessarily true. It depends on the type checker implementation. If a type checker assumes that int is a subtype of float (as mypy does), then it is unsound. If a type checker assumes that any annotation float implies float | int, then it's not unsound. There is a proposal in the mypy issue tracker to change mypy to do the latter, but it hasn't been implemented.

I attempted to implement this suggestion in pyright but needed to back out part of it for compatibility reasons. See this issue for details. The problem is that there's currently no way in the type system to provide an annotation that means "only float, not float | int", so methods like int.__truediv__ cannot be annotated in a way that allows a type checker to implement soundness checks without generating a bunch of false positives.

Here's an illustrative code sample that generates a runtime error. Pyright detects this error but mypy does not currently.

Code sample in pyright playground

def func1(f: float):
    if not isinstance(f, float):
        f.hex()  # Runtime error

func1(1)

from typing.

superbobry avatar superbobry commented on July 20, 2024

Thanks for an extended reply, Eric.

The type int is not a subtype of float in the Python type system; the spec is clear that this is the case.

I think it would be useful if the spec clarified that int it is neither a subtype, nor is consistent with float. In my mind, saying that it isn't a subtype does not immediately eliminate it from the other relation.

Instead, the spec indicates that when you use float within a type expression, a type checker should implicitly interpret that expression as though the user had specified float | int.

I agree that this interpretation is sound, but I also find it confusing to treat float (and complex) as effectively a macro/syntax sugar.

Do you think we could re-evaluate this as part of the work in the spec? For example, we could explore an alternative design where literals have flexible types, so 42 would be {int, float, complex} instead of just int, and 42.0 -- {float, complex}.

from typing.

superbobry avatar superbobry commented on July 20, 2024

Looking at

def func1(f: float):
    if not isinstance(f, float):
        f.hex()  # Runtime error

func1(1)

as a user, my first reaction would be to conclude that the body of the if is unreachable. This is true for any class, so it is natural to extend that to float.

from typing.

erictraut avatar erictraut commented on July 20, 2024

Do you think we could re-evaluate this as part of the work in the spec?

The spec is a living document, and the community welcomes new proposals. If this is a topic that's important to you and you'd like to champion a modification, feel free to create a new thread in the typing forum. This is an area where backward compatibility is really important, so any such proposals will be reviewed with that in mind.

from typing.

AlexWaygood avatar AlexWaygood commented on July 20, 2024

I think it's unnecessary (and arguably inappropriate) for it to discuss PEP 3141, since it has no bearing on the type system.

I'm not sure I agree. PEP-3141 is a PEP that has been accepted for 17 years, and has not been deprecated. At runtime, Python recognises complex as a "virtual subclass" of numbers.Complex, float as a virtual subclass of numbers.Real and int as a virtual subclass of numbers.Integral. For any other stdlib module like this, we would either try to model this subtyping relationship in typeshed, or type checkers would implement special-casing to model it. It's reasonable for users to expect type checkers to understand the subtyping relationship that holds true at runtime, and it's a persistent source of surprise that type checkers do not.

I'm fully aware of the reasons why neither typeshed nor type checkers support PEP-3141, I don't believe that should change, and I don't want to restart a conversation about whether they should or not. But the decision by PEP-484 to state that PEP-3141 is a "runtime-only" numeric tower, and to instead implement a parallel "static-only" numeric tower that is understood by type checkers but is not understood by issubclass() checks at runtime -- this is, in my view, a fairly crucial design choice of the type system.

from typing.

JelleZijlstra avatar JelleZijlstra commented on July 20, 2024

I was probably the person who cut out the mention of PEP 3141 from the spec. I agree with @erictraut above that a discussion of this topic is a better fit for the user-facing type system reference (which unfortunately we haven't yet done much work on). If all we are saying is that there are no special cases for type checkers, that doesn't feel like it's worth saying in the spec.

Eric's suggestion above about the interpretation of the float/int special case is interesting and it does seem to be a more sensible interpretation than treating int directly as a subtype of float.

from typing.

AlexWaygood avatar AlexWaygood commented on July 20, 2024

If all we are saying is that there are no special cases for type checkers

From my perspective, there is a special case when it comes to PEP-3141. For all other stdlib ABCs, we've taken pains in typeshed to ensure that type checkers understand runtime virtual subclasses of those ABCs as static subtypes of those ABCs. list inherits from collections.abc.MutableSequence in typeshed, even though it doesn't at runtime; set inherits from collections.abc.MutableSet in typeshed, even though it doesn't at runtime; we pretend os.PathLike and contextlib.AbstractContextManager are protocols in typeshed, even though at runtime they're just ABCs. It's only the numbers ABCs where we deliberately don't try to model the subtyping relationship in typeshed. (And, again, I support that policy.)

from typing.

superbobry avatar superbobry commented on July 20, 2024

I thought about this a bit more, and unfortunately flexible types have the same drawbacks as existing special-casing -- they allow for unsoundness (unless supplemented with runtime type checking).

I wonder if the "right" thing to do here would be to abandon the idea of special-casing of int, float and complex altogether? The runtime type of 42 is always an int and never float nor complex. Why allow passing 42 to an API which requires a float? Moreover, the fix is always obvious -- just add .0 or j to your literal.

from typing.

gvanrossum avatar gvanrossum commented on July 20, 2024

unsoundness (unless supplemented with runtime type checking)

But we always have runtime type checking, right? AttributeError, TypeError, and so on are exactly that. So what's the unsoundness complaint about?

from typing.

superbobry avatar superbobry commented on July 20, 2024

But we always have runtime type checking, right? [...]

Raising AttributeError is not the same as saying "you called function f with an int instead of a float and that caused AttributeError such and such".

Because Python users have been doing that for the last 30 years.

I agree that the type system should be designed around existing Python code, but given that it is probably impossible to account for every Python dynamic feature in a reasonable way, there will always be compromises.

Could you give an example which stops working if 42 is statically modelled as just int with no subtyping/consistent subtyping connection to float?

from typing.

superbobry avatar superbobry commented on July 20, 2024

This sounds like a trick question.

It was not intended as one, actually.

If we can guarantee that int <: float <: complex structurally, then there is no issue with the original wording in PEP-484, because users cannot observe the difference between the types.

from typing.

gvanrossum avatar gvanrossum commented on July 20, 2024

If we can guarantee that int <: float <: complex structurally, then there is no issue with the original wording in PEP-484, because users cannot observe the difference between the types.

That would be my preferred approach (and what I had in mind when we created PEP 484, nearly 10 years ago).

from typing.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.