Skip to content

BUG: Shouldn't numpy's floating types (e.g. np.float_) and python's native float type be compatible? #23663

Closed

Description

@simonaltrogge

Describe the issue:

When trying to type hint functions accepting scalars, I get unexpected error messages from my type checker (Pylance). As np.float_ is a subtype of python's native float type, I would expect to be able to pass a variable with type np.float_ when a native float is expected.

Reproduce the code example:

import numpy as np

def idx(x: float) -> float:
    return x

arr = np.arange(5.0, dtype=np.float_)
sum = np.sum(arr)
idx(sum)

Error message:

Argument of type "float_" cannot be assigned to parameter "x" of type "float" in function "idx"
  "float_" is incompatible with "float" Pylance (reportGeneralTypeIssues)

Runtime information:

1.24.1
3.11.0 | packaged by conda-forge | (main, Jan 14 2023, 12:27:40) [GCC 11.3.0]
WARNING: threadpoolctl not found in system! Install it by pip install threadpoolctl. Once installed, try np.show_runtime again for more detailed build information
[{'simd_extensions': {'baseline': ['SSE', 'SSE2', 'SSE3'],
'found': ['SSSE3',
'SSE41',
'POPCNT',
'SSE42',
'AVX',
'F16C',
'FMA3',
'AVX2'],
'not_found': ['AVX512F',
'AVX512CD',
'AVX512_KNL',
'AVX512_KNM',
'AVX512_SKX',
'AVX512_CLX',
'AVX512_CNL',
'AVX512_ICL']}}]
None

Context for the issue:

This issue makes it either impossible or much harder to correctly type hint functions such that they are compatible with numpy.

Activity

  1. changed the title [-]BUG: Shouldn't numpy's floating types (e.g. `np.float_`) and python's native `float` types be compatible?[/-] [+]BUG: Shouldn't numpy's floating types (e.g. `np.float_`) and python's native `float` type be compatible?[/+] on Apr 25, 2023
  2. MatteoRaso commented on Apr 25, 2023

    @MatteoRaso
    Contributor

    As np.float_ is a subtype of python's native float type

    That's what the documentation says, but it seems that this isn't actually the case.

    >>>np.float_
    <class 'numpy.float64'>
    

    Since this isn't actually the Python float type (only equivalent to it), this is probably why PyLance thinks that the two are incompatible.

  3. rkern commented on Apr 25, 2023

    @rkern
    Member

    np.float64 (which np.float_ is just an alias for) is a subclass of float.

    >>> np.float64.mro()
    [numpy.float64,
     numpy.floating,
     numpy.inexact,
     numpy.number,
     numpy.generic,
     float,
     object]
    
  4. rkern commented on Apr 25, 2023

    @rkern
    Member

    FWIW, I'd probably just not use np.float_ there. Idiomatically, I'd just use dtype=float or dtype=np.float64. np.float_ and np.int_ are really there more for reference than for use in dtype= arguments.

  5. simonaltrogge commented on May 9, 2023

    @simonaltrogge
    ContributorAuthor

    Even if I change np.float_ to np.float64, I still get an equivalent error message. I have submitted a corresponding bug report to microsoft/pylance-release (see microsoft/pylance-release#4332).

  6. rchiodo commented on May 9, 2023

    @rchiodo

    I think Pylance is correct here. The type float64 (at least in the type stubs) is defined like this:

    float64 = floating[_64Bit]

    where floating is a class that isn't derived from float:

    class floating(inexact[_NBit1]):
        ...

    The MRO list at runtime isn't used by Pylance/Pyright. It's determined solely by the type heirarchy we can query statically.

    If I change the definition of floating in the type stub to be like so:

    class floating(inexact[_NBit1], float):
       ...

    Then the error reported by Pylance goes away.

  7. rkern commented on May 9, 2023

    @rkern
    Member

    Well, floating doesn't subclass from float, only float64 is.

  8. hmc-cs-mdrissi commented on May 10, 2023

    @hmc-cs-mdrissi

    Hmm then similarly you could define float64 as,

    class float64(floating[_64Bit], float):
     ...
  9. BvB93 commented on May 12, 2023

    @BvB93
    Member

    Hmm then similarly you could define float64 as,

    You could, though the reason why the likes of np.floating subclasses (among a few others) are modeled as parametrized generics in the stub files is because, to put it mildly, it's incredibly difficult to determine the output bitsize for a given operation with the currently available typing tools and as such we generally have to resort to simply returning np.floating[Any] (arbitrary/unknown-sized float). Now, due to the presence of the Any-parameter and the fact that the likes of np.float64 are treated as sub-types but not proper subclasses, it means that you can freely cast from one to the other (while you cannot do the same for superclass -> subclass without some heavy handed use of typing.cast lest typecheckers can and will complain).

    The downside of this approach is that information about the float superclass is lost from np.float64, simply because the various np.floating sub-types are all treated as exactly equivalent (barring their bit size). It might be an option instead to pretend that np.floating as a whole inherits from float (and the similarly with np.complexfloating and complex). This would technically be a lie, though I suspect it's a minor one and one that might be worth the effort (especially with the autrocious state of statically duck-typing scalars).

  10. jorenham commented on Aug 17, 2024

    @jorenham
    Member

    I believe that this is a duplicate of #21906

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions