Skip to content

Commit

Permalink
Fix use of __index__ on Python 2
Browse files Browse the repository at this point in the history
  • Loading branch information
YannickJadoul committed Jan 23, 2021
1 parent 8356073 commit 2092295
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 28 deletions.
37 changes: 17 additions & 20 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -1040,26 +1040,30 @@ struct type_caster<T, enable_if_t<std::is_arithmetic<T>::value && !is_std_char_t
return false;
} else if (PyFloat_Check(src.ptr())) {
return false;
} else if (!convert && !index_check(src.ptr()) && !PYBIND11_LONG_CHECK(src.ptr())) {
} else if (!convert && !PYBIND11_LONG_CHECK(src.ptr()) && !index_check(src.ptr())) {
return false;
} else {
handle src_or_index = src;
py_value = (py_type) -1;
#if PY_VERSION_HEX < 0x03080000
object index;
if (!PYBIND11_LONG_CHECK(src.ptr()) && index_check(src.ptr())) {
if (!PYBIND11_LONG_CHECK(src.ptr())) { // So: index_check(src.ptr())
index = reinterpret_steal<object>(PyNumber_Index(src.ptr()));
src_or_index = index ? index : handle();
if (!index) {
PyErr_Clear();
if (!convert)
return false;
}
else {
src_or_index = index;
}
}
#endif
if (src_or_index) {
if (std::is_unsigned<py_type>::value) {
py_value = as_unsigned<py_type>(src_or_index.ptr());
} else { // signed integer:
py_value = sizeof(T) <= sizeof(long)
? (py_type) PyLong_AsLong(src_or_index.ptr())
: (py_type) PYBIND11_LONG_AS_LONGLONG(src_or_index.ptr());
}
if (std::is_unsigned<py_type>::value) {
py_value = as_unsigned<py_type>(src_or_index.ptr());
} else { // signed integer:
py_value = sizeof(T) <= sizeof(long)
? (py_type) PyLong_AsLong(src_or_index.ptr())
: (py_type) PYBIND11_LONG_AS_LONGLONG(src_or_index.ptr());
}
}

Expand All @@ -1069,15 +1073,8 @@ struct type_caster<T, enable_if_t<std::is_arithmetic<T>::value && !is_std_char_t
// Check to see if the conversion is valid (integers should match exactly)
// Signed/unsigned checks happen elsewhere
if (py_err || (std::is_integral<T>::value && sizeof(py_type) != sizeof(T) && py_value != (py_type) (T) py_value)) {
bool type_error = py_err && PyErr_ExceptionMatches(
#if PY_VERSION_HEX < 0x03000000 && !defined(PYPY_VERSION)
PyExc_SystemError
#else
PyExc_TypeError
#endif
);
PyErr_Clear();
if (type_error && convert && PyNumber_Check(src.ptr())) {
if (py_err && convert && PyNumber_Check(src.ptr())) {
auto tmp = reinterpret_steal<object>(std::is_floating_point<T>::value
? PyNumber_Float(src.ptr())
: PyNumber_Long(src.ptr()));
Expand Down
24 changes: 16 additions & 8 deletions tests/test_builtin_casters.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,18 @@ def test_integer_casting():
assert "incompatible function arguments" in str(excinfo.value)


@pytest.mark.filterwarnings("ignore:an integer is required:DeprecationWarning")
def test_int_convert():
class DeepThought(object):
def __int__(self):
return 42

class DoubleThought(object):
def __int__(self):
return 42

def __index__(self):
return 0

class ShallowThought(object):
pass

Expand Down Expand Up @@ -284,7 +290,7 @@ def __int__(self):

convert, noconvert = m.int_passthrough, m.int_passthrough_noconvert

def require_implicit(v):
def requires_conversion(v):
pytest.raises(TypeError, noconvert, v)

def cant_convert(v):
Expand All @@ -294,20 +300,22 @@ def cant_convert(v):
assert noconvert(7) == 7
cant_convert(3.14159)
assert convert(DeepThought()) == 42
require_implicit(DeepThought())
requires_conversion(DeepThought())
assert convert(DoubleThought()) == 0 # Fishy; `int(DoubleThought)` == 42
assert noconvert(DoubleThought()) == 0
cant_convert(ShallowThought())
cant_convert(FuzzyThought())

# Before Python 3.8, `int(obj)` does not pick up on `obj.__index__`, but pybind11
# "backports" this behavior.
# Before Python 3.8, `PyLong_AsLong` does not pick up on `obj.__index__`,
# but pybind11 "backports" this behavior.
assert convert(IndexedThought()) == 42
assert noconvert(IndexedThought()) == 42
assert convert(TypeErrorThought()) == 42
require_implicit(TypeErrorThought())
cant_convert(RaisingThought()) # no fall-back to `__int__`if `__index__` raises
requires_conversion(TypeErrorThought())
assert convert(RaisingThought()) == 42
requires_conversion(RaisingThought())


@pytest.mark.filterwarnings("ignore:an integer is required:DeprecationWarning")
def test_numpy_int_convert():
np = pytest.importorskip("numpy")

Expand Down

0 comments on commit 2092295

Please sign in to comment.