diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 0bcaad60..2270c285 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -19,27 +19,22 @@ add_definitions(-DBOOST_TEST_DYN_LINK) # for unit_test_framework find_package(Boost 1.55 REQUIRED COMPONENTS iostreams serialization unit_test_framework) +set(LIBRARIES stdc++ m ${Boost_LIBRARIES}) + if(USE_PYTHON) - # try to find boost.python - find_package(Boost 1.55 - COMPONENTS iostreams serialization unit_test_framework python) - - execute_process(COMMAND python -c "import sys; sys.stdout.write(\"%i.%i\" % (sys.version_info.major, sys.version_info.minor))" - OUTPUT_VARIABLE PYTHON_VERSION) - find_package(PythonLibs ${PYTHON_VERSION} EXACT) - - if(Boost_PYTHON_FOUND AND PYTHONLIBS_FOUND - AND ${PYTHON_VERSION} VERSION_LESS 3) - # Python3 is not yet supported - + find_package(PythonInterp) + find_package(PythonLibs ${PYTHON_VERSION_STRING} EXACT) + find_library(Boost_PYTHON_LIBRARY + NAMES + boost_python-py${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR} + boost_python + HINTS ${Boost_LIBRARY_DIR} + NO_DEFAULT_PATH) + message(STATUS "Found boost.python: ${Boost_PYTHON_LIBRARY}") + if(Boost_PYTHON_LIBRARY AND PYTHONLIBS_FOUND) set(HAVE_PYTHON 1) - message(STATUS "Python version: ${PYTHON_VERSION}") include_directories(${PYTHON_INCLUDE_DIRS}) - if(${PYTHON_VERSION} VERSION_GREATER 3.0) - add_definitions(-DHAVE_PYTHON3) - endif() - if(USE_NUMPY) find_package(Numpy 1.7) endif() @@ -49,10 +44,11 @@ if(USE_PYTHON) include_directories(${NUMPY_INCLUDE_DIR}) add_definitions(-DHAVE_NUMPY) endif() + + set(LIBRARIES ${LIBRARIES} ${Boost_PYTHON_LIBRARY} ${PYTHON_LIBRARIES}) endif() endif() -set(LIBRARIES stdc++ m ${Boost_LIBRARIES} ${PYTHON_LIBRARIES}) include_directories(../include ${Boost_INCLUDE_DIRS}) if(CMAKE_BUILD_TYPE STREQUAL "debug" OR COVERAGE) @@ -80,7 +76,7 @@ add_library(boost_histogram SHARED target_link_libraries(boost_histogram ${LIBRARIES}) # python bindings -if(Boost_PYTHON_FOUND) +if(HAVE_PYTHON) add_library(pyhistogram MODULE ../src/python/module.cpp ../src/python/axis.cpp diff --git a/build/FindNumpy.cmake b/build/FindNumpy.cmake index e2972889..f26fa9fc 100644 --- a/build/FindNumpy.cmake +++ b/build/FindNumpy.cmake @@ -5,10 +5,10 @@ set(NUMPY_FOUND FALSE) execute_process( - COMMAND python -c "import numpy, sys; sys.stdout.write(numpy.get_include())" + COMMAND ${PYTHON_EXECUTABLE} -c "import numpy, sys; sys.stdout.write(numpy.get_include())" OUTPUT_VARIABLE NUMPY_INCLUDE_DIR) execute_process( - COMMAND python -c "import numpy, sys; sys.stdout.write(numpy.version.version)" + COMMAND ${PYTHON_EXECUTABLE} -c "import numpy, sys; sys.stdout.write(numpy.version.version)" OUTPUT_VARIABLE NUMPY_VERSION ) diff --git a/src/python/module.cpp b/src/python/module.cpp index c6048b67..8b264a64 100644 --- a/src/python/module.cpp +++ b/src/python/module.cpp @@ -3,6 +3,11 @@ # define PY_ARRAY_UNIQUE_SYMBOL boost_histogram_ARRAY_API # define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION # include +# if PY_MAJOR_VERSION >= 3 +static void* init_numpy() { import_array(); return NULL; } +# else +static void init_numpy() { import_array(); } +# endif #endif namespace boost { @@ -16,7 +21,7 @@ namespace histogram { BOOST_PYTHON_MODULE(histogram) { #ifdef HAVE_NUMPY - import_array(); + init_numpy(); #endif boost::histogram::register_axis_types(); boost::histogram::register_basic_histogram(); diff --git a/src/python/serialization_suite.hpp b/src/python/serialization_suite.hpp index 669fc6a0..f7865841 100644 --- a/src/python/serialization_suite.hpp +++ b/src/python/serialization_suite.hpp @@ -15,9 +15,16 @@ namespace boost { namespace histogram { namespace detail { -class python_str_sink : public iostreams::sink { +#if PY_MAJOR_VERSION < 3 +# define PyBytes_FromStringAndSize PyString_FromStringAndSize +# define PyBytes_AS_STRING PyString_AS_STRING +# define PyBytes_Size PyString_Size +# define _PyBytes_Resize _PyString_Resize +#endif + +class python_bytes_sink : public iostreams::sink { public: - python_str_sink(PyObject** pstr) : + python_bytes_sink(PyObject** pstr) : pstr_(pstr), len_(0), pos_(0) @@ -26,7 +33,7 @@ public: std::streamsize write(const char* s, std::streamsize n) { if (len_ == 0) { - *pstr_ = PyString_FromStringAndSize(s, n); + *pstr_ = PyBytes_FromStringAndSize(s, n); if (*pstr_ == 0) { PyErr_SetString(PyExc_RuntimeError, "cannot allocate memory"); python::throw_error_already_set(); @@ -35,10 +42,10 @@ public: } else { if (pos_ + n > len_) { len_ = pos_ + n; - if (_PyString_Resize(pstr_, len_) == -1) + if (_PyBytes_Resize(pstr_, len_) == -1) python::throw_error_already_set(); } - char* b = PyString_AS_STRING(*pstr_); + char* b = PyBytes_AS_STRING(*pstr_); std::copy(s, s + n, b + pos_); } pos_ += n; @@ -59,7 +66,7 @@ struct serialization_suite : python::pickle_suite python::tuple getstate(python::object obj) { PyObject* pobj = 0; - iostreams::stream os(&pobj); + iostreams::stream os(&pobj); archive::text_oarchive oa(os); oa << python::extract(obj)(); os.flush(); @@ -84,7 +91,7 @@ struct serialization_suite : python::pickle_suite // restore the C++ object python::object o = state[1]; iostreams::stream - is(python::extract(o)(), python::len(o)); + is(PyBytes_AS_STRING(o.ptr()), PyBytes_Size(o.ptr())); archive::text_iarchive ia(is); ia >> python::extract(obj)(); } @@ -93,6 +100,11 @@ struct serialization_suite : python::pickle_suite bool getstate_manages_dict() { return true; } }; +#undef PyBytes_FromStringAndSize +#undef PyBytes_AS_STRING +#undef PyBytes_Size +#undef _PyBytes_Resize + } } diff --git a/test/python_suite_test.py.in b/test/python_suite_test.py.in index 1bb62384..93d8f5c9 100755 --- a/test/python_suite_test.py.in +++ b/test/python_suite_test.py.in @@ -1,11 +1,14 @@ -#!/usr/bin/env python +#!@PYTHON_EXECUTABLE@ import unittest from math import pi from histogram import histogram, regular_axis, polar_axis, \ variable_axis, category_axis, integer_axis -import cPickle -import StringIO +import pickle import os +if @PYTHON_VERSION_MAJOR@ == 3: + from io import BytesIO +else: + from StringIO import StringIO as BytesIO have_numpy = "@HAVE_NUMPY@" if have_numpy: @@ -28,7 +31,7 @@ class test_regular_axis(unittest.TestCase): regular_axis(0, 1.0, 2.0) with self.assertRaises(TypeError): regular_axis("1", 1.0, 2.0) - with self.assertRaises(StandardError): + with self.assertRaises(Exception): regular_axis(-1, 1.0, 2.0) with self.assertRaises(RuntimeError): regular_axis(1, 2.0, 1.0) @@ -58,7 +61,7 @@ class test_regular_axis(unittest.TestCase): def test_getitem(self): v = [1.0, 1.25, 1.5, 1.75, 2.0] a = regular_axis(4, 1.0, 2.0) - for i in xrange(5): + for i in range(5): self.assertEqual(a[i], v[i]) def test_iter(self): @@ -91,7 +94,7 @@ class test_polar_axis(unittest.TestCase): polar_axis(4, 1.0, label="pa") with self.assertRaises(TypeError): polar_axis() - with self.assertRaises(StandardError): + with self.assertRaises(Exception): polar_axis(-1) with self.assertRaises(TypeError): polar_axis(4, 1.0, uoflow=True) @@ -120,7 +123,7 @@ class test_polar_axis(unittest.TestCase): def test_getitem(self): v = [1.0, 1.0 + 0.5 * pi, 1.0 + pi, 1.0 + 1.5 *pi, 1.0 + 2.0 * pi] a = polar_axis(4, 1.0) - for i in xrange(5): + for i in range(5): self.assertEqual(a[i], v[i]) def test_iter(self): @@ -182,7 +185,7 @@ class test_variable_axis(unittest.TestCase): def test_getitem(self): v = [-0.1, 0.2, 0.3] a = variable_axis(*v) - for i in xrange(3): + for i in range(3): self.assertEqual(a[i], v[i]) def test_iter(self): @@ -243,7 +246,7 @@ class test_category_axis(unittest.TestCase): def test_getitem(self): c = "A", "B", "C" a = category_axis(*c) - for i in xrange(3): + for i in range(3): self.assertEqual(a[i], c[i]) def test_iter(self): @@ -266,7 +269,8 @@ class test_integer_axis(unittest.TestCase): integer_axis(1, 2, 3) self.assertEqual(integer_axis(-1, 2), integer_axis(-1, 2)) self.assertNotEqual(integer_axis(-1, 2), integer_axis(-1, 2, label="ia")) - self.assertNotEqual(integer_axis(-1, 2), integer_axis(-1, 2, uoflow=True)) + self.assertNotEqual(integer_axis(-1, 2, uoflow=False), + integer_axis(-1, 2, uoflow=True)) def test_len(self): self.assertEqual(len(integer_axis(-1, 2)), 4) @@ -284,7 +288,7 @@ class test_integer_axis(unittest.TestCase): def test_getitem(self): v = [-1, 0, 1, 2] a = integer_axis(-1, 2) - for i in xrange(4): + for i in range(4): self.assertEqual(a[i], v[i]) def test_iter(self): @@ -331,7 +335,6 @@ class histogram_test(unittest.TestCase): self.assertNotEqual(h, histogram(regular_axis(1, -1, 1))) self.assertNotEqual(h, histogram(integer_axis(-1, 2))) self.assertNotEqual(h, histogram(integer_axis(-1, 1, label="ia"))) - self.assertNotEqual(h, histogram(integer_axis(-1, 1, uoflow=True))) def test_copy(self): a = histogram(integer_axis(-1, 1)) @@ -347,9 +350,9 @@ class histogram_test(unittest.TestCase): h0 = histogram(integer_axis(-1, 1, uoflow=False)) h1 = histogram(integer_axis(-1, 1, uoflow=True)) for h in (h0, h1): - with self.assertRaises(StandardError): + with self.assertRaises(Exception): h.fill() - with self.assertRaises(StandardError): + with self.assertRaises(Exception): h.fill(1, 2) h.fill(-10) h.fill(-1) @@ -381,12 +384,12 @@ class histogram_test(unittest.TestCase): h.fill(-1) h.fill(1) h.fill(1) - for i in xrange(255): + for i in range(255): h.fill(0) self.assertEqual(h.depth, 1) h.fill(0) self.assertEqual(h.depth, 2) - for i in xrange(1000-256): + for i in range(1000-256): h.fill(0) self.assertEqual(h.value(-1), 0) self.assertEqual(h.value(0), 1) @@ -405,9 +408,9 @@ class histogram_test(unittest.TestCase): h.fill(1, 0) h.fill(3, -1) h.fill(0, -3) - with self.assertRaises(StandardError): + with self.assertRaises(Exception): h.fill(1) - with self.assertRaises(StandardError): + with self.assertRaises(Exception): h.fill(1, 2, 3) m = [[1, 1, 0, 0, 0, 0], @@ -415,8 +418,8 @@ class histogram_test(unittest.TestCase): [0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]] - for i in xrange(h.axis(0).bins + 2*uoflow): - for j in xrange(h.axis(1).bins + 2*uoflow): + for i in range(h.axis(0).bins + 2*uoflow): + for j in range(h.axis(1).bins + 2*uoflow): self.assertEqual(h.value(i, j), m[i][j]) def test_add_2d(self): @@ -441,8 +444,8 @@ class histogram_test(unittest.TestCase): h += h self.assertEqual(h, h2) - for i in xrange(h.axis(0).bins + 2*uoflow): - for j in xrange(h.axis(1).bins + 2*uoflow): + for i in range(h.axis(0).bins + 2*uoflow): + for j in range(h.axis(1).bins + 2*uoflow): self.assertEqual(h.value(i, j), 2 * m[i][j]) self.assertEqual(h.variance(i, j), 2 * m[i][j]) @@ -478,8 +481,8 @@ class histogram_test(unittest.TestCase): h += h self.assertNotEqual(h, h2) - for i in xrange(h.axis(0).bins + 2*uoflow): - for j in xrange(h.axis(1).bins + 2*uoflow): + for i in range(h.axis(0).bins + 2*uoflow): + for j in range(h.axis(1).bins + 2*uoflow): self.assertEqual(h.value(i, j), 2 * m[i][j]) self.assertEqual(h.variance(i, j), 2 * m[i][j]) @@ -489,21 +492,21 @@ class histogram_test(unittest.TestCase): regular_axis(20, 0.0, 20.0, uoflow=False), variable_axis(0.0, 1.0, 2.0), polar_axis(4, label='pa')) - for i in xrange(a.axis(0).bins): + for i in range(a.axis(0).bins): a.fill(i, 0, 0, 0, 0) - for j in xrange(a.axis(1).bins): + for j in range(a.axis(1).bins): a.fill(i, j, 0, 0, 0) - for k in xrange(a.axis(2).bins): + for k in range(a.axis(2).bins): a.fill(i, j, k, 0, 0) - for l in xrange(a.axis(3).bins): + for l in range(a.axis(3).bins): a.fill(i, j, k, l, 0) - for m in xrange(a.axis(4).bins): + for m in range(a.axis(4).bins): a.fill(i, j, k, l, m * 0.5 * pi) - io = StringIO.StringIO() - cPickle.dump(a, io) + io = BytesIO() + pickle.dump(a, io) io.seek(0) - b = cPickle.load(io) + b = pickle.load(io) self.assertNotEqual(id(a), id(b)) self.assertEqual(a.dim, b.dim) self.assertEqual(a.axis(0), b.axis(0)) @@ -519,19 +522,19 @@ class histogram_test(unittest.TestCase): integer_axis(0, 3, label='ia'), regular_axis(4, 0.0, 4.0, uoflow=False), variable_axis(0.0, 1.0, 2.0)) - for i in xrange(a.axis(0).bins): + for i in range(a.axis(0).bins): a.fill(i, 0, 0, 0, w=3) - for j in xrange(a.axis(1).bins): + for j in range(a.axis(1).bins): a.fill(i, j, 0, 0, w=10) - for k in xrange(a.axis(2).bins): + for k in range(a.axis(2).bins): a.fill(i, j, k, 0, w=2) - for l in xrange(a.axis(3).bins): + for l in range(a.axis(3).bins): a.fill(i, j, k, l, w=5) - io = StringIO.StringIO() - cPickle.dump(a, io) + io = BytesIO() + pickle.dump(a, io) io.seek(0) - b = cPickle.load(io) + b = pickle.load(io) self.assertNotEqual(id(a), id(b)) self.assertEqual(a.dim, b.dim) self.assertEqual(a.axis(0), b.axis(0)) @@ -544,17 +547,17 @@ class histogram_test(unittest.TestCase): @unittest.skipUnless(have_numpy, "requires build with numpy-support") def test_numpy_conversion_0(self): a = histogram(integer_axis(0, 2, uoflow=False)) - for i in xrange(100): + for i in range(100): a.fill(1) b = numpy.array(a) # a copy v = numpy.asarray(a) # a view self.assertEqual(b.dtype, numpy.uint8) self.assertTrue(numpy.all(b == numpy.array((0, 100, 0)))) - for i in xrange(100): + for i in range(100): a.fill(1) self.assertTrue(numpy.all(b == numpy.array((0, 100, 0)))) self.assertTrue(numpy.all(v == numpy.array((0, 200, 0)))) - for i in xrange(100): + for i in range(100): a.fill(1) b = numpy.array(a) self.assertEqual(b.dtype, numpy.uint16) @@ -565,7 +568,7 @@ class histogram_test(unittest.TestCase): @unittest.skipUnless(have_numpy, "requires build with numpy-support") def test_numpy_conversion_1(self): a = histogram(integer_axis(0, 2, uoflow=False)) - for i in xrange(10): + for i in range(10): a.fill(1, w=3) b = numpy.array(a) # a copy v = numpy.asarray(a) # a view