[Numpy-discussion] Comparison between numpy scalars returns numpy bool class and not native python bool class

2024-06-27 Thread Stefano Miccoli via NumPy-Discussion
It is well known that ‘np.bool' is not interchangeable with python ‘bool’, and 
in fact 'issubclass(np.bool, bool)’ is false.

On the contrary, numpy floats are subclassing python 
floats—'issubclass(np.float64, float) is true—so I’m wondering if the fact that 
scalar comparison returns a np.bool breaks the Liskov substitution principle. 
In fact  ’(np.float64(1) > 0) is True’ is unexpectedly false.

I was hit by this behaviour because in python structural pattern matching, the 
‘a > 1’ subject will not match neither ’True’ or ‘False’ if ‘a' is a numpy 
scalar: In this short example

import numpy as np
a = np.float64(1)
assert isinstance(a, float)
match a > 1:
case True | False:
print('python float')
case _:
print('Huh?: numpy float’)

the default clause is matched. If we set instead ‘a = float(1)’, the first 
clause will be matched. The surprise factor is quite high here, in my opinion.
(Let me add that ‘True', ‘False', ‘None' are special in python structural 
pattern matching, because they are matched by identity and not by equality.)

I’m not sure if this behaviour can be avoided, or if we have to live with the 
fact that numpy floats are to be kept well contained and never mixed with 
python floats.

Stefano

smime.p7s
Description: S/MIME cryptographic signature
___
NumPy-Discussion mailing list -- numpy-discussion@python.org
To unsubscribe send an email to numpy-discussion-le...@python.org
https://mail.python.org/mailman3/lists/numpy-discussion.python.org/
Member address: arch...@mail-archive.com


[Numpy-discussion] Re: Comparison between numpy scalars returns numpy bool class and not native python bool class

2024-06-27 Thread Aaron Meurer
Apparently the reason this happens is that True, False, and None are
compared using 'is' in structural pattern matching (see
https://peps.python.org/pep-0634/#literal-patterns).

There's no way NumPy could avoid this. First off, Python won't even
let you subclass bool:

>>> class mybool(bool):
... pass
Traceback (most recent call last):
  File "", line 1, in 
TypeError: type 'bool' is not an acceptable base type

np.bool_ objects *do* compare equal to bool:

>>> type(np.array(1) > 0)

>>> (np.array(1) > 0) == True
True

but that doesn't matter because True is specifically special cased in
structural pattern matching.

The workaround is to use np.True_ and np.False_ in your pattern

>>> match a > 1:
... case np.True_ | np.False_:
... print('python float')
... case _:
... print('Huh?: numpy float')
python float

Fortunately, since these compare equal to Python bool, this will also
work even if a > 1 is a normal True or False:

>>> a = 1
>>> import numpy as np
... a = np.float64(1)
... assert isinstance(a, float)
... match a > 1:
... case np.True_ | np.False_:
... print('python float')
... case _:
... print('Huh?: numpy float')
python float

Aaron Meurer

On Thu, Jun 27, 2024 at 3:33 PM Stefano Miccoli via NumPy-Discussion
 wrote:
>
> It is well known that ‘np.bool' is not interchangeable with python ‘bool’, 
> and in fact 'issubclass(np.bool, bool)’ is false.
>
> On the contrary, numpy floats are subclassing python 
> floats—'issubclass(np.float64, float) is true—so I’m wondering if the fact 
> that scalar comparison returns a np.bool breaks the Liskov substitution 
> principle. In fact  ’(np.float64(1) > 0) is True’ is unexpectedly false.
>
> I was hit by this behaviour because in python structural pattern matching, 
> the ‘a > 1’ subject will not match neither ’True’ or ‘False’ if ‘a' is a 
> numpy scalar: In this short example
>
> import numpy as np
> a = np.float64(1)
> assert isinstance(a, float)
> match a > 1:
> case True | False:
> print('python float')
> case _:
> print('Huh?: numpy float’)
>
> the default clause is matched. If we set instead ‘a = float(1)’, the first 
> clause will be matched. The surprise factor is quite high here, in my opinion.
> (Let me add that ‘True', ‘False', ‘None' are special in python structural 
> pattern matching, because they are matched by identity and not by equality.)
>
> I’m not sure if this behaviour can be avoided, or if we have to live with the 
> fact that numpy floats are to be kept well contained and never mixed with 
> python floats.
>
> Stefano___
> NumPy-Discussion mailing list -- numpy-discussion@python.org
> To unsubscribe send an email to numpy-discussion-le...@python.org
> https://mail.python.org/mailman3/lists/numpy-discussion.python.org/
> Member address: asmeu...@gmail.com
___
NumPy-Discussion mailing list -- numpy-discussion@python.org
To unsubscribe send an email to numpy-discussion-le...@python.org
https://mail.python.org/mailman3/lists/numpy-discussion.python.org/
Member address: arch...@mail-archive.com


[Numpy-discussion] Re: Comparison between numpy scalars returns numpy bool class and not native python bool class

2024-06-27 Thread Aaron Meurer
On Thu, Jun 27, 2024 at 3:48 PM Aaron Meurer  wrote:
>
> Apparently the reason this happens is that True, False, and None are
> compared using 'is' in structural pattern matching (see
> https://peps.python.org/pep-0634/#literal-patterns).
>
> There's no way NumPy could avoid this. First off, Python won't even
> let you subclass bool:
>
> >>> class mybool(bool):
> ... pass
> Traceback (most recent call last):
>   File "", line 1, in 
> TypeError: type 'bool' is not an acceptable base type
>
> np.bool_ objects *do* compare equal to bool:
>
> >>> type(np.array(1) > 0)
> 
> >>> (np.array(1) > 0) == True
> True
>
> but that doesn't matter because True is specifically special cased in
> structural pattern matching.
>
> The workaround is to use np.True_ and np.False_ in your pattern
>
> >>> match a > 1:
> ... case np.True_ | np.False_:
> ... print('python float')
> ... case _:
> ... print('Huh?: numpy float')
> python float
>
> Fortunately, since these compare equal to Python bool, this will also
> work even if a > 1 is a normal True or False:
>
> >>> a = 1
> >>> import numpy as np
> ... a = np.float64(1)
> ... assert isinstance(a, float)
> ... match a > 1:
> ... case np.True_ | np.False_:
> ... print('python float')
> ... case _:
> ... print('Huh?: numpy float')
> python float

Typo. This should have just been:

>>> a = 1
>>> match a > 1:
... case np.True_ | np.False_:
... print("matches python bool")
... case _:
... print("doesn't match")
matches python bool

Aaron Meurer

>
> Aaron Meurer
>
> On Thu, Jun 27, 2024 at 3:33 PM Stefano Miccoli via NumPy-Discussion
>  wrote:
> >
> > It is well known that ‘np.bool' is not interchangeable with python ‘bool’, 
> > and in fact 'issubclass(np.bool, bool)’ is false.
> >
> > On the contrary, numpy floats are subclassing python 
> > floats—'issubclass(np.float64, float) is true—so I’m wondering if the fact 
> > that scalar comparison returns a np.bool breaks the Liskov substitution 
> > principle. In fact  ’(np.float64(1) > 0) is True’ is unexpectedly false.
> >
> > I was hit by this behaviour because in python structural pattern matching, 
> > the ‘a > 1’ subject will not match neither ’True’ or ‘False’ if ‘a' is a 
> > numpy scalar: In this short example
> >
> > import numpy as np
> > a = np.float64(1)
> > assert isinstance(a, float)
> > match a > 1:
> > case True | False:
> > print('python float')
> > case _:
> > print('Huh?: numpy float’)
> >
> > the default clause is matched. If we set instead ‘a = float(1)’, the first 
> > clause will be matched. The surprise factor is quite high here, in my 
> > opinion.
> > (Let me add that ‘True', ‘False', ‘None' are special in python structural 
> > pattern matching, because they are matched by identity and not by equality.)
> >
> > I’m not sure if this behaviour can be avoided, or if we have to live with 
> > the fact that numpy floats are to be kept well contained and never mixed 
> > with python floats.
> >
> > Stefano___
> > NumPy-Discussion mailing list -- numpy-discussion@python.org
> > To unsubscribe send an email to numpy-discussion-le...@python.org
> > https://mail.python.org/mailman3/lists/numpy-discussion.python.org/
> > Member address: asmeu...@gmail.com
___
NumPy-Discussion mailing list -- numpy-discussion@python.org
To unsubscribe send an email to numpy-discussion-le...@python.org
https://mail.python.org/mailman3/lists/numpy-discussion.python.org/
Member address: arch...@mail-archive.com