Change "format_descriptor_format" implementation as suggested by @Lalaland. Additional tests meant to ensure consistency between py::format_descriptor<>, np.array, np.format_parser turn out to be useful only to highlight long-standing inconsistencies.

This commit is contained in:
Ralf W. Grosse-Kunstleve 2023-05-18 15:22:07 -07:00
parent 03dafde5cb
commit 28492edc83
2 changed files with 65 additions and 44 deletions

View File

@ -14,33 +14,33 @@
#include "pybind11_tests.h" #include "pybind11_tests.h"
TEST_SUBMODULE(buffers, m) { TEST_SUBMODULE(buffers, m) {
#define PYBIND11_LOCAL_DEF(...) \
if (cpp_name == #__VA_ARGS__) \
return py::format_descriptor<__VA_ARGS__>::format();
m.def("format_descriptor_format", [](const std::string &cpp_name) { m.def("format_descriptor_format", [](const std::string &cpp_name) {
PYBIND11_LOCAL_DEF(PyObject *) // https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables
PYBIND11_LOCAL_DEF(bool) static auto table = new std::map<std::string, std::string>;
PYBIND11_LOCAL_DEF(std::int8_t) if (table->empty()) {
PYBIND11_LOCAL_DEF(std::uint8_t) #define PYBIND11_ASSIGN_HELPER(...) \
PYBIND11_LOCAL_DEF(std::int16_t) (*table)[#__VA_ARGS__] = py::format_descriptor<__VA_ARGS__>::format();
PYBIND11_LOCAL_DEF(std::uint16_t) PYBIND11_ASSIGN_HELPER(PyObject *)
PYBIND11_LOCAL_DEF(std::int32_t) PYBIND11_ASSIGN_HELPER(bool)
PYBIND11_LOCAL_DEF(std::uint32_t) PYBIND11_ASSIGN_HELPER(std::int8_t)
PYBIND11_LOCAL_DEF(std::int64_t) PYBIND11_ASSIGN_HELPER(std::uint8_t)
PYBIND11_LOCAL_DEF(std::uint64_t) PYBIND11_ASSIGN_HELPER(std::int16_t)
PYBIND11_LOCAL_DEF(float) PYBIND11_ASSIGN_HELPER(std::uint16_t)
PYBIND11_LOCAL_DEF(double) PYBIND11_ASSIGN_HELPER(std::int32_t)
PYBIND11_LOCAL_DEF(long double) PYBIND11_ASSIGN_HELPER(std::uint32_t)
PYBIND11_LOCAL_DEF(std::complex<float>) PYBIND11_ASSIGN_HELPER(std::int64_t)
PYBIND11_LOCAL_DEF(std::complex<double>) PYBIND11_ASSIGN_HELPER(std::uint64_t)
PYBIND11_LOCAL_DEF(std::complex<long double>) PYBIND11_ASSIGN_HELPER(float)
return std::string("UNKNOWN"); PYBIND11_ASSIGN_HELPER(double)
PYBIND11_ASSIGN_HELPER(long double)
PYBIND11_ASSIGN_HELPER(std::complex<float>)
PYBIND11_ASSIGN_HELPER(std::complex<double>)
PYBIND11_ASSIGN_HELPER(std::complex<long double>)
#undef PYBIND11_ASSIGN_HELPER
}
return (*table)[cpp_name];
}); });
#undef PYBIND11_LOCAL_DEF
// test_from_python / test_to_python: // test_from_python / test_to_python:
class Matrix { class Matrix {
public: public:

View File

@ -12,29 +12,50 @@ np = pytest.importorskip("numpy")
@pytest.mark.parametrize( @pytest.mark.parametrize(
("cpp_name", "expected_codes"), ("cpp_name", "expected_fmts", "np_array_dtype"),
[ [
("PyObject *", ["O"]), ("PyObject *", ["O"], object),
("bool", ["?"]), ("bool", ["?"], np.bool_),
("std::int8_t", ["b"]), ("std::int8_t", ["b"], np.int8),
("std::uint8_t", ["B"]), ("std::uint8_t", ["B"], np.uint8),
("std::int16_t", ["h"]), ("std::int16_t", ["h"], np.int16),
("std::uint16_t", ["H"]), ("std::uint16_t", ["H"], np.uint16),
("std::int32_t", ["i"]), ("std::int32_t", ["i"], np.int32),
("std::uint32_t", ["I"]), ("std::uint32_t", ["I"], np.uint32),
("std::int64_t", ["q"]), ("std::int64_t", ["q"], np.int64),
("std::uint64_t", ["Q"]), ("std::uint64_t", ["Q"], np.uint64),
("float", ["f"]), ("float", ["f"], np.float32),
("double", ["d"]), ("double", ["d"], np.float64),
("long double", ["g", "d"]), ("long double", ["g", "d"], np.float128),
("std::complex<float>", ["Zf"]), ("std::complex<float>", ["Zf"], np.complex64),
("std::complex<double>", ["Zd"]), ("std::complex<double>", ["Zd"], np.complex128),
("std::complex<long double>", ["Zg", "Zd"]), ("std::complex<long double>", ["Zg", "Zd"], np.complex256),
("", ["UNKNOWN"]),
], ],
) )
def test_format_descriptor_format(cpp_name, expected_codes): def test_format_descriptor_format(cpp_name, expected_fmts, np_array_dtype):
assert m.format_descriptor_format(cpp_name) in expected_codes fmt = m.format_descriptor_format(cpp_name)
assert fmt in expected_fmts
# Everything below just documents long-standing inconsistencies.
# See also: https://github.com/pybind/pybind11/issues/1908
# py::format_descriptor<> vs np.array:
na = np.array([], dtype=np_array_dtype)
bi = m.get_buffer_info(na)
if fmt == "q":
assert bi.format in ["q", "l"]
elif fmt == "Q":
assert bi.format in ["Q", "L"]
else:
assert bi.format == fmt
# py::format_descriptor<> vs np.format_parser():
fmtp = fmt[1:] if fmt.startswith("Z") else fmt
fp = np.format_parser(fmtp, [], [])
assert fp.dtype is not None
# DO NOT try to compare fp.dtype and na.dtype, unless you have a lot of
# spare time to make sense of it and possibly chime in under #1908.
def test_from_python(): def test_from_python():