From a576e6a8ca5f22f9f9d5f149929637c3337ad086 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Wed, 29 Jul 2015 17:51:54 +0200 Subject: [PATCH] keyword argument support, removed last traces of std::function<> usage --- CMakeLists.txt | 3 +- README.md | 17 +--- example/example.cpp | 2 + example/example1.cpp | 4 +- example/example10.cpp | 9 +- example/example10.py | 0 example/example11.cpp | 17 ++++ example/example11.py | 25 ++++++ example/example2.cpp | 4 +- example/example3.cpp | 2 +- example/example4.cpp | 2 +- example/example5.cpp | 4 +- example/example6.cpp | 4 +- example/example7.cpp | 2 +- example/example8.cpp | 4 +- example/example8.py | 0 example/example9.cpp | 3 +- example/example9.py | 0 example/run_test.py | 0 include/pybind/cast.h | 20 +++-- include/pybind/numpy.h | 68 ++++++++------- include/pybind/pybind.h | 178 ++++++++++++++++++++++++++++++---------- 22 files changed, 253 insertions(+), 115 deletions(-) mode change 100644 => 100755 example/example10.py create mode 100644 example/example11.cpp create mode 100755 example/example11.py mode change 100644 => 100755 example/example8.py mode change 100644 => 100755 example/example9.py mode change 100644 => 100755 example/run_test.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 581a3a16e..5d255ea88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ string(TOUPPER "${CMAKE_BUILD_TYPE}" U_CMAKE_BUILD_TYPE) if (UNIX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-unsequenced") if (NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -flto") endif() endif() @@ -60,6 +60,7 @@ add_library(example SHARED example/example8.cpp example/example9.cpp example/example10.cpp + example/example11.cpp ) set_target_properties(example PROPERTIES PREFIX "") diff --git a/README.md b/README.md index bb2b12927..e3b86273f 100644 --- a/README.md +++ b/README.md @@ -56,19 +56,6 @@ In addition to the core functionality, pybind11 provides some extra goodies: - 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.: - -- Fine grained exception translation: currently, all exceptions derived from - `std::exception` are mapped to a Python `Exception`, but that's it. - -- Default arguments in C++ functions are ignored, though their effect can be - emulated by binding multiple overloads using anonymous functions. - -- Python keyword arguments are not supported in bindings - -- Weak pointers are not supported - ## What does the binding code look like? Here is a simple example. The directory `example` contains many more. ```C++ @@ -180,7 +167,7 @@ objects (e.g. a NumPy matrix). py::class_(m, "MatrixXd") .def("__init__", [](Eigen::MatrixXd &m, py::buffer b) { /* Request a buffer descriptor from Python */ - py::buffer_info info = b.request(); + py::buffer_info info = b.request(); /* Some sanity checks ... */ if (info.format != py::format_descriptor::value()) @@ -231,7 +218,7 @@ 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``. +``numpy.dtype.float64``. ```Python >>> x = np.array([[1, 3],[5, 7]]) >>> y = np.array([[2, 4],[6, 8]]) diff --git a/example/example.cpp b/example/example.cpp index d60b24e92..2cc7e5056 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -19,6 +19,7 @@ void init_ex7(py::module &); void init_ex8(py::module &); void init_ex9(py::module &); void init_ex10(py::module &); +void init_ex11(py::module &); PYTHON_PLUGIN(example) { py::module m("example", "pybind example plugin"); @@ -33,6 +34,7 @@ PYTHON_PLUGIN(example) { init_ex8(m); init_ex9(m); init_ex10(m); + init_ex11(m); return m.ptr(); } diff --git a/example/example1.cpp b/example/example1.cpp index 0cdc1f026..2173539a2 100644 --- a/example/example1.cpp +++ b/example/example1.cpp @@ -1,6 +1,6 @@ /* - example/example1.cpp -- Example 1: constructors, deconstructors, - attribute access, __str__, argument and return value conventions + example/example1.cpp -- constructors, deconstructors, attribute access, + __str__, argument and return value conventions Copyright (c) 2015 Wenzel Jakob diff --git a/example/example10.cpp b/example/example10.cpp index 360066ae1..4a0339776 100644 --- a/example/example10.cpp +++ b/example/example10.cpp @@ -1,5 +1,6 @@ /* - example/example10.cpp -- Example 10: auto-vectorize functions for NumPy + example/example10.cpp -- auto-vectorize functions over NumPy array + arguments Copyright (c) 2015 Wenzel Jakob @@ -20,14 +21,16 @@ std::complex my_func3(std::complex c) { } void init_ex10(py::module &m) { - // Vectorize all arguments (though non-vector arguments are also allowed) + // Vectorize all arguments of a function (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); } ); - // Vectorize all arguments (complex numbers) + + // Vectorize a complex-valued function m.def("vectorized_func3", py::vectorize(my_func3)); } diff --git a/example/example10.py b/example/example10.py old mode 100644 new mode 100755 diff --git a/example/example11.cpp b/example/example11.cpp new file mode 100644 index 000000000..522485cb8 --- /dev/null +++ b/example/example11.cpp @@ -0,0 +1,17 @@ +/* + example/example11.cpp -- keyword arguments and default values + + 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" + +void kw_func(int x, int y) { std::cout << "kw_func(x=" << x << ", y=" << y << ")" << std::endl; } + +void init_ex11(py::module &m) { + m.def("kw_func", &kw_func, py::arg("x"), py::arg("y")); + m.def("kw_func2", &kw_func, py::arg("x") = 100, py::arg("y") = 200); +} diff --git a/example/example11.py b/example/example11.py new file mode 100755 index 000000000..733f6debf --- /dev/null +++ b/example/example11.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import sys, pydoc +sys.path.append('.') + +import example + +from example import kw_func +from example import kw_func2 + +print(pydoc.render_doc(kw_func, "Help on %s")) +print(pydoc.render_doc(kw_func2, "Help on %s")) + +kw_func(5, 10) +kw_func(5, y = 10) +kw_func(y = 10, x = 5) + +kw_func2() + +kw_func2(5) +kw_func2(x=5) + +kw_func2(y=10) + +kw_func2(5, 10) +kw_func2(x=5, y=10) diff --git a/example/example2.cpp b/example/example2.cpp index d064fb359..08fdd06a7 100644 --- a/example/example2.cpp +++ b/example/example2.cpp @@ -1,6 +1,6 @@ /* - example/example2.cpp2 -- Example 2: singleton design pattern, static - functions and variables, passing and interacting with Python types + example/example2.cpp2 -- singleton design pattern, static functions and + variables, passing and interacting with Python types Copyright (c) 2015 Wenzel Jakob diff --git a/example/example3.cpp b/example/example3.cpp index fec5d30a6..089f219cc 100644 --- a/example/example3.cpp +++ b/example/example3.cpp @@ -1,5 +1,5 @@ /* - example/example3.cpp -- Example 3: operator overloading + example/example3.cpp -- operator overloading Copyright (c) 2015 Wenzel Jakob diff --git a/example/example4.cpp b/example/example4.cpp index ed9d0da5a..3293e9391 100644 --- a/example/example4.cpp +++ b/example/example4.cpp @@ -1,5 +1,5 @@ /* - example/example4.cpp -- Example 4: global constants and functions, enumerations + example/example4.cpp -- global constants and functions, enumerations Copyright (c) 2015 Wenzel Jakob diff --git a/example/example5.cpp b/example/example5.cpp index 7d617793d..738f9f4b2 100644 --- a/example/example5.cpp +++ b/example/example5.cpp @@ -1,6 +1,6 @@ /* - example/example5.cpp -- Example 5: inheritance, callbacks, acquiring - and releasing the global interpreter lock + example/example5.cpp -- inheritance, callbacks, acquiring and releasing the + global interpreter lock Copyright (c) 2015 Wenzel Jakob diff --git a/example/example6.cpp b/example/example6.cpp index 1e0359ace..952caf0f5 100644 --- a/example/example6.cpp +++ b/example/example6.cpp @@ -1,6 +1,6 @@ /* - example/example6.cpp -- Example 6: supporting Pythons' sequence - protocol, iterators, etc. + example/example6.cpp -- supporting Pythons' sequence protocol, iterators, + etc. Copyright (c) 2015 Wenzel Jakob diff --git a/example/example7.cpp b/example/example7.cpp index 82ab3b656..b68b55396 100644 --- a/example/example7.cpp +++ b/example/example7.cpp @@ -1,5 +1,5 @@ /* - example/example7.cpp -- Example 7: supporting Pythons' buffer protocol + example/example7.cpp -- supporting Pythons' buffer protocol Copyright (c) 2015 Wenzel Jakob diff --git a/example/example8.cpp b/example/example8.cpp index 3aac43a34..475f72a6c 100644 --- a/example/example8.cpp +++ b/example/example8.cpp @@ -1,6 +1,6 @@ /* - example/example8.cpp -- Example 8: binding classes with - custom reference counting, implicit conversions between types + example/example8.cpp -- binding classes with custom reference counting, + implicit conversions between types Copyright (c) 2015 Wenzel Jakob diff --git a/example/example8.py b/example/example8.py old mode 100644 new mode 100755 diff --git a/example/example9.cpp b/example/example9.cpp index 0e070b1c2..81b6edc09 100644 --- a/example/example9.cpp +++ b/example/example9.cpp @@ -1,6 +1,5 @@ /* - example/example9.cpp -- Example 9: nested modules - and internal references + example/example9.cpp -- nested modules and internal references Copyright (c) 2015 Wenzel Jakob diff --git a/example/example9.py b/example/example9.py old mode 100644 new mode 100755 diff --git a/example/run_test.py b/example/run_test.py old mode 100644 new mode 100755 diff --git a/include/pybind/cast.h b/include/pybind/cast.h index 9da3fcaac..301304764 100644 --- a/include/pybind/cast.h +++ b/include/pybind/cast.h @@ -392,14 +392,22 @@ public: return cast(src, policy, parent, typename make_index_sequence::type()); } - static std::string name() { + static std::string name(const char **keywords = nullptr, const char **values = nullptr) { std::array names {{ type_caster::type>::name()... }}; std::string result("("); int counter = 0; for (auto const &name : names) { + if (keywords && keywords[counter]) { + result += keywords[counter]; + result += " : "; + } result += name; + if (values && values[counter]) { + result += " = "; + result += values[counter]; + } if (++counter < size) result += ", "; } @@ -407,12 +415,12 @@ public: return result; } - 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, ReturnValue>::type call(Func &&f) { + return call(std::forward(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()); + template typename std::enable_if::value, detail::void_type>::type call(Func &&f) { + call(std::forward(f), typename make_index_sequence::type()); return detail::void_type(); } @@ -421,7 +429,7 @@ public: } protected: - template ReturnValue call(Func &f, index_sequence) { + template ReturnValue call(Func &&f, index_sequence) { return f((Tuple) std::get(value)...); } diff --git a/include/pybind/numpy.h b/include/pybind/numpy.h index 033679470..3c06854f0 100644 --- a/include/pybind/numpy.h +++ b/include/pybind/numpy.h @@ -10,6 +10,8 @@ #pragma once #include +#include + #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable: 4127) // warning C4127: Conditional expression is constant @@ -132,6 +134,15 @@ public: } }; +#define DECL_FMT(t, n) template<> struct npy_format_descriptor { enum { value = array::API::n }; } +DECL_FMT(int8_t, NPY_BYTE); DECL_FMT(uint8_t, NPY_UBYTE); DECL_FMT(int16_t, NPY_SHORT); +DECL_FMT(uint16_t, NPY_USHORT); DECL_FMT(int32_t, NPY_INT); DECL_FMT(uint32_t, NPY_UINT); +DECL_FMT(int64_t, NPY_LONGLONG); DECL_FMT(uint64_t, NPY_ULONGLONG); DECL_FMT(float, NPY_FLOAT); +DECL_FMT(double, NPY_DOUBLE); DECL_FMT(bool, NPY_BOOL); DECL_FMT(std::complex, NPY_CFLOAT); +DECL_FMT(std::complex, NPY_CDOUBLE); +#undef DECL_FMT + + NAMESPACE_BEGIN(detail) PYBIND_TYPE_CASTER_PYTYPE(array) PYBIND_TYPE_CASTER_PYTYPE(array_dtype) PYBIND_TYPE_CASTER_PYTYPE(array_dtype) @@ -142,24 +153,20 @@ PYBIND_TYPE_CASTER_PYTYPE(array_dtype) PYBIND_TYPE_CASTER_PYTYPE(array_ PYBIND_TYPE_CASTER_PYTYPE(array_dtype>) PYBIND_TYPE_CASTER_PYTYPE(array_dtype>) PYBIND_TYPE_CASTER_PYTYPE(array_dtype) -NAMESPACE_END(detail) -#define DECL_FMT(t, n) template<> struct npy_format_descriptor { enum { value = array::API::n }; } -DECL_FMT(int8_t, NPY_BYTE); DECL_FMT(uint8_t, NPY_UBYTE); DECL_FMT(int16_t, NPY_SHORT); -DECL_FMT(uint16_t, NPY_USHORT); DECL_FMT(int32_t, NPY_INT); DECL_FMT(uint32_t, NPY_UINT); -DECL_FMT(int64_t, NPY_LONGLONG); DECL_FMT(uint64_t, NPY_ULONGLONG); DECL_FMT(float, NPY_FLOAT); -DECL_FMT(double, NPY_DOUBLE); DECL_FMT(bool, NPY_BOOL); DECL_FMT(std::complex, NPY_CFLOAT); -DECL_FMT(std::complex, NPY_CDOUBLE); -#undef DECL_FMT +template +struct vectorize_helper { + typename std::remove_reference::type f; -template - std::function...)> - vectorize(func_type &&f, return_type (*) (args_type ...), - detail::index_sequence) { + vectorize_helper(const Func &f) : f(f) { } - return [f](array_dtype... args) -> array { + object operator()(array_dtype... args) { + return run(args..., typename make_index_sequence::type()); + } + + template object run(array_dtype&... args, index_sequence) { /* Request buffers from all parameters */ - const size_t N = sizeof...(args_type); + const size_t N = sizeof...(Args); std::array buffers {{ args.request()... }}; /* Determine dimensions parameters of output array */ @@ -174,7 +181,7 @@ template strides(ndim); if (ndim > 0) { - strides[ndim-1] = sizeof(return_type); + strides[ndim-1] = sizeof(Return); for (int i=ndim-1; i>0; --i) strides[i-1] = strides[i] * shape[i]; } @@ -186,31 +193,32 @@ template result(count); + std::vector result(count); for (size_t i=0; i::value(), + return array(buffer_info(result.data(), sizeof(Return), + format_descriptor::value(), ndim, shape, strides)); - }; + } +}; + +NAMESPACE_END(detail) + +template +detail::vectorize_helper vectorize(const Func &f, Return (*) (Args ...)) { + return detail::vectorize_helper(f); } -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 +detail::vectorize_helper vectorize(Return (*f) (Args ...)) { + return vectorize(f, f); } template auto vectorize(func &&f) -> decltype( diff --git a/include/pybind/pybind.h b/include/pybind/pybind.h index 4cfaab382..64ae5a0dd 100644 --- a/include/pybind/pybind.h +++ b/include/pybind/pybind.h @@ -76,26 +76,82 @@ private: template using arg_value_caster = detail::type_caster>; - - template void process_args(const std::tuple &args, function_entry *entry) { - process_args(args, entry, typename detail::make_index_sequence::type()); + template static void process_extras(const std::tuple &args, + function_entry *entry, const char **kw, const char **def) { + process_extras(args, entry, kw, def, typename detail::make_index_sequence::type()); } - template void process_args(const - std::tuple &args, function_entry *entry, - detail::index_sequence) { - int unused[] = { 0, (process_arg(std::get(args), entry), 0)... }; + template static void process_extras(const std::tuple &args, + function_entry *entry, const char **kw, const char **def, detail::index_sequence) { + int unused[] = { 0, (process_extra(std::get(args), entry, kw, def), 0)... }; (void) unused; } - void process_arg(const char *doc, function_entry *entry) { entry->doc = doc; } - void process_arg(const pybind::doc &d, function_entry *entry) { entry->doc = d.value; } - void process_arg(const pybind::name &n, function_entry *entry) { entry->name = n.value; } - void process_arg(const pybind::arg &, function_entry *entry) { entry->keywords++; } - void process_arg(const pybind::is_method &, function_entry *entry) { entry->is_method = true; } - void process_arg(const pybind::return_value_policy p, function_entry *entry) { entry->policy = p; } - void process_arg(pybind::sibling s, function_entry *entry) { entry->sibling = s.value; } + template static void process_extras(const std::tuple &args, + PyObject *pyArgs, PyObject *kwargs, bool is_method) { + process_extras(args, pyArgs, kwargs, is_method, typename detail::make_index_sequence::type()); + } + template static void process_extras(const std::tuple &args, + PyObject *pyArgs, PyObject *kwargs, bool is_method, detail::index_sequence) { + int index = is_method ? 1 : 0; + int unused[] = { 0, (process_extra(std::get(args), index, pyArgs, kwargs), 0)... }; + (void) unused; + } + + static void process_extra(const char *doc, function_entry *entry, const char **, const char **) { entry->doc = doc; } + static void process_extra(const pybind::doc &d, function_entry *entry, const char **, const char **) { entry->doc = d.value; } + static void process_extra(const pybind::name &n, function_entry *entry, const char **, const char **) { entry->name = n.value; } + static void process_extra(const pybind::arg &a, function_entry *entry, const char **kw, const char **) { + if (entry->is_method && entry->keywords == 0) + kw[entry->keywords++] = "self"; + kw[entry->keywords++] = a.name; + } + template + static void process_extra(const pybind::arg_t &a, function_entry *entry, const char **kw, const char **def) { + if (entry->is_method && entry->keywords == 0) + kw[entry->keywords++] = "self"; + kw[entry->keywords] = a.name; + def[entry->keywords++] = strdup(std::to_string(a.value).c_str()); + } + + static void process_extra(const pybind::is_method &, function_entry *entry, const char **, const char **) { entry->is_method = true; } + static void process_extra(const pybind::return_value_policy p, function_entry *entry, const char **, const char **) { entry->policy = p; } + static void process_extra(pybind::sibling s, function_entry *entry, const char **, const char **) { entry->sibling = s.value; } + + template static void process_extra(T, int &, PyObject *, PyObject *) { } + static void process_extra(const pybind::arg &a, int &index, PyObject *args, PyObject *kwargs) { + if (kwargs) { + if (PyTuple_GET_ITEM(args, index) != nullptr) { + index++; + return; + } + PyObject *value = PyDict_GetItemString(kwargs, a.name); + if (value) { + Py_INCREF(value); + PyTuple_SetItem(args, index, value); + } + } + index++; + } + template + static void process_extra(const pybind::arg_t &a, int &index, PyObject *args, PyObject *kwargs) { + if (PyTuple_GET_ITEM(args, index) != nullptr) { + index++; + return; + } + PyObject *value = nullptr; + if (kwargs) + value = PyDict_GetItemString(kwargs, a.name); + if (value) { + Py_INCREF(value); + } else { + value = detail::type_caster::type>::cast( + a.value, return_value_policy::automatic, nullptr); + } + PyTuple_SetItem(args, index, value); + index++; + } public: cpp_function() { } @@ -104,7 +160,7 @@ public: cpp_function(Return (*f)(Arg...), Extra&&... extra) { struct capture { Return (*f)(Arg...); - std::tuple extra; + std::tuple extras; }; function_entry *entry = new function_entry(); @@ -114,18 +170,23 @@ public: typedef return_value_caster cast_out; entry->impl = [](function_entry *entry, PyObject *pyArgs, PyObject *kwargs, PyObject *parent) -> PyObject * { + capture *data = (capture *) entry->data; + process_extras(data->extras, pyArgs, kwargs, entry->is_method); cast_in args; - if (!args.load(pyArgs, true)) return nullptr; - auto f = ((capture *) entry->data)->f; - (void)kwargs; - return cast_out::cast(args.template call(f), entry->policy, parent); + if (!args.load(pyArgs, true)) + return nullptr; + return cast_out::cast(args.template call(data->f), entry->policy, parent); }; - entry->signature = cast_in::name(); + const int N = sizeof...(Extra) > sizeof...(Arg) ? sizeof...(Extra) : sizeof...(Arg); + std::array kw{}, def{}; + process_extras(((capture *) entry->data)->extras, entry, kw.data(), def.data()); + + entry->signature = cast_in::name(kw.data(), def.data()); entry->signature += " -> "; entry->signature += cast_out::name(); - process_args(((capture *) entry->data)->extra, entry); - initialize(entry); + + initialize(entry, sizeof...(Arg)); } /// Delegating helper constructor to deal with lambda functions @@ -136,7 +197,6 @@ public: std::forward(extra)...); } - /// Class methods (non-const) template cpp_function( Return (Class::*f)(Arg...), Extra&&... extra) { @@ -157,7 +217,7 @@ private: void initialize(Func &&f, Return (*)(Arg...), Extra&&... extra) { struct capture { typename std::remove_reference::type f; - std::tuple extra; + std::tuple extras; }; function_entry *entry = new function_entry(); @@ -167,27 +227,50 @@ private: typedef return_value_caster cast_out; entry->impl = [](function_entry *entry, PyObject *pyArgs, PyObject *kwargs, PyObject *parent) -> PyObject *{ + capture *data = (capture *)entry->data; + process_extras(data->extras, pyArgs, kwargs, entry->is_method); cast_in args; - if (!args.load(pyArgs, true)) return nullptr; - Func &f = ((capture *) entry->data)->f; - (void)kwargs; - return cast_out::cast(args.template call(f), entry->policy, parent); + if (!args.load(pyArgs, true)) + return nullptr; + return cast_out::cast(args.template call(data->f), entry->policy, parent); }; - entry->signature = cast_in::name(); + const int N = sizeof...(Extra) > sizeof...(Arg) ? sizeof...(Extra) : sizeof...(Arg); + std::array kw{}, def{}; + process_extras(((capture *) entry->data)->extras, entry, kw.data(), def.data()); + + entry->signature = cast_in::name(kw.data(), def.data()); entry->signature += " -> "; entry->signature += cast_out::name(); - process_args(((capture *) entry->data)->extra, entry); - initialize(entry); + + initialize(entry, sizeof...(Arg)); } static PyObject *dispatcher(PyObject *self, PyObject *args, PyObject *kwargs ) { function_entry *overloads = (function_entry *) PyCapsule_GetPointer(self, nullptr); + int nargs = (int) PyTuple_Size(args); PyObject *result = nullptr; - PyObject *parent = PyTuple_Size(args) > 0 ? PyTuple_GetItem(args, 0) : nullptr; + PyObject *parent = nargs > 0 ? PyTuple_GetItem(args, 0) : nullptr; try { for (function_entry *it = overloads; it != nullptr; it = it->next) { - if ((result = it->impl(it, args, kwargs, parent)) != nullptr) + PyObject *args_ = args; + + if (it->keywords != 0 && it->keywords != nargs) { + args_ = PyTuple_New(it->keywords); + for (int i=0; iimpl(it, args_, kwargs, parent); + + if (args_ != args) { + Py_DECREF(args_); + } + + if (result != nullptr) break; } } catch (const error_already_set &) { return nullptr; @@ -221,9 +304,14 @@ private: } } - void initialize(function_entry *entry) { + void initialize(function_entry *entry, int args) { if (entry->name == nullptr) entry->name = ""; + if (entry->keywords != 0 && entry->keywords != args) + throw std::runtime_error( + "cpp_function(): function \"" + std::string(entry->name) + "\" takes " + + std::to_string(args) + " arguments, but " + std::to_string(entry->keywords) + + " pybind::arg entries were specified!"); entry->is_constructor = !strcmp(entry->name, "__init__"); @@ -578,32 +666,32 @@ public: return *this; } - class_ &def_property_readonly(const char *name, const cpp_function &fget) { - def_property(name, fget, cpp_function()); + class_ &def_property_readonly(const char *name, const cpp_function &fget, const char *doc = nullptr) { + def_property(name, fget, cpp_function(), doc); return *this; } - class_ &def_property_readonly_static(const char *name, const cpp_function &fget) { - def_property_static(name, fget, cpp_function()); + class_ &def_property_readonly_static(const char *name, const cpp_function &fget, const char *doc = nullptr) { + def_property_static(name, fget, cpp_function(), doc); return *this; } - class_ &def_property(const char *name, const cpp_function &fget, const cpp_function &fset) { + class_ &def_property(const char *name, const cpp_function &fget, const cpp_function &fset, const char *doc = nullptr) { + object doc_obj = doc ? pybind::str(doc) : (object) const_cast(fget).attr("__doc__"); object property( PyObject_CallFunction((PyObject *)&PyProperty_Type, const_cast("OOOO"), fget.ptr() ? fget.ptr() : Py_None, - fset.ptr() ? fset.ptr() : Py_None, Py_None, - ((object) const_cast(fget).attr("__doc__")).ptr()), false); + fset.ptr() ? fset.ptr() : Py_None, Py_None, doc_obj.ptr()), false); attr(name) = property; return *this; } - class_ &def_property_static(const char *name, const cpp_function &fget, const cpp_function &fset) { + class_ &def_property_static(const char *name, const cpp_function &fget, const cpp_function &fset, const char *doc = nullptr) { + object doc_obj = doc ? pybind::str(doc) : (object) const_cast(fget).attr("__doc__"); object property( PyObject_CallFunction((PyObject *)&PyProperty_Type, - const_cast("OOOO"), fget.ptr() ? fget.ptr() : Py_None, - fset.ptr() ? fset.ptr() : Py_None, Py_None, - ((object) const_cast(fget).attr("__doc__")).ptr()), false); + const_cast("OOOs"), fget.ptr() ? fget.ptr() : Py_None, + fset.ptr() ? fset.ptr() : Py_None, Py_None, doc_obj.ptr()), false); metaclass().attr(name) = property; return *this; }