Understanding the working mechanis of python unary arithmetic operators.

hongy...@gmail.com hongyi.zhao at gmail.com
Sun Oct 3 08:21:01 EDT 2021


On Sunday, October 3, 2021 at 6:31:05 PM UTC+8, ju... at diegidio.name wrote:
> On Sunday, 3 October 2021 at 11:24:58 UTC+2, hongy... at gmail.com wrote: 
> > On Sunday, October 3, 2021 at 2:18:17 PM UTC+8, hongy... at gmail.com wrote: 
> > > On Saturday, October 2, 2021 at 4:59:54 PM UTC+8, ju... at diegidio.name wrote: 
> > > > On Saturday, 2 October 2021 at 10:34:27 UTC+2, hongy... at gmail.com wrote: 
> > > > > See the following testings: 
> > > > > 
> > > > > In [24]: a=3.1415926535897932384626433832795028841971 
> > > > > In [27]: -a 
> > > > > Out[27]: -3.141592653589793 
> > > > You've never heard of floating-point? Double precision has 53 significant bits of mantissa, corresponding approximately to 16 decimal digits. 
> > > > <https://en.wikipedia.org/wiki/Double-precision_floating-point_format#IEEE_754_double-precision_binary_floating-point_format:_binary64> 
> > > > > In [17]: ~-+1 
> > > > > Out[17]: 0 
> > > > << The unary ~ (invert) operator yields the bitwise inversion of its integer argument. The bitwise inversion of x is defined as -(x+1). It only applies to integral numbers or to custom objects that override the __invert__() special method. >> 
> > > > <https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations> 
> > > A further inference based on the above description: 
> > > 
> > > Let us consider this equation: -(x+1) = x, the solution is -0.5, which is not an integer. So we can safely come to a conclusion: 
> > > 
> > > If bool(a) == True, \forall a \in integer, then ~bool(a) == False; and vice versa. 
> > > 
> > > This is exactly the theoretical basis to filter some specific columns in pandas, just as the issue discussed here [1]. 
> > Sorry my not very precise description above. I should have wanted to express the fact that I observed below: 
> > In [3]: import numpy as np 
> > In [15]: ~np.array([True]) 
> > Out[15]: array([False]) 
> > 
> > In [16]: ~np.array([False]) 
> > Out[16]: array([ True]) 
> > 
> > But the normal `True' and `False' don't the good symmetric feature as shown above: 
> > 
> > In [21]: bool(~True) 
> > Out[21]: True 
> > 
> > In [22]: bool(~False) 
> > Out[22]: True
> You keep missing the point:
> << The unary ~ (invert) operator yields the bitwise inversion of its integer argument. The bitwise inversion of x is defined as -(x+1). It only applies to integral numbers or to custom objects that override the __invert__() special method. >>
> Then you can guess that numpy overrides it and gives you *logical* negation of boolean values,

I try to dig through the numpy source code to pinning point the overriding/monkey patching/decorating code snippets, as follows:

$ rg -A5 -uu 'def __invert__' .
./numpy/__init__.pyi
2022:    def __invert__(self: NDArray[bool_]) -> NDArray[bool_]: ...
2023-    @overload
2024:    def __invert__(self: NDArray[_IntType]) -> NDArray[_IntType]: ...
2025-    @overload
2026:    def __invert__(self: NDArray[object_]) -> Any: ...
2027-
2028-    @overload
2029-    def __pos__(self: NDArray[_NumberType]) -> NDArray[_NumberType]: ...
2030-    @overload
2031-    def __pos__(self: NDArray[timedelta64]) -> NDArray[timedelta64]: ...
--
2885:    def __invert__(self) -> bool_: ...
2886-    __lshift__: _BoolBitOp[int8]
2887-    __rlshift__: _BoolBitOp[int8]
2888-    __rshift__: _BoolBitOp[int8]
2889-    __rrshift__: _BoolBitOp[int8]
2890-    __and__: _BoolBitOp[bool_]
--
2993:    def __invert__(self: _IntType) -> _IntType: ...
2994-    # Ensure that objects annotated as `integer` support bit-wise operations
2995-    def __lshift__(self, other: _IntLike_co) -> integer: ...
2996-    def __rlshift__(self, other: _IntLike_co) -> integer: ...
2997-    def __rshift__(self, other: _IntLike_co) -> integer: ...
2998-    def __rrshift__(self, other: _IntLike_co) -> integer: ...

./numpy/array_api/_array_object.py
510:    def __invert__(self: Array, /) -> Array:
511-        """
512-        Performs the operation __invert__.
513-        """
514-        if self.dtype not in _integer_or_boolean_dtypes:
515-            raise TypeError("Only integer or boolean dtypes are allowed in __invert__")

./numpy/lib/user_array.py
179:    def __invert__(self):
180-        return self._rc(invert(self.array))
181-
182-    def _scalarfunc(self, func):
183-        if self.ndim == 0:
184-            return func(self[0])

./numpy/lib/mixins.pyi
62:    def __invert__(self): ...

Which is corresponding to the overriding mentioned above by you?

> while on primitives you get the usual *arithmetic* behaviour, where bool(~False) implicitly is bool(~int(False)) = bool(~0) = bool(-1) = True. 

Implicit cast happens here automatically.
 
> Please take note: (typically!) ~ denotes a bitwise operation on integers, not logical negation on booleans. 

Thank you for stressing the point again.

HZ


More information about the Python-list mailing list