numpy/numpy — pull request #3526 by zyv (2013-07-16 13:40:00+00:00)
Title: BUG: Fix PyType_FastSubclass() check for numpy.int_ and Py3k inheritance

Quote from the documentation at http://docs.scipy.org/doc/numpy/reference/arrays.scalars.html :

> Five of the scalar types are essentially equivalent to fundamental Python types and therefore inherit from them as well as from the generic array scalar type <...>

However, presently, the code deviates from this document in two ways:

1) For `numpy.int_` type, the correct `tp_flags` are not set, which means that if one uses specialized fast-path Python C-API macro `PyInt_Check` on its instances to check whether the object is a subclass of a Python builtin, the result will be different (`False`) as compared to `PyObject_TypeCheck` (`True`).

This results in a different behavior of seemingly the same logic run through the Python interpreter using its MRO facilities and hand-written or generated modules, using optimized C-API calls directly.

2) If one is using Py3k, then the code is expanded only to contain `SINGLE_INHERIT` macros, which means that `numpy.int_` is not a subclass of `int` under Py3k at all.

My patch fixes these two issues by (a) introducing new macro `DUAL_INHERIT_INT`, which expands to `DUAL_INHERIT(XXX, Long, XXX)` under Py3k and `DUAL_INHERIT(XXX, Int, XXX)` otherwise and (b) setting `tp_flags` correctly from within this macro.

In order to test my changes, you will need the following minimal Cython program that generates code using optimized C-API calls:

```python
def inspect(x):
print("My MRO: {0}".format(type.mro(type(x))))
if isinstance(x, int):
print("I'm an normal integer number")
elif isinstance(x, long):
print("I'm an < Py3k long integer number")
elif isinstance(x, float):
print("I'm a floating point number")
else:
print("I don't know who I am")
```

Test output on the x86_64 machine:

##### Python 2.7, without patch
```
In [1]: import bt

In [2]: import numpy as np

In [3]: type.mro(type(np.int_(0)))
Out[3]: [numpy.int64, numpy.signedinteger, numpy.integer, numpy.number, numpy.generic, int, object]

In [4]: type.mro(type(np.int32(0)))
Out[4]: [numpy.int32, numpy.signedinteger, numpy.integer, numpy.number, numpy.generic, object]

In [5]: type.mro(type(np.int64(0)))
Out[5]: [numpy.int64, numpy.signedinteger, numpy.integer, numpy.number, numpy.generic, int, object]

In [6]: bt.inspect(np.int_(0))
My MRO: [, , , , , , ]
I don't know who I am <--- WRONG!

In [7]: bt.inspect(np.int32(0))
My MRO: [, , , , , ]
I don't know who I am

In [8]: bt.inspect(np.int64(0))
My MRO: [, , , , , , ]
I don't know who I am <--- WRONG!
```
##### Python 3.2, without patch
```
In [1]: import bt

In [2]: import numpy as np

In [3]: type.mro(type(np.int_(0)))
Out[3]: [numpy.int64, numpy.signedinteger, numpy.integer, numpy.number, numpy.generic, builtins.object]
^^^^ WRONG!
In [4]: type.mro(type(np.int32(0)))
Out[4]: [numpy.int32, numpy.signedinteger, numpy.integer, numpy.number, numpy.generic, builtins.object]

In [5]: type.mro(type(np.int64(0)))
Out[5]: [numpy.int64, numpy.signedinteger, numpy.integer, numpy.number, numpy.generic, builtins.object]
^^^^ WRONG!
In [6]: bt.inspect(np.int_(0))
My MRO: [, , , , , ]
I don't know who I am
^^^^ WRONG!
In [7]: bt.inspect(np.int32(0))
My MRO: [, , , , , ]
I don't know who I am

In [8]: bt.inspect(np.int64(0))
My MRO: [, , , , , ]
I don't know who I am
^^^^ WRONG!
```
##### Python 2.7, with patch
```
In [1]: import bt

In [2]: import numpy as np

In [3]: type.mro(type(np.int_(0)))
Out[3]: [numpy.int64, numpy.signedinteger, numpy.integer, numpy.number, numpy.generic, int, object]

In [4]: type.mro(type(np.int32(0)))
Out[4]: [numpy.int32, numpy.signedinteger, numpy.integer, numpy.number, numpy.generic, object]

In [5]: type.mro(type(np.int64(0)))
Out[5]: [numpy.int64, numpy.signedinteger, numpy.integer, numpy.number, numpy.generic, int, object]

In [6]: bt.inspect(np.int_(0))
My MRO: [, , , , , , ]
I'm an normal integer number
^^^^ FIXED!
In [7]: bt.inspect(np.int32(0))
My MRO: [, , , , , ]
I don't know who I am

In [8]: bt.inspect(np.int64(0))
My MRO: [, , , , , , ]
I'm an normal integer number
^^^^ FIXED!
```
##### Python 3.2, with patch
```
In [1]: import bt

In [2]: import numpy as np

In [3]: type.mro(type(np.int_(0)))
Out[3]: [numpy.int64, numpy.signedinteger, numpy.integer, numpy.number, numpy.generic, builtins.int, builtins.object]
^^^^ FIXED!
In [4]: type.mro(type(np.int32(0)))
Out[4]: [numpy.int32, numpy.signedinteger, numpy.integer, numpy.number, numpy.generic, builtins.object]

In [5]: type.mro(type(np.int64(0)))
Out[5]: [numpy.int64, numpy.signedinteger, numpy.integer, numpy.number, numpy.generic, builtins.int, builtins.object]
^^^^ FIXED!
In [6]: bt.inspect(np.int_(0))
My MRO: [, , , , , , ]
I'm an normal integer number
^^^^ FIXED!
In [7]: bt.inspect(np.int32(0))
My MRO: [, , , , , ]
I don't know who I am

In [8]: bt.inspect(np.int64(0))
My MRO: [, , , , , , ]
I'm an normal integer number
^^^^ FIXED!
```

Thank you for considering my bugfix for inclusion in NumPy,

--Yury.