diff --git a/CMakeLists.txt b/CMakeLists.txt index a028d8cd..581a3a16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,11 +44,11 @@ include_directories(${PYTHON_INCLUDE_DIR} include) add_library(example SHARED include/pybind/cast.h include/pybind/common.h - include/pybind/mpl.h include/pybind/operators.h include/pybind/pybind.h include/pybind/pytypes.h include/pybind/typeid.h + include/pybind/numpy.h example/example.cpp example/example1.cpp example/example2.cpp @@ -59,6 +59,7 @@ add_library(example SHARED example/example7.cpp example/example8.cpp example/example9.cpp + example/example10.cpp ) set_target_properties(example PROPERTIES PREFIX "") diff --git a/README.md b/README.md index a526bdd6..bb2b1292 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,13 @@ become an excessively large and unnecessary dependency. Think of this library as a tiny self-contained version of Boost.Python with everything stripped away that isn't relevant for binding generation. The whole -codebase requires just over 2000 lines of code and just depends on Python and +codebase requires just over 2000 lines of code and only depends on Python and the C++ standard library. This compact implementation was possible thanks to some of the new C++11 language features (tuples, lambda functions and variadic templates), and by only targeting Python 3.x and higher. ## Core features -The following C++ features can be mapped to Python +The following core C++ features can be mapped to Python - Functions accepting and returning custom data structures per value, reference, or pointer - Instance methods and static methods @@ -44,12 +44,18 @@ In addition to the core functionality, pybind11 provides some extra goodies: Pythons' buffer protocols. This is handy e.g. for fast conversion between C++ matrix classes like Eigen and NumPy without expensive copy operations. +- pybind11 can automatically vectorize functions so that they are transparently + applied to all entries of one or more NumPy array arguments. + - Python's slice-based access and assignment operations can be supported with just a few lines of code. - pybind11 uses C++11 move constructors and move assignment operators whenever possible to efficiently transfer custom data types. +- It is possible to bind C++11 lambda functions with captured variables. The + lambda capture data is stored inside the resulting Python function object. + ## Limitations Various things that Boost.Python can do remain unsupported, e.g.: @@ -104,3 +110,152 @@ PYTHON_PLUGIN(example) { return m.ptr(); } ``` + +## A collection of specific use cases (mostly buffer-related for now) +For brevity, let's set +```C++ +namespace py = pybind; +``` +### Exposing buffer views +Python supports an extremely general and convenient approach for exchanging +data between plugin libraries. Types can expose a buffer view which provides +fast direct access to the raw internal representation. Suppose we want to bind +the following simplistic Matrix class: + +```C++ +class Matrix { +public: + Matrix(size_t rows, size_t cols) : m_rows(rows), m_cols(cols) { + m_data = new float[rows*cols]; + } + float *data() { return m_data; } + size_t rows() const { return m_rows; } + size_t cols() const { return m_cols; } +private: + size_t m_rows, m_cols; + float *m_data; +}; +``` +The following binding code exposes the ``Matrix`` contents as a buffer object, +making it possible to cast Matrixes into NumPy arrays. It is even possible to +completely avoid copy operations with Python expressions like +``np.array(matrix_instance, copy = False)``. +```C++ +py::class_(m, "Matrix") + .def_buffer([](Matrix &m) -> py::buffer_info { + return py::buffer_info( + m.data(), /* Pointer to buffer */ + sizeof(float), /* Size of one scalar */ + py::format_descriptor::value(), /* Python struct-style format descriptor */ + 2, /* Number of dimensions */ + { m.rows(), m.cols() }, /* Buffer dimensions */ + { sizeof(float) * m.rows(), /* Strides (in bytes) for each index */ + sizeof(float) } + ); + }); +``` +The snippet above binds a lambda function, which can create ``py::buffer_info`` +description records on demand describing a given matrix. The contents of +``py::buffer_info`` mirror the Python buffer protocol specification. +```C++ +struct buffer_info { + void *ptr; + size_t itemsize; + std::string format; + int ndim; + std::vector shape; + std::vector strides; +}; +``` +### Taking Python buffer objects as arguments +To create a C++ function that can take a Python buffer object as an argument, +simply use the type ``py::buffer`` as one of its arguments. Buffers can exist +in a great variety of configurations, hence some safety checks are usually +necessary in the function body. Below, you can see an basic example on how to +define a custom constructor for the Eigen double precision matrix +(``Eigen::MatrixXd``) type, which supports initialization from compatible +buffer +objects (e.g. a NumPy matrix). +```C++ +py::class_(m, "MatrixXd") + .def("__init__", [](Eigen::MatrixXd &m, py::buffer b) { + /* Request a buffer descriptor from Python */ + py::buffer_info info = b.request(); + + /* Some sanity checks ... */ + if (info.format != py::format_descriptor::value()) + throw std::runtime_error("Incompatible format: expected a double array!"); + + if (info.ndim != 2) + throw std::runtime_error("Incompatible buffer dimension!"); + + if (info.strides[0] == sizeof(double)) { + /* Buffer has the right layout -- directly copy. */ + new (&m) Eigen::MatrixXd(info.shape[0], info.shape[1]); + memcpy(m.data(), info.ptr, sizeof(double) * m.size()); + } else { + /* Oops -- the buffer is transposed */ + new (&m) Eigen::MatrixXd(info.shape[1], info.shape[0]); + memcpy(m.data(), info.ptr, sizeof(double) * m.size()); + m.transposeInPlace(); + } + }); +``` + +### Taking NumPy arrays as arguments + +By exchanging ``py::buffer`` with ``py::array`` in the above snippet, we can +restrict the function so that it only accepts NumPy arrays (rather than any +type of Python object satisfying the buffer object protocol). + +In many situations, we want to define a function which only accepts a NumPy +array of a certain data type. This is possible via the ``py::array_dtype`` +template. For instance, the following function requires the argument to be a +dense array of doubles in C-style ordering. +```C++ +void f(py::array_dtype array); +``` +When it is invoked with a different type (e.g. an integer), the binding code +will attempt to cast the input into a NumPy array of the requested type. + +### Auto-vectorizing a function over NumPy array arguments +Suppose we want to bind a function with the following signature to Python so +that it can process arbitrary NumPy array arguments (vectors, matrices, general +N-D arrays) in addition to its normal arguments: +```C++ +double my_func(int x, float y, double z); +``` +This is extremely simple to do! +```C++ +m.def("vectorized_func", py::vectorize(my_func)); +``` +Invoking the function like below causes 4 calls to be made to ``my_func`` with +each of the the array elements. The result is returned as a NumPy array of type +``numpy.dtype.float64``. +```Python +>>> x = np.array([[1, 3],[5, 7]]) +>>> y = np.array([[2, 4],[6, 8]]) +>>> z = 3 +>>> result = vectorized_func(x, y, z) +``` +The scalar argument ``z`` is transparently replicated 4 times. The input +arrays ``x`` and ``y`` are automatically converted into the right types (they +are of type ``numpy.dtype.int64`` but need to be ``numpy.dtype.int32`` and +``numpy.dtype.float32``, respectively) + +Sometimes we might want to explitly exclude an argument from the vectorization +because it makes little sense to wrap it in a NumPy array. For instance, +suppose the function signature was +```C++ +double my_func(int x, float y, my_custom_type *z); +``` +This can be done with a stateful Lambda closure: +```C++ +// Vectorize a lambda function with a capture object (e.g. to exclude some arguments from the vectorization) +m.def("vectorized_func", + [](py::array_dtype x, py::array_dtype y, my_custom_type *z) { + auto stateful_closure = [z](int x, float y) { return my_func(x, y, z); }; + return py::vectorize(stateful_closure)(x, y); + } +); +``` diff --git a/example/example.cpp b/example/example.cpp index 800fb9f8..d60b24e9 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -18,6 +18,7 @@ void init_ex6(py::module &); void init_ex7(py::module &); void init_ex8(py::module &); void init_ex9(py::module &); +void init_ex10(py::module &); PYTHON_PLUGIN(example) { py::module m("example", "pybind example plugin"); @@ -31,6 +32,7 @@ PYTHON_PLUGIN(example) { init_ex7(m); init_ex8(m); init_ex9(m); + init_ex10(m); return m.ptr(); } diff --git a/example/example10.cpp b/example/example10.cpp new file mode 100644 index 00000000..b53c053e --- /dev/null +++ b/example/example10.cpp @@ -0,0 +1,28 @@ +/* + example/example10.cpp -- Example 10: auto-vectorize functions for NumPy + + Copyright (c) 2015 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include "example.h" +#include + +double my_func(int x, float y, double z) { + std::cout << "my_func(x:int=" << x << ", y:float=" << y << ", z:float=" << z << ")" << std::endl; + return x*y*z; +} + +void init_ex10(py::module &m) { + // Vectorize all arguments (though non-vector arguments are also allowed) + m.def("vectorized_func", py::vectorize(my_func)); + + // Vectorize a lambda function with a capture object (e.g. to exclude some arguments from the vectorization) + m.def("vectorized_func2", + [](py::array_dtype x, py::array_dtype y, float z) { + return py::vectorize([z](int x, float y) { return my_func(x, y, z); })(x, y); + } + ); +} diff --git a/example/example10.py b/example/example10.py new file mode 100644 index 00000000..33729267 --- /dev/null +++ b/example/example10.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +import sys +sys.path.append('.') + +import example +import numpy as np + +from example import vectorized_func +from example import vectorized_func2 + +for f in [vectorized_func, vectorized_func2]: + print(f(1, 2, 3)) + print(f(np.array(1), np.array(2), 3)) + print(f(np.array([1, 3]), np.array([2, 4]), 3)) + print(f(np.array([[1, 3, 5], [7, 9, 11]]), np.array([[2, 4, 6], [8, 10, 12]]), 3)) + print(np.array([[1, 3, 5], [7, 9, 11]])* np.array([[2, 4, 6], [8, 10, 12]])*3) diff --git a/include/pybind/cast.h b/include/pybind/cast.h index a3143d20..6c26f41b 100644 --- a/include/pybind/cast.h +++ b/include/pybind/cast.h @@ -11,7 +11,6 @@ #pragma once #include -#include #include #include #include @@ -63,7 +62,8 @@ public: return Py_None; } // avoid an issue with internal references matching their parent's address - bool dont_cache = parent && ((instance *) parent)->value == (void *) src; + bool dont_cache = policy == return_value_policy::reference_internal && + parent && ((instance *) parent)->value == (void *) src; auto& internals = get_internals(); auto it_instance = internals.registered_instances.find(src); if (it_instance != internals.registered_instances.end() && !dont_cache) { @@ -126,7 +126,7 @@ protected: object temp; }; -#define TYPE_CASTER(type, py_name) \ +#define PYBIND_TYPE_CASTER(type, py_name) \ protected: \ type value; \ public: \ @@ -137,7 +137,7 @@ protected: operator type*() { return &value; } \ operator type&() { return value; } \ -#define TYPE_CASTER_NUMBER(type, py_type, from_type, to_pytype) \ +#define PYBIND_TYPE_CASTER_NUMBER(type, py_type, from_type, to_pytype) \ template <> class type_caster { \ public: \ bool load(PyObject *src, bool) { \ @@ -151,30 +151,30 @@ protected: static PyObject *cast(type src, return_value_policy /* policy */, PyObject * /* parent */) { \ return to_pytype((py_type) src); \ } \ - TYPE_CASTER(type, #type); \ + PYBIND_TYPE_CASTER(type, #type); \ }; -TYPE_CASTER_NUMBER(int32_t, long, PyLong_AsLong, PyLong_FromLong) -TYPE_CASTER_NUMBER(uint32_t, unsigned long, PyLong_AsUnsignedLong, PyLong_FromUnsignedLong) -TYPE_CASTER_NUMBER(int64_t, PY_LONG_LONG, PyLong_AsLongLong, PyLong_FromLongLong) -TYPE_CASTER_NUMBER(uint64_t, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong, PyLong_FromUnsignedLongLong) +PYBIND_TYPE_CASTER_NUMBER(int32_t, long, PyLong_AsLong, PyLong_FromLong) +PYBIND_TYPE_CASTER_NUMBER(uint32_t, unsigned long, PyLong_AsUnsignedLong, PyLong_FromUnsignedLong) +PYBIND_TYPE_CASTER_NUMBER(int64_t, PY_LONG_LONG, PyLong_AsLongLong, PyLong_FromLongLong) +PYBIND_TYPE_CASTER_NUMBER(uint64_t, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong, PyLong_FromUnsignedLongLong) #if defined(__APPLE__) // size_t/ssize_t are separate types on Mac OS X -TYPE_CASTER_NUMBER(ssize_t, Py_ssize_t, PyLong_AsSsize_t, PyLong_FromSsize_t) -TYPE_CASTER_NUMBER(size_t, size_t, PyLong_AsSize_t, PyLong_FromSize_t) +PYBIND_TYPE_CASTER_NUMBER(ssize_t, Py_ssize_t, PyLong_AsSsize_t, PyLong_FromSsize_t) +PYBIND_TYPE_CASTER_NUMBER(size_t, size_t, PyLong_AsSize_t, PyLong_FromSize_t) #endif -TYPE_CASTER_NUMBER(float, float, PyFloat_AsDouble, PyFloat_FromDouble) -TYPE_CASTER_NUMBER(double, double, PyFloat_AsDouble, PyFloat_FromDouble) +PYBIND_TYPE_CASTER_NUMBER(float, float, PyFloat_AsDouble, PyFloat_FromDouble) +PYBIND_TYPE_CASTER_NUMBER(double, double, PyFloat_AsDouble, PyFloat_FromDouble) -template <> class type_caster { +template <> class type_caster { public: bool load(PyObject *, bool) { return true; } - static PyObject *cast(mpl::detail::void_type, return_value_policy /* policy */, PyObject * /* parent */) { + static PyObject *cast(detail::void_type, return_value_policy /* policy */, PyObject * /* parent */) { Py_INCREF(Py_None); return Py_None; } - TYPE_CASTER(mpl::detail::void_type, "None"); + PYBIND_TYPE_CASTER(detail::void_type, "None"); }; template <> class type_caster { @@ -189,7 +189,7 @@ public: Py_INCREF(result); return result; } - TYPE_CASTER(bool, "bool"); + PYBIND_TYPE_CASTER(bool, "bool"); }; template <> class type_caster { @@ -203,7 +203,7 @@ public: static PyObject *cast(const std::string &src, return_value_policy /* policy */, PyObject * /* parent */) { return PyUnicode_FromString(src.c_str()); } - TYPE_CASTER(std::string, "str"); + PYBIND_TYPE_CASTER(std::string, "str"); }; #ifdef HAVE_WCHAR_H @@ -218,7 +218,7 @@ public: static PyObject *cast(const std::wstring &src, return_value_policy /* policy */, PyObject * /* parent */) { return PyUnicode_FromWideChar(src.c_str(), src.length()); } - TYPE_CASTER(std::wstring, "wstr"); + PYBIND_TYPE_CASTER(std::wstring, "wstr"); }; #endif @@ -280,7 +280,7 @@ public: } return list; } - TYPE_CASTER(type, "list<" + value_conv::name() + ">"); + PYBIND_TYPE_CASTER(type, "list<" + value_conv::name() + ">"); }; template struct type_caster> { @@ -322,7 +322,7 @@ public: } return dict; } - TYPE_CASTER(type, "dict<" + key_conv::name() + ", " + value_conv::name() + ">"); + PYBIND_TYPE_CASTER(type, "dict<" + key_conv::name() + ", " + value_conv::name() + ">"); }; template class type_caster> { @@ -337,8 +337,8 @@ public: } static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent) { - PyObject *o1 = type_caster::type>::cast(src.first, policy, parent); - PyObject *o2 = type_caster::type>::cast(src.second, policy, parent); + PyObject *o1 = type_caster::type>::cast(src.first, policy, parent); + PyObject *o2 = type_caster::type>::cast(src.second, policy, parent); if (!o1 || !o2) { Py_XDECREF(o1); Py_XDECREF(o2); @@ -358,8 +358,8 @@ public: return type(first, second); } protected: - type_caster::type> first; - type_caster::type> second; + type_caster::type> first; + type_caster::type> second; }; template class type_caster> { @@ -368,16 +368,16 @@ public: enum { size = sizeof...(Tuple) }; bool load(PyObject *src, bool convert) { - return load(src, convert, typename mpl::make_index_sequence::type()); + return load(src, convert, typename make_index_sequence::type()); } static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent) { - return cast(src, policy, parent, typename mpl::make_index_sequence::type()); + return cast(src, policy, parent, typename make_index_sequence::type()); } static std::string name() { std::array names {{ - type_caster::type>::name()... + type_caster::type>::name()... }}; std::string result("("); int counter = 0; @@ -390,15 +390,29 @@ public: return result; } - operator type() { - return cast(typename mpl::make_index_sequence::type()); + template typename std::enable_if::value, ReturnValue>::type call(Func &f) { + return call(f, typename make_index_sequence::type()); } + + template typename std::enable_if::value, detail::void_type>::type call(Func &f) { + call(f, typename make_index_sequence::type()); + return detail::void_type(); + } + + operator type() { + return cast(typename make_index_sequence::type()); + } + protected: - template type cast(mpl::index_sequence) { + template ReturnValue call(Func &f, index_sequence) { + return f((Tuple) std::get(value)...); + } + + template type cast(index_sequence) { return type((Tuple) std::get(value)...); } - template bool load(PyObject *src, bool convert, mpl::index_sequence) { + template bool load(PyObject *src, bool convert, index_sequence) { if (!PyTuple_Check(src)) return false; if (PyTuple_Size(src) != size) @@ -413,9 +427,9 @@ protected: } /* Implementation: Convert a C++ tuple into a Python tuple */ - template static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent, mpl::index_sequence) { + template static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent, index_sequence) { std::array results {{ - type_caster::type>::cast(std::get(src), policy, parent)... + type_caster::type>::cast(std::get(src), policy, parent)... }}; bool success = true; for (auto result : results) @@ -436,7 +450,7 @@ protected: } protected: - std::tuple::type>...> value; + std::tuple::type>...> value; }; /// Type caster for holder types like std::shared_ptr, etc. @@ -467,39 +481,29 @@ public: src.inc_ref(); return (PyObject *) src.ptr(); } - TYPE_CASTER(handle, "handle"); + PYBIND_TYPE_CASTER(handle, "handle"); }; -#define TYPE_CASTER_PYTYPE(name) \ +#define PYBIND_TYPE_CASTER_PYTYPE(name) \ template <> class type_caster { \ public: \ bool load(PyObject *src, bool) { value = name(src, true); return true; } \ static PyObject *cast(const name &src, return_value_policy /* policy */, PyObject * /* parent */) { \ src.inc_ref(); return (PyObject *) src.ptr(); \ } \ - TYPE_CASTER(name, #name); \ + PYBIND_TYPE_CASTER(name, #name); \ }; -TYPE_CASTER_PYTYPE(object) -TYPE_CASTER_PYTYPE(buffer) -TYPE_CASTER_PYTYPE(capsule) -TYPE_CASTER_PYTYPE(dict) -TYPE_CASTER_PYTYPE(float_) -TYPE_CASTER_PYTYPE(int_) -TYPE_CASTER_PYTYPE(list) -TYPE_CASTER_PYTYPE(slice) -TYPE_CASTER_PYTYPE(tuple) -TYPE_CASTER_PYTYPE(function) -TYPE_CASTER_PYTYPE(array) - -#undef TYPE_CASTER -#undef TYPE_CASTER_PYTYPE -#undef TYPE_CASTER_NUMBER +PYBIND_TYPE_CASTER_PYTYPE(object) PYBIND_TYPE_CASTER_PYTYPE(buffer) +PYBIND_TYPE_CASTER_PYTYPE(capsule) PYBIND_TYPE_CASTER_PYTYPE(dict) +PYBIND_TYPE_CASTER_PYTYPE(float_) PYBIND_TYPE_CASTER_PYTYPE(int_) +PYBIND_TYPE_CASTER_PYTYPE(list) PYBIND_TYPE_CASTER_PYTYPE(slice) +PYBIND_TYPE_CASTER_PYTYPE(tuple) PYBIND_TYPE_CASTER_PYTYPE(function) NAMESPACE_END(detail) template inline T cast(PyObject *object) { - detail::type_caster::type> conv; + detail::type_caster::type> conv; if (!conv.load(object, true)) throw cast_error("Unable to cast Python object to C++ type"); return conv; @@ -508,7 +512,7 @@ template inline T cast(PyObject *object) { template inline object cast(const T &value, return_value_policy policy = return_value_policy::automatic, PyObject *parent = nullptr) { if (policy == return_value_policy::automatic) policy = std::is_pointer::value ? return_value_policy::take_ownership : return_value_policy::copy; - return object(detail::type_caster::type>::cast(value, policy, parent), false); + return object(detail::type_caster::type>::cast(value, policy, parent), false); } template inline T handle::cast() { return pybind::cast(m_ptr); } @@ -516,7 +520,7 @@ template inline T handle::cast() { return pybind::cast(m_ptr); } template inline object handle::call(Args&&... args_) { const size_t size = sizeof...(Args); std::array args{ - { detail::type_caster::type>::cast( + { detail::type_caster::type>::cast( std::forward(args_), return_value_policy::automatic, nullptr)... } }; bool fail = false; diff --git a/include/pybind/common.h b/include/pybind/common.h index 0bbaa413..d1a6b47e 100644 --- a/include/pybind/common.h +++ b/include/pybind/common.h @@ -30,10 +30,10 @@ #include #include #include -#include #include #include #include +#include /// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode #if defined(_MSC_VER) @@ -79,31 +79,27 @@ enum class return_value_policy : int { /// Format strings for basic number types template struct format_descriptor { }; -template<> struct format_descriptor { static std::string value() { return "b"; }; }; -template<> struct format_descriptor { static std::string value() { return "B"; }; }; -template<> struct format_descriptor { static std::string value() { return "h"; }; }; -template<> struct format_descriptor { static std::string value() { return "H"; }; }; -template<> struct format_descriptor { static std::string value() { return "i"; }; }; -template<> struct format_descriptor { static std::string value() { return "I"; }; }; -template<> struct format_descriptor { static std::string value() { return "q"; }; }; -template<> struct format_descriptor { static std::string value() { return "Q"; }; }; -template<> struct format_descriptor { static std::string value() { return "f"; }; }; -template<> struct format_descriptor { static std::string value() { return "d"; }; }; +#define DECL_FMT(t, n) template<> struct format_descriptor { static std::string value() { return n; }; }; +DECL_FMT(int8_t, "b"); DECL_FMT(uint8_t, "B"); DECL_FMT(int16_t, "h"); DECL_FMT(uint16_t, "H"); +DECL_FMT(int32_t, "i"); DECL_FMT(uint32_t, "I"); DECL_FMT(int64_t, "q"); DECL_FMT(uint64_t, "Q"); +DECL_FMT(float , "f"); DECL_FMT(double, "d"); +#undef DECL_FMT /// Information record describing a Python buffer object struct buffer_info { void *ptr; - size_t itemsize; + size_t itemsize, count; std::string format; // for dense contents, this should be set to format_descriptor::value int ndim; std::vector shape; std::vector strides; - buffer_info(void *ptr, size_t itemsize, const std::string &format, - int ndim, const std::vector &shape, - const std::vector &strides) + buffer_info(void *ptr, size_t itemsize, const std::string &format, int ndim, + const std::vector &shape, const std::vector &strides) : ptr(ptr), itemsize(itemsize), format(format), ndim(ndim), - shape(shape), strides(strides) {} + shape(shape), strides(strides) { + count = 1; for (int i=0; i registered_instances; }; +/// Return a reference to the current 'internals' information inline internals &get_internals(); +/// Index sequence for convenient template metaprogramming involving tuples +template struct index_sequence { }; +template struct make_index_sequence : make_index_sequence { }; +template struct make_index_sequence <0, S...> { typedef index_sequence type; }; + +/// Strip the class from a method type +template struct remove_class {}; +template struct remove_class { typedef R type(A...); }; +template struct remove_class { typedef R type(A...); }; + +/// Helper template to strip away type modifiers +template struct decay { typedef T type; }; +template struct decay { typedef typename decay::type type; }; +template struct decay { typedef typename decay::type type; }; +template struct decay { typedef typename decay::type type; }; +template struct decay { typedef typename decay::type type; }; +template struct decay { typedef typename decay::type type; }; +template struct decay { typedef typename decay::type type; }; + +/// Helper type to replace 'void' in some expressions +struct void_type { }; NAMESPACE_END(detail) NAMESPACE_END(pybind) diff --git a/include/pybind/mpl.h b/include/pybind/mpl.h deleted file mode 100644 index 27d18b40..00000000 --- a/include/pybind/mpl.h +++ /dev/null @@ -1,195 +0,0 @@ -/* - pybind/mpl.h: Simple library for type manipulation and template metaprogramming - - Copyright (c) 2015 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include -#include - -NAMESPACE_BEGIN(pybind) -NAMESPACE_BEGIN(mpl) - -/// Index sequence for convenient template metaprogramming involving tuples -template struct index_sequence { }; -template struct make_index_sequence : make_index_sequence { }; -template struct make_index_sequence <0, S...> { typedef index_sequence type; }; - -/// Helper template to strip away type modifiers -template struct normalize_type { typedef T type; }; -template struct normalize_type { typedef typename normalize_type::type type; }; -template struct normalize_type { typedef typename normalize_type::type type; }; -template struct normalize_type { typedef typename normalize_type::type type; }; -template struct normalize_type { typedef typename normalize_type::type type; }; -template struct normalize_type { typedef typename normalize_type::type type; }; -template struct normalize_type { typedef typename normalize_type::type type; }; - -NAMESPACE_BEGIN(detail) - -/// Strip the class from a method type -template struct remove_class {}; -template struct remove_class { typedef R type(A...); }; -template struct remove_class { typedef R type(A...); }; - -/** - * \brief Convert a lambda function to a std::function - * From http://stackoverflow.com/questions/11893141/inferring-the-call-signature-of-a-lambda-or-arbitrary-callable-for-make-functio - */ -template struct lambda_signature_impl { - using type = typename remove_class< - decltype(&std::remove_reference::type::operator())>::type; -}; -template struct lambda_signature_impl { typedef R type(A...); }; -template struct lambda_signature_impl { typedef R type(A...); }; -template struct lambda_signature_impl { typedef R type(A...); }; -template using lambda_signature = typename lambda_signature_impl::type; -template using make_function_type = std::function>; - -NAMESPACE_END(detail) - -template detail::make_function_type make_function(F &&f) { - return detail::make_function_type(std::forward(f)); } - -NAMESPACE_BEGIN(detail) - -struct void_type { }; - -/// Helper functions for calling a function using a tuple argument while dealing with void/non-void return values -template struct tuple_dispatch { - typedef RetType return_type; - template return_type operator()(const Func &f, Arg && args, index_sequence) { - return f(std::get(std::forward(args))...); - } -}; - -/// Helper functions for calling a function using a tuple argument (special case for void return values) -template <> struct tuple_dispatch { - typedef void_type return_type; - template return_type operator()(const Func &f, Arg &&args, index_sequence) { - f(std::get(std::forward(args))...); - return return_type(); - } -}; - -NAMESPACE_END(detail) - -/// For lambda functions delegate to their 'operator()' -template struct function_traits : function_traits> { }; - -/* Deal with reference arguments */ -template - struct function_traits : function_traits {}; -template - struct function_traits : function_traits {}; -template - struct function_traits : function_traits {}; - -/// Type traits for function pointers -template -struct function_traits { - enum { - nargs = sizeof...(Args), - is_method = 0, - is_const = 0 - }; - typedef std::function f_type; - typedef detail::tuple_dispatch dispatch_type; - typedef typename dispatch_type::return_type return_type; - typedef std::tuple args_type; - - template struct arg { - typedef typename std::tuple_element::type type; - }; - - static f_type cast(ReturnType (*func)(Args ...)) { return func; } - - static return_type dispatch(const f_type &f, args_type &&args) { - return dispatch_type()(f, std::move(args), - typename make_index_sequence::type()); - } -}; - -/// Type traits for ordinary methods -template -struct function_traits { - enum { - nargs = sizeof...(Args), - is_method = 1, - is_const = 0 - }; - typedef std::function f_type; - typedef detail::tuple_dispatch dispatch_type; - typedef typename dispatch_type::return_type return_type; - typedef std::tuple args_type; - - template struct arg { - typedef typename std::tuple_element::type type; - }; - - static f_type cast(ReturnType (ClassType::*func)(Args ...)) { return std::mem_fn(func); } - - static return_type dispatch(const f_type &f, args_type &&args) { - return dispatch_type()(f, std::move(args), - typename make_index_sequence::type()); - } -}; - -/// Type traits for const methods -template -struct function_traits { - enum { - nargs = sizeof...(Args), - is_method = 1, - is_const = 1 - }; - typedef std::function f_type; - typedef detail::tuple_dispatch dispatch_type; - typedef typename dispatch_type::return_type return_type; - typedef std::tuple args_type; - - template struct arg { - typedef typename std::tuple_element::type type; - }; - - static f_type cast(ReturnType (ClassType::*func)(Args ...) const) { - return std::mem_fn(func); - } - - static return_type dispatch(const f_type &f, args_type &&args) { - return dispatch_type()(f, std::move(args), - typename make_index_sequence::type()); - } -}; - -/// Type traits for std::functions -template -struct function_traits> { - enum { - nargs = sizeof...(Args), - is_method = 0, - is_const = 0 - }; - typedef std::function f_type; - typedef detail::tuple_dispatch dispatch_type; - typedef typename dispatch_type::return_type return_type; - typedef std::tuple args_type; - - template struct arg { - typedef typename std::tuple_element::type type; - }; - - static f_type cast(const f_type &func) { return func; } - - static return_type dispatch(const f_type &f, args_type &&args) { - return dispatch_type()(f, std::move(args), - typename make_index_sequence::type()); - } -}; - -NAMESPACE_END(mpl) -NAMESPACE_END(pybind) diff --git a/include/pybind/numpy.h b/include/pybind/numpy.h new file mode 100644 index 00000000..f4a4a74e --- /dev/null +++ b/include/pybind/numpy.h @@ -0,0 +1,201 @@ +/* + pybind/numpy.h: Basic NumPy support, auto-vectorization support + + Copyright (c) 2015 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4127) // warning C4127: Conditional expression is constant +#endif + +NAMESPACE_BEGIN(pybind) + +class array : public buffer { +protected: + struct API { + enum Entries { + API_PyArray_Type = 2, + API_PyArray_DescrFromType = 45, + API_PyArray_FromAny = 69, + API_PyArray_NewCopy = 85, + API_PyArray_NewFromDescr = 94, + API_NPY_C_CONTIGUOUS = 0x0001, + API_NPY_F_CONTIGUOUS = 0x0002, + API_NPY_NPY_ARRAY_FORCECAST = 0x0010, + API_NPY_ENSURE_ARRAY = 0x0040 + }; + + static API lookup() { + PyObject *numpy = PyImport_ImportModule("numpy.core.multiarray"); + PyObject *capsule = numpy ? PyObject_GetAttrString(numpy, "_ARRAY_API") : nullptr; + void **api_ptr = (void **) (capsule ? PyCapsule_GetPointer(capsule, NULL) : nullptr); + Py_XDECREF(capsule); + Py_XDECREF(numpy); + if (api_ptr == nullptr) + throw std::runtime_error("Could not acquire pointer to NumPy API!"); + API api; + api.PyArray_Type = (decltype(api.PyArray_Type)) api_ptr[API_PyArray_Type]; + api.PyArray_DescrFromType = (decltype(api.PyArray_DescrFromType)) api_ptr[API_PyArray_DescrFromType]; + api.PyArray_FromAny = (decltype(api.PyArray_FromAny)) api_ptr[API_PyArray_FromAny]; + api.PyArray_NewCopy = (decltype(api.PyArray_NewCopy)) api_ptr[API_PyArray_NewCopy]; + api.PyArray_NewFromDescr = (decltype(api.PyArray_NewFromDescr)) api_ptr[API_PyArray_NewFromDescr]; + return api; + } + + bool PyArray_Check(PyObject *obj) const { return (bool) PyObject_TypeCheck(obj, PyArray_Type); } + + PyObject *(*PyArray_DescrFromType)(int); + PyObject *(*PyArray_NewFromDescr) + (PyTypeObject *, PyObject *, int, Py_intptr_t *, + Py_intptr_t *, void *, int, PyObject *); + PyObject *(*PyArray_NewCopy)(PyObject *, int); + PyTypeObject *PyArray_Type; + PyObject *(*PyArray_FromAny) (PyObject *, PyObject *, int, int, int, PyObject *); + }; +public: + PYBIND_OBJECT_DEFAULT(array, buffer, lookup_api().PyArray_Check) + + template array(size_t size, const Type *ptr) { + API& api = lookup_api(); + PyObject *descr = api.PyArray_DescrFromType( + (int) format_descriptor::value()[0]); + if (descr == nullptr) + throw std::runtime_error("NumPy: unsupported buffer format!"); + Py_intptr_t shape = (Py_intptr_t) size; + PyObject *tmp = api.PyArray_NewFromDescr( + api.PyArray_Type, descr, 1, &shape, nullptr, (void *) ptr, 0, nullptr); + if (tmp == nullptr) + throw std::runtime_error("NumPy: unable to create array!"); + m_ptr = api.PyArray_NewCopy(tmp, -1 /* any order */); + Py_DECREF(tmp); + if (m_ptr == nullptr) + throw std::runtime_error("NumPy: unable to copy array!"); + } + + array(const buffer_info &info) { + API& api = lookup_api(); + if (info.format.size() != 1) + throw std::runtime_error("Unsupported buffer format!"); + PyObject *descr = api.PyArray_DescrFromType(info.format[0]); + if (descr == nullptr) + throw std::runtime_error("NumPy: unsupported buffer format '" + info.format + "'!"); + PyObject *tmp = api.PyArray_NewFromDescr( + api.PyArray_Type, descr, info.ndim, (Py_intptr_t *) &info.shape[0], + (Py_intptr_t *) &info.strides[0], info.ptr, 0, nullptr); + if (tmp == nullptr) + throw std::runtime_error("NumPy: unable to create array!"); + m_ptr = api.PyArray_NewCopy(tmp, -1 /* any order */); + Py_DECREF(tmp); + if (m_ptr == nullptr) + throw std::runtime_error("NumPy: unable to copy array!"); + } + +protected: + static API &lookup_api() { + static API api = API::lookup(); + return api; + } +}; + +template class array_dtype : public array { +public: + PYBIND_OBJECT_CVT(array_dtype, array, is_non_null, m_ptr = ensure(m_ptr)); + array_dtype() : array() { } + static bool is_non_null(PyObject *ptr) { return ptr != nullptr; } + static PyObject *ensure(PyObject *ptr) { + API &api = lookup_api(); + PyObject *descr = api.PyArray_DescrFromType(format_descriptor::value()[0]); + return api.PyArray_FromAny(ptr, descr, 0, 0, + API::API_NPY_C_CONTIGUOUS | API::API_NPY_ENSURE_ARRAY | + API::API_NPY_NPY_ARRAY_FORCECAST, nullptr); + } +}; + +NAMESPACE_BEGIN(detail) +PYBIND_TYPE_CASTER_PYTYPE(array) +PYBIND_TYPE_CASTER_PYTYPE(array_dtype) PYBIND_TYPE_CASTER_PYTYPE(array_dtype) +PYBIND_TYPE_CASTER_PYTYPE(array_dtype) PYBIND_TYPE_CASTER_PYTYPE(array_dtype) +PYBIND_TYPE_CASTER_PYTYPE(array_dtype) PYBIND_TYPE_CASTER_PYTYPE(array_dtype) +PYBIND_TYPE_CASTER_PYTYPE(array_dtype) PYBIND_TYPE_CASTER_PYTYPE(array_dtype) +PYBIND_TYPE_CASTER_PYTYPE(array_dtype) PYBIND_TYPE_CASTER_PYTYPE(array_dtype) +NAMESPACE_END(detail) + +template + std::function...)> + vectorize(func_type &&f, return_type (*) (args_type ...), + detail::index_sequence) { + + return [f](array_dtype... args) -> array { + /* Request buffers from all parameters */ + const size_t N = sizeof...(args_type); + std::array buffers {{ args.request()... }}; + + /* Determine dimensions parameters of output array */ + int ndim = 0; size_t count = 0; + std::vector shape; + for (size_t i=0; i count) { + ndim = buffers[i].ndim; + shape = buffers[i].shape; + count = buffers[i].count; + } + } + std::vector strides(ndim); + if (ndim > 0) { + strides[ndim-1] = sizeof(return_type); + for (int i=ndim-1; i>0; --i) + strides[i-1] = strides[i] * shape[i]; + } + + /* Check if the parameters are actually compatible */ + for (size_t i=0; i result(count); + for (size_t i=0; i::value(), + ndim, shape, strides)); + }; +} + +template + std::function...)> + vectorize(func_type &&f, return_type (*f_) (args_type ...) = nullptr) { + return vectorize(f, f_, typename detail::make_index_sequence::type()); +} + +template +std::function...)> vectorize(return_type (*f) (args_type ...)) { + return vectorize(f, f); +} + +template auto vectorize(func &&f) -> decltype( + vectorize(std::forward(f), (typename detail::remove_class::type::operator())>::type *) nullptr)) { + return vectorize(std::forward(f), (typename detail::remove_class::type::operator())>::type *) nullptr); +} + +NAMESPACE_END(pybind) + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif diff --git a/include/pybind/operators.h b/include/pybind/operators.h index 9e3011a2..c2c13383 100644 --- a/include/pybind/operators.h +++ b/include/pybind/operators.h @@ -17,13 +17,12 @@ NAMESPACE_BEGIN(detail) /// Enumeration with all supported operator types enum op_id : int { - op_add, op_sub, op_mul, op_div, op_mod, op_divmod, op_pow, - op_lshift, op_rshift, op_and, op_xor, op_or, op_neg, - op_pos, op_abs, op_invert, op_int, op_long, op_float, - op_str, op_cmp, op_gt, op_ge, op_lt, op_le, op_eq, op_ne, - op_iadd, op_isub, op_imul, op_idiv, op_imod, op_ilshift, - op_irshift, op_iand, op_ixor, op_ior, op_complex, op_bool, - op_nonzero, op_repr, op_truediv + op_add, op_sub, op_mul, op_div, op_mod, op_divmod, op_pow, op_lshift, + op_rshift, op_and, op_xor, op_or, op_neg, op_pos, op_abs, op_invert, + op_int, op_long, op_float, op_str, op_cmp, op_gt, op_ge, op_lt, op_le, + op_eq, op_ne, op_iadd, op_isub, op_imul, op_idiv, op_imod, op_ilshift, + op_irshift, op_iand, op_ixor, op_ior, op_complex, op_bool, op_nonzero, + op_repr, op_truediv }; enum op_type : int { @@ -33,12 +32,11 @@ enum op_type : int { }; struct self_t { }; +static const self_t self = self_t(); /// Type for an unused type slot struct undefined_t { }; -static const self_t self = self_t(); - /// Don't warn about an unused variable inline self_t __self() { return self; } @@ -140,7 +138,6 @@ PYBIND_UNARY_OPERATOR(float, float_, (double) l) #undef PYBIND_BINARY_OPERATOR #undef PYBIND_INPLACE_OPERATOR #undef PYBIND_UNARY_OPERATOR - NAMESPACE_END(detail) using detail::self; diff --git a/include/pybind/pybind.h b/include/pybind/pybind.h index d4ebdc80..e8c4ff5d 100644 --- a/include/pybind/pybind.h +++ b/include/pybind/pybind.h @@ -22,49 +22,112 @@ NAMESPACE_BEGIN(pybind) +/// Wraps an arbitrary C++ function/method/lambda function/.. into a callable Python object class cpp_function : public function { -public: +private: + /// Chained list of function entries for overloading struct function_entry { - std::function impl; + PyObject * (*impl) (function_entry *, PyObject *, PyObject *); + void *data; std::string signature, doc; bool is_constructor; + return_value_policy policy; function_entry *next = nullptr; }; + /// Picks a suitable return value converter from cast.h + template using return_value_caster = + detail::type_caster::value, detail::void_type, typename detail::decay::type>::type>; + + /// Picks a suitable argument value converter from cast.h + template using arg_value_caster = + detail::type_caster>; +public: cpp_function() { } - template cpp_function( - Func &&_func, const char *name = nullptr, const char *doc = nullptr, - return_value_policy policy = return_value_policy::automatic, - function sibling = function(), bool is_method = false) { - /* Function traits extracted from the template type 'Func' */ - typedef mpl::function_traits f_traits; - /* Suitable input and output casters */ - typedef typename detail::type_caster cast_in; - typedef typename detail::type_caster::type> cast_out; - typename f_traits::f_type func = f_traits::cast(std::forward(_func)); + /// Vanilla function pointers + template + cpp_function(return_type (*f)(arg_type...), const char *name = nullptr, + const char *doc = nullptr, return_value_policy policy = return_value_policy::automatic, + const function &sibling = function(), bool is_method = false) { - auto impl = [func, policy](PyObject *pyArgs) -> PyObject *{ + typedef arg_value_caster cast_in; + typedef return_value_caster cast_out; + + auto impl = [](function_entry *entry, PyObject *pyArgs, PyObject *parent) -> PyObject * { cast_in args; - if (!args.load(pyArgs, true)) - return nullptr; - PyObject *parent = policy != return_value_policy::reference_internal - ? nullptr : PyTuple_GetItem(pyArgs, 0); - return cast_out::cast( - f_traits::dispatch(func, args.operator typename f_traits::args_type()), - policy, parent); + if (!args.load(pyArgs, true)) return nullptr; + auto f = (return_type (*) (arg_type...)) entry->data; + return cast_out::cast(args.template call(f), + entry->policy, parent); }; initialize(name, doc, cast_in::name() + std::string(" -> ") + cast_out::name(), - sibling, is_method, std::move(impl)); + sibling, is_method, policy, impl, (void *) f); } + + /// Delegating helper constructor to deal with lambda functions + template + cpp_function(func &&f, const char *name = nullptr, + const char *doc = nullptr, + return_value_policy policy = return_value_policy::automatic, + const function &sibling = function(), bool is_method = false) { + initialize(std::forward(f), name, doc, policy, sibling, is_method, + (typename detail::remove_class::type::operator())>::type *) nullptr); + } + + + /// Class methods (non-const) + template cpp_function( + return_type (class_type::*f)(arg_type...), const char *name = nullptr, + const char *doc = nullptr, return_value_policy policy = return_value_policy::automatic, + const function &sibling = function(), bool is_method = false) { + initialize([f](class_type *c, arg_type... args) -> return_type { return (c->*f)(args...); }, + name, doc, policy, sibling, is_method, (return_type (*)(class_type *, arg_type ...)) nullptr); + } + + /// Class methods (const) + template cpp_function( + return_type (class_type::*f)(arg_type...) const, const char *name = nullptr, + const char *doc = nullptr, return_value_policy policy = return_value_policy::automatic, + const function &sibling = function(), bool is_method = false) { + initialize([f](const class_type *c, arg_type... args) -> return_type { return (c->*f)(args...); }, + name, doc, policy, sibling, is_method, (return_type (*)(const class_type *, arg_type ...)) nullptr); + } + private: + /// Functors, lambda functions, etc. + template + void initialize(func &&f, const char *name, const char *doc, + return_value_policy policy, const function &sibling, + bool is_method, return_type (*)(arg_type...)) { + + typedef arg_value_caster cast_in; + typedef return_value_caster cast_out; + struct capture { typename std::remove_reference::type f; }; + void *ptr = new capture { std::forward(f) }; + + auto impl = [](function_entry *entry, PyObject *pyArgs, PyObject *parent) -> PyObject *{ + cast_in args; + if (!args.load(pyArgs, true)) return nullptr; + func &f = ((capture *) entry->data)->f; + return cast_out::cast(args.template call(f), + entry->policy, parent); + }; + + initialize(name, doc, cast_in::name() + std::string(" -> ") + cast_out::name(), + sibling, is_method, policy, impl, ptr); + } + static PyObject *dispatcher(PyObject *self, PyObject *args, PyObject * /* kwargs */) { function_entry *overloads = (function_entry *) PyCapsule_GetPointer(self, nullptr); PyObject *result = nullptr; + PyObject *parent = PyTuple_Size(args) > 0 ? PyTuple_GetItem(args, 0) : nullptr; try { for (function_entry *it = overloads; it != nullptr; it = it->next) { - if ((result = it->impl(args)) != nullptr) + if ((result = it->impl(it, args, parent)) != nullptr) break; } } catch (const error_already_set &) { return nullptr; @@ -100,15 +163,19 @@ private: void initialize(const char *name, const char *doc, const std::string &signature, function sibling, - bool is_method, std::function &&impl) { + bool is_method, return_value_policy policy, + PyObject *(*impl) (function_entry *, PyObject *, PyObject *), + void *data) { if (name == nullptr) name = ""; /* Linked list of function call handlers (for overloading) */ function_entry *entry = new function_entry(); - entry->impl = std::move(impl); + entry->impl = impl; entry->is_constructor = !strcmp(name, "__init__"); + entry->policy = policy; entry->signature = signature; + entry->data = data; if (doc) entry->doc = doc; if (!sibling.ptr() || !PyCFunction_Check(sibling.ptr())) { @@ -159,16 +226,15 @@ private: class cpp_method : public cpp_function { public: cpp_method () { } - template - cpp_method(Func &&_func, const char *name = nullptr, const char *doc = nullptr, - return_value_policy policy = return_value_policy::automatic, - function sibling = function()) - : cpp_function(std::forward(_func), name, doc, policy, sibling, true) { } + template cpp_method(func &&f, const char *name = nullptr, + const char *doc = nullptr, return_value_policy + policy = return_value_policy::automatic, function sibling = function()) + : cpp_function(std::forward(f), name, doc, policy, sibling, true) {} }; class module : public object { public: - PYTHON_OBJECT_DEFAULT(module, object, PyModule_Check) + PYBIND_OBJECT_DEFAULT(module, object, PyModule_Check) module(const char *name, const char *doc = nullptr) { PyModuleDef *def = new PyModuleDef(); @@ -214,7 +280,7 @@ template struct init; /// Basic support for creating new Python heap types class custom_type : public object { public: - PYTHON_OBJECT_DEFAULT(custom_type, object, PyType_Check) + PYBIND_OBJECT_DEFAULT(custom_type, object, PyType_Check) custom_type(object &scope, const char *name_, const std::string &type_name, size_t type_size, size_t instance_size, @@ -364,14 +430,13 @@ protected: static void releasebuffer(PyObject *, Py_buffer *view) { delete (buffer_info *) view->internal; } }; - NAMESPACE_END(detail) template > class class_ : public detail::custom_type { public: typedef detail::instance instance_type; - PYTHON_OBJECT(class_, detail::custom_type, PyType_Check) + PYBIND_OBJECT(class_, detail::custom_type, PyType_Check) class_(object &scope, const char *name, const char *doc = nullptr) : detail::custom_type(scope, name, type_id(), sizeof(type), @@ -603,6 +668,3 @@ NAMESPACE_END(pybind) #if defined(_MSC_VER) #pragma warning(pop) #endif - -#undef PYTHON_OBJECT -#undef PYTHON_OBJECT_DEFAULT diff --git a/include/pybind/pytypes.h b/include/pybind/pytypes.h index 33aa69fa..2586a8ba 100644 --- a/include/pybind/pytypes.h +++ b/include/pybind/pytypes.h @@ -19,9 +19,7 @@ class object; class str; class object; class dict; -NAMESPACE_BEGIN(detail) -class accessor; -NAMESPACE_END(detail) +namespace detail { class accessor; } /// Holds a reference to a Python object (no reference counting) class handle { @@ -189,7 +187,6 @@ private: PyObject *dict, *key, *value; ssize_t pos = 0; }; - NAMESPACE_END(detail) inline detail::accessor handle::operator[](handle key) { return detail::accessor(ptr(), key.ptr(), false); } @@ -197,21 +194,24 @@ inline detail::accessor handle::operator[](const char *key) { return detail::acc inline detail::accessor handle::attr(handle key) { return detail::accessor(ptr(), key.ptr(), true); } inline detail::accessor handle::attr(const char *key) { return detail::accessor(ptr(), key, true); } -#define PYTHON_OBJECT(Name, Parent, CheckFun) \ - Name(const handle &h, bool borrowed) : Parent(h, borrowed) { } \ - Name(const object& o): Parent(o) { } \ - Name(object&& o): Parent(std::move(o)) { } \ - Name& operator=(object&& o) { return static_cast(object::operator=(std::move(o))); } \ - Name& operator=(object& o) { return static_cast(object::operator=(o)); } \ +#define PYBIND_OBJECT_CVT(Name, Parent, CheckFun, CvtStmt) \ + Name(const handle &h, bool borrowed) : Parent(h, borrowed) { CvtStmt; } \ + Name(const object& o): Parent(o) { CvtStmt; } \ + Name(object&& o): Parent(std::move(o)) { CvtStmt; } \ + Name& operator=(object&& o) { return static_cast(object::operator=(std::move(o))); CvtStmt; } \ + Name& operator=(object& o) { return static_cast(object::operator=(o)); CvtStmt; } \ bool check() const { return m_ptr != nullptr && (bool) CheckFun(m_ptr); } -#define PYTHON_OBJECT_DEFAULT(Name, Parent, CheckFun) \ - PYTHON_OBJECT(Name, Parent, CheckFun) \ +#define PYBIND_OBJECT(Name, Parent, CheckFun) \ + PYBIND_OBJECT_CVT(Name, Parent, CheckFun, ) + +#define PYBIND_OBJECT_DEFAULT(Name, Parent, CheckFun) \ + PYBIND_OBJECT(Name, Parent, CheckFun) \ Name() : Parent() { } class str : public object { public: - PYTHON_OBJECT_DEFAULT(str, object, PyUnicode_Check) + PYBIND_OBJECT_DEFAULT(str, object, PyUnicode_Check) str(const char *s) : object(PyUnicode_FromString(s), false) { } operator const char *() const { return PyUnicode_AsUTF8(m_ptr); } }; @@ -221,13 +221,13 @@ inline std::ostream &operator<<(std::ostream &os, const object &obj) { os << (co class bool_ : public object { public: - PYTHON_OBJECT_DEFAULT(bool_, object, PyBool_Check) + PYBIND_OBJECT_DEFAULT(bool_, object, PyBool_Check) operator bool() const { return m_ptr && PyLong_AsLong(m_ptr) != 0; } }; class int_ : public object { public: - PYTHON_OBJECT_DEFAULT(int_, object, PyLong_Check) + PYBIND_OBJECT_DEFAULT(int_, object, PyLong_Check) int_(int value) : object(PyLong_FromLong((long) value), false) { } int_(size_t value) : object(PyLong_FromSize_t(value), false) { } int_(ssize_t value) : object(PyLong_FromSsize_t(value), false) { } @@ -236,7 +236,7 @@ public: class float_ : public object { public: - PYTHON_OBJECT_DEFAULT(float_, object, PyFloat_Check) + PYBIND_OBJECT_DEFAULT(float_, object, PyFloat_Check) float_(float value) : object(PyFloat_FromDouble((double) value), false) { } float_(double value) : object(PyFloat_FromDouble((double) value), false) { } operator float() const { return (float) PyFloat_AsDouble(m_ptr); } @@ -245,7 +245,7 @@ public: class slice : public object { public: - PYTHON_OBJECT_DEFAULT(slice, object, PySlice_Check) + PYBIND_OBJECT_DEFAULT(slice, object, PySlice_Check) slice(ssize_t start_, ssize_t stop_, ssize_t step_) { int_ start(start_), stop(stop_), step(step_); m_ptr = PySlice_New(start.ptr(), stop.ptr(), step.ptr()); @@ -257,7 +257,7 @@ public: class capsule : public object { public: - PYTHON_OBJECT_DEFAULT(capsule, object, PyCapsule_CheckExact) + PYBIND_OBJECT_DEFAULT(capsule, object, PyCapsule_CheckExact) capsule(void *value) : object(PyCapsule_New(value, nullptr, nullptr), false) { } template operator T *() const { T * result = static_cast(PyCapsule_GetPointer(m_ptr, nullptr)); @@ -268,7 +268,7 @@ public: class tuple : public object { public: - PYTHON_OBJECT_DEFAULT(tuple, object, PyTuple_Check) + PYBIND_OBJECT_DEFAULT(tuple, object, PyTuple_Check) tuple(size_t size) : object(PyTuple_New((Py_ssize_t) size), false) { } size_t size() const { return (size_t) PyTuple_Size(m_ptr); } detail::tuple_accessor operator[](size_t index) { return detail::tuple_accessor(ptr(), index); } @@ -276,7 +276,7 @@ public: class dict : public object { public: - PYTHON_OBJECT(dict, object, PyDict_Check) + PYBIND_OBJECT(dict, object, PyDict_Check) dict() : object(PyDict_New(), false) { } size_t size() const { return (size_t) PyDict_Size(m_ptr); } detail::dict_iterator begin() { return (++detail::dict_iterator(ptr(), 0)); } @@ -285,7 +285,7 @@ public: class list : public object { public: - PYTHON_OBJECT(list, object, PyList_Check) + PYBIND_OBJECT(list, object, PyList_Check) list(size_t size = 0) : object(PyList_New((ssize_t) size), false) { } size_t size() const { return (size_t) PyList_Size(m_ptr); } detail::list_iterator begin() { return detail::list_iterator(ptr(), 0); } @@ -296,12 +296,12 @@ public: class function : public object { public: - PYTHON_OBJECT_DEFAULT(function, object, PyFunction_Check) + PYBIND_OBJECT_DEFAULT(function, object, PyFunction_Check) }; class buffer : public object { public: - PYTHON_OBJECT_DEFAULT(buffer, object, PyObject_CheckBuffer) + PYBIND_OBJECT_DEFAULT(buffer, object, PyObject_CheckBuffer) buffer_info request(bool writable = false) { int flags = PyBUF_STRIDES | PyBUF_FORMAT; @@ -322,88 +322,6 @@ private: Py_buffer *view = nullptr; }; -class array : public buffer { -protected: - struct API { - enum Entries { - API_PyArray_Type = 2, - API_PyArray_DescrFromType = 45, - API_PyArray_NewCopy = 85, - API_PyArray_NewFromDescr = 94 - }; - - static API lookup() { - PyObject *numpy = PyImport_ImportModule("numpy.core.multiarray"); - PyObject *capsule = numpy ? PyObject_GetAttrString(numpy, "_ARRAY_API") : nullptr; - void **api_ptr = (void **) (capsule ? PyCapsule_GetPointer(capsule, NULL) : nullptr); - Py_XDECREF(capsule); - Py_XDECREF(numpy); - if (api_ptr == nullptr) - throw std::runtime_error("Could not acquire pointer to NumPy API!"); - API api; - api.PyArray_DescrFromType = (decltype(api.PyArray_DescrFromType)) api_ptr[API_PyArray_DescrFromType]; - api.PyArray_NewFromDescr = (decltype(api.PyArray_NewFromDescr)) api_ptr[API_PyArray_NewFromDescr]; - api.PyArray_NewCopy = (decltype(api.PyArray_NewCopy)) api_ptr[API_PyArray_NewCopy]; - api.PyArray_Type = (decltype(api.PyArray_Type)) api_ptr[API_PyArray_Type]; - return api; - } - - bool PyArray_Check(PyObject *obj) const { - return (bool) PyObject_TypeCheck(obj, PyArray_Type); - } - - PyObject *(*PyArray_DescrFromType)(int); - PyObject *(*PyArray_NewFromDescr) - (PyTypeObject *, PyObject *, int, Py_intptr_t *, - Py_intptr_t *, void *, int, PyObject *); - PyObject *(*PyArray_NewCopy)(PyObject *, int); - PyTypeObject *PyArray_Type; - }; -public: - PYTHON_OBJECT_DEFAULT(array, buffer, lookup_api().PyArray_Check) - - template array(size_t size, const Type *ptr) { - API& api = lookup_api(); - PyObject *descr = api.PyArray_DescrFromType( - (int) format_descriptor::value()[0]); - if (descr == nullptr) - throw std::runtime_error("NumPy: unsupported buffer format!"); - Py_intptr_t shape = (Py_intptr_t) size; - PyObject *tmp = api.PyArray_NewFromDescr( - api.PyArray_Type, descr, 1, &shape, nullptr, (void *) ptr, 0, nullptr); - if (tmp == nullptr) - throw std::runtime_error("NumPy: unable to create array!"); - m_ptr = api.PyArray_NewCopy(tmp, -1 /* any order */); - Py_DECREF(tmp); - if (m_ptr == nullptr) - throw std::runtime_error("NumPy: unable to copy array!"); - } - - array(const buffer_info &info) { - API& api = lookup_api(); - if (info.format.size() != 1) - throw std::runtime_error("Unsupported buffer format!"); - PyObject *descr = api.PyArray_DescrFromType(info.format[0]); - if (descr == nullptr) - throw std::runtime_error("NumPy: unsupported buffer format '" + info.format + "'!"); - PyObject *tmp = api.PyArray_NewFromDescr( - api.PyArray_Type, descr, info.ndim, (Py_intptr_t *) &info.shape[0], - (Py_intptr_t *) &info.strides[0], info.ptr, 0, nullptr); - if (tmp == nullptr) - throw std::runtime_error("NumPy: unable to create array!"); - m_ptr = api.PyArray_NewCopy(tmp, -1 /* any order */); - Py_DECREF(tmp); - if (m_ptr == nullptr) - throw std::runtime_error("NumPy: unable to copy array!"); - } -protected: - static API &lookup_api() { - static API api = API::lookup(); - return api; - } -}; - - NAMESPACE_BEGIN(detail) inline internals &get_internals() { static internals *internals_ptr = nullptr;