GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Improve duck type compatibility of int with float · Issue #100268 · python/cpython

Improve duck type compatibility of int with float #100268

New issue
Copy link
New issue
Copy link
Closed
Closed
Improve duck type compatibility of int with float#100268
Copy link
Labels
topic-typingtype-featureA feature request or enhancementA feature request or enhancement

Description

@JakubTesarek
JakubTesarek
opened on Dec 15, 2022
Issue body actions

Feature or enhancement

Function arguments and variables annotated with float should not allow value of type int.

Pitch

PEP484 suggested that when an argument is annotated as having type float, an argument of type int is acceptable;. This allows this kind of typing to be valid:

x: float = 2

But int is not subtype of float and doesn't provide the same interface. Float provides methods that are not available in int:

  • is_integer
  • fromhex
  • hex

This violates LSP and is problematic especially with is_integer:

def method_requiring_whole_number_float(number: float):
    if number.is_integer():
        ...

This method clearly states that it requires float and as an author of such code, I would expect that is_integer would be available if my typing is correct.

There are workarounds (if int(number) == number:) but they render the is_integer useless as it can never be safely used.

Just adding the missing methods to int (or removing the extra methods from float) would not be valid solution as there are other problems stemming from the fact that int is not float. Eg.:

def split_whole_and_decimal(number: float) -> tuple[str, str]:
    return str(number).split('.')  # it's reasonable to expect that the output contains a dot `.` as  `float` does but `int` doesn't 

I'm proposing an errata to PEP484 that will remove when an argument is annotated as having type float, an argument of type int is acceptable;.

Linked PRs

  • gh-100268: Add is_integer method to int #100439

Activity

  1. JakubTesarek
    added
    type-featureA feature request or enhancementA feature request or enhancement
    on Dec 15, 2022
  2. AlexWaygood commented on Dec 15, 2022

    @AlexWaygood
    AlexWaygood
    on Dec 15, 2022
    Member
    More actions

    Issues like this need to be filed over at https://github.com/python/typing rather than on this issue tracker. Perhaps @JelleZijlstra or @gvanrossum can transfer it over.

    Having said that, while I agree in principle with many of your points, I doubt we'll be changing this. It's been this way for many years now, and changing it now would be very breaking for a large number of users.

  3. AlexWaygood
    added
    topic-typing
    on Dec 15, 2022
  4. JakubTesarek commented on Dec 15, 2022

    @JakubTesarek
    JakubTesarek
    on Dec 15, 2022
    Author
    More actions

    @AlexWaygood Thank you and sorry for the incorrect filing of this issue.

    I agree that this has been part of Python since the beginning of support of typing. It would be a big BC break (affecting also all my projects).

    I was thinking about sending a PR to mypy that would "fix" this using an optional switch. But then I realised that because of how the PEP484 is phrased, it would be incorrect to disallow assigning int to float. Maybe there's a way it could be rephrased so that it would allow for such option in Mypy? In current state, my PR would be almost certainly rejected.

  5. carljm
    changed the title [-]Arguments annotated with `float` should not allow value of type `int.`[/-] [+]Arguments annotated with `float` should not allow value of type `int`[/+] on Dec 15, 2022
  6. rhettinger commented on Dec 15, 2022

    @rhettinger
    rhettinger
    on Dec 15, 2022
    Contributor
    More actions

    While your premise it true, a float is not an int. I don't think this has a chance. The PEP 484 decision was intentional and pragmatic. It reflects what is done in a lot of real world code (functions that accept floats also accept ints). And subsequent to the PEP being approved, the decision proved to be useful in practice.

    To change the decision would require broad discussion and buy in and perhaps another PEP. If you want to pursue that route, I recommend starting a discussion on the forums. Just expect that it will be an uphill battle. The original decisions wasn't made lightly.

    Reacted by Alex Waygood and Carl Meyer
  7. rhettinger
    closed this as not plannedon Dec 16, 2022
  8. JelleZijlstra commented on Dec 16, 2022

    @JelleZijlstra
    JelleZijlstra
    on Dec 16, 2022
    Member
    More actions

    I agree with what was said above, but a few more thoughts:

    • mypy could add an option to disable the int/float (and complex/float) promotion, but one complication would be that typeshed stubs are written under the assumption that float includes int. So it would have to continue to apply the promotion when checking calls to stdlib functions.
    • You raise a good point about methods that exist on float but not int. Let's see if we can resolve those differences:
      • int.is_integer could be added with the trivial behavior of always returning True. This seems harmless to me but others may have objections; feel free to open an issue requesting it be added. A similar precedent is complex.__complex__, which was added in Should we define complex.__complex__ and bytes.__bytes__? #68422.
      • float.fromhex is a classmethod. If we added int.fromhex and made it accept the same format as float.fromhex, it would have to return a float, which would be weird for an int classmethod; and if it accepted a different format, we wouldn't actually help int/float interoperability.
      • float.hex returns a hex representation that is specific to floats, and it would be weird to have this method on ints but return a float-specific representation.
    Reacted by Alex Waygood, Carl Meyer and Gregory P. Smith
  9. AlexWaygood commented on Dec 16, 2022

    @AlexWaygood
    AlexWaygood
    on Dec 16, 2022
    Member
    More actions
    • int.is_integer could be added with the trivial behavior of always returning True. This seems harmless to me but others may have objections; feel free to open an issue requesting it be added. A similar precedent is complex.__complex__, which was added in Should we define complex.complex and bytes.bytes? #68422.

    I'd be happy to see this added, and I agree it would lessen some of the pain here. Another precedent is the way that int objects have fairly pointless imag and real properties. I believe this is purely so they conform a little more closely with the numeric tower in the numbers module, which specifies that Integral numbers are a subtype of Complex numbers:

    >>> x = 5
    >>> x.real
    5
    >>> x.imag
    0
  10. gvanrossum commented on Dec 16, 2022

    @gvanrossum
    gvanrossum
    on Dec 16, 2022
    Member
    More actions

    Int is supposed to be duck type compatible with float. So yes, it should have all (instance) methods of float.

    Reacted by Alex Waygood, Carl Meyer, Jakub Tesárek and Gregory P. Smith
    Reacted by Gary Yendell
  11. AlexWaygood commented on Dec 16, 2022

    @AlexWaygood
    AlexWaygood
    on Dec 16, 2022
    Member
    More actions

    Yes, fromhex() is an alternative constructor and a classmethod, so is much less relevant to LSP concerns.

  12. hauntsaninja
    changed the title [-]Arguments annotated with `float` should not allow value of type `int`[/-] [+]Improve duck type compatibility of int with float[/+] on Dec 22, 2022
  13. hauntsaninja
    reopened this on Dec 22, 2022
  14. hauntsaninja commented on Dec 22, 2022

    @hauntsaninja
    hauntsaninja
    on Dec 22, 2022
    Contributor
    More actions

    I think there's consensus that is_integer should be added to int. This is safe, straightforward and has precedent with the complex attributes, like Alex mentions. I agree that we should leave hex alone.

    This is brought up somewhat regularly on mypy's issue tracker. I think there's a little bit of an XY complaint happening here in that the actual behaviour that static typing users get confused by is isinstance(1, float) is False — and static typing users maybe don't really care about the is_integer method. Note that mypy's behaviour has recently started to reflect the runtime more accurately (see python/mypy#13781 ), which should help with these complaints.

    OP also provides a split_whole_and_decimal example, but I don't find that compelling since float.__str__ will happily spit out 1e+20

    Reacted by Alex Waygood, Jelle Zijlstra, Carl Meyer and Dave
  15. hauntsaninja
    added a commit that references this issue on Dec 22, 2022

    pythongh-100268: Add is_integer method to int

    930121e
  16. mdickinson commented on Dec 23, 2022

    @mdickinson
    mdickinson
    on Dec 23, 2022
    Member
    More actions

    For history, see also issue #70867 and the reverted PR #6121.

    Reacted by Shantanu, Gregory P. Smith and Alex Waygood
  17. mdickinson commented on Dec 23, 2022

    @mdickinson
    mdickinson
    on Dec 23, 2022
    Member
    More actions

    @hauntsaninja Do you have bandwidth to make a corresponding PR for adding is_integer to fractions.Fraction? (decimal.Decimal is more involved, and might be better left to another day).

  18. gpshead
    added a commit that references this issue on Dec 24, 2022

    gh-100268: Add is_integer method to int (#100439)

    3e46f9f
  19. gpshead commented on Dec 24, 2022

    @gpshead
    gpshead
    on Dec 24, 2022
    Member
    More actions

    Okay, int gains the method in 3.12 now (PR merged). That seemed to be the most pressing issue.

    I expect less demand for .is_integer() from Fraction and Decimal users?

    The problem adding it to int addressed was the practicality of what a : float type annotation means given that type checkers intentionally define that to implicitly mean float|int to match the expectations of most of the worlds code. I'm unaware of anything implicitly unioning Fraction or Decimal along with float or int.

    Regardless, separate PRs make sense to consider for those.

  20. hauntsaninja commented on Dec 24, 2022

    @hauntsaninja
    hauntsaninja
    on Dec 24, 2022
    Contributor
    More actions

    Thanks to everyone who reviewed the PR! I'm closing this issue, since we have no other changes planned to int (or float).

    I'm happy to open a PR adding is_integer to fractions.Fraction; opened a corresponding issue at #100488 and PR at #100489. We can discuss the merits of the addition over there!

  21. hauntsaninja
    closed this as completedon Dec 24, 2022
  22. iritkatriel
    added a commit that references this issue on Dec 28, 2022

    Merged main into regmachine branch (#50)

    fdc73cb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic-typingtype-featureA feature request or enhancementA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.