Commit 48548ea4 by Wenzel Jakob

general cleanup of the codebase

- new pybind11::base<> attribute to indicate a subclass relationship
- unified infrastructure for parsing variadic arguments in class_ and cpp_function
- use 'handle' and 'object' more consistently everywhere
parent 9180519d
......@@ -80,6 +80,7 @@ include_directories(${PYTHON_INCLUDE_DIR})
include_directories(include)
set(PYBIND11_HEADERS
include/pybind11/attr.h
include/pybind11/cast.h
include/pybind11/common.h
include/pybind11/complex.h
......@@ -167,7 +168,7 @@ elseif (UNIX)
# Strip unnecessary sections of the binary on Linux/Mac OS
if(APPLE)
set_target_properties(example PROPERTIES MACOSX_RPATH ".")
set_target_properties(example PROPERTIES LINK_FLAGS "-undefined dynamic_lookup -dead_strip")
set_target_properties(example PROPERTIES LINK_FLAGS "-undefined dynamic_lookup ")
if (NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG)
add_custom_command(TARGET example POST_BUILD COMMAND strip -u -r ${PROJECT_SOURCE_DIR}/example/example.so)
endif()
......
......@@ -925,7 +925,7 @@ If not available, the signature may not be very helpful, e.g.:
FUNCTIONS
...
| myFunction(...)
| Signature : (MyClass, arg : SomeType = <SomeType object at 0x101b7b080>) -> None
| Signature : (MyClass, arg : SomeType = <SomeType object at 0x101b7b080>) -> NoneType
...
The first way of addressing this is by defining ``SomeType.__repr__``.
......
......@@ -5,15 +5,19 @@ Changelog
1.2 (not yet released)
--------------------------
* Optional: efficient generation of function signatures at compile time using C++14
* Switched to a simpler and more general way of dealing with function default arguments
Unused keyword arguments in function calls are now detected and cause errors as expected
* Switched to a simpler and more general way of dealing with function default
arguments. Unused keyword arguments in function calls are now detected and
cause errors as expected
* New ``keep_alive`` call policy analogous to Boost.Python's ``with_custodian_and_ward``
* New ``pybind11::base<>`` attribute to indicate a subclass relationship
* Improved interface for RAII type wrappers in ``pytypes.h``
* Use RAII type wrappers consistently within pybind11 itself. This
fixes various potential refcount leaks when exceptions occur
* Added new ``bytes`` RAII type wrapper (maps to ``string`` in Python 2.7).
* Made handle and related RAII classes const correct
* Made handle and related RAII classes const correct, using them more
consistently everywhere now
* Got rid of the ugly ``__pybind11__`` attributes on the Python side---they are
now stored in a C++ hash table that is not visible in Python
* Fixed refcount leaks involving NumPy arrays and bound functions
......@@ -21,7 +25,9 @@ Changelog
* Removed an unnecessary copy operation in ``pybind11::vectorize``
* Fixed naming clashes when both pybind11 and NumPy headers are included
* Added conversions for additional exception types
* Documentation improvements (using multiple extension modules, smart pointers, other minor clarifications)
* Documentation improvements (using multiple extension modules, smart pointers,
other minor clarifications)
* unified infrastructure for parsing variadic arguments in class_ and cpp_function
* Fixed license text (was: ZLIB, should have been: 3-clause BSD)
* Python 3.2 compatibility
......
......@@ -177,9 +177,22 @@ inheritance relationship:
std::string bark() const { return "woof!"; }
};
To capture the hierarchical relationship in pybind11, we must assign a name to
the ``Pet`` :class:`class_` instance and reference it when binding the ``Dog``
class.
There are two different ways of indicating a hierarchical relationship to
pybind11: the first is by specifying the C++ base class explicitly during
construction using the ``base`` attribute:
.. code-block:: cpp
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &>())
.def_readwrite("name", &Pet::name);
py::class_<Dog>(m, "Dog", py::base<Pet>() /* <- specify C++ parent type */)
.def(py::init<const std::string &>())
.def("bark", &Dog::bark);
Alternatively, we can also assign a name to the previously bound ``Pet``
:class:`class_` object and reference it when binding the ``Dog`` class:
.. code-block:: cpp
......@@ -187,11 +200,12 @@ class.
pet.def(py::init<const std::string &>())
.def_readwrite("name", &Pet::name);
py::class_<Dog>(m, "Dog", pet /* <- specify parent */)
py::class_<Dog>(m, "Dog", pet /* <- specify Python parent type */)
.def(py::init<const std::string &>())
.def("bark", &Dog::bark);
Instances then expose fields and methods of both types:
Functionality-wise, both approaches are completely equivalent. Afterwards,
instances will expose fields and methods of both types:
.. code-block:: python
......@@ -242,14 +256,14 @@ The overload signatures are also visible in the method's docstring:
| Methods defined here:
|
| __init__(...)
| Signature : (Pet, str, int) -> None
| Signature : (Pet, str, int) -> NoneType
|
| set(...)
| 1. Signature : (Pet, int) -> None
| 1. Signature : (Pet, int) -> NoneType
|
| Set the pet's age
|
| 2. Signature : (Pet, str) -> None
| 2. Signature : (Pet, str) -> NoneType
|
| Set the pet's name
......
......@@ -30,10 +30,14 @@ and that the pybind11 repository is located in a subdirectory named :file:`pybin
# Try to autodetect Python (can be overridden manually if needed)
set(Python_ADDITIONAL_VERSIONS 3.4 3.5 3.6 3.7)
if (NOT ${PYBIND11_PYTHON_VERSION} STREQUAL "")
find_package(PythonLibs ${PYBIND11_PYTHON_VERSION} EXACT REQUIRED)
find_package(PythonLibs ${PYBIND11_PYTHON_VERSION} EXACT)
if (NOT PythonLibs_FOUND)
find_package(PythonLibs ${PYBIND11_PYTHON_VERSION} REQUIRED)
endif()
else()
find_package(PythonLibs REQUIRED)
endif()
find_package(PythonInterp ${PYTHONLIBS_VERSION_STRING} EXACT REQUIRED)
# Uncomment the following line if you will also require a matching Python interpreter
# find_package(PythonInterp ${PYTHONLIBS_VERSION_STRING} EXACT REQUIRED)
......@@ -115,7 +119,7 @@ and that the pybind11 repository is located in a subdirectory named :file:`pybin
# Strip unnecessary sections of the binary on Linux/Mac OS
if(APPLE)
set_target_properties(example PROPERTIES MACOSX_RPATH ".")
set_target_properties(example PROPERTIES LINK_FLAGS "-undefined dynamic_lookup -dead_strip")
set_target_properties(example PROPERTIES LINK_FLAGS "-undefined dynamic_lookup ")
if (NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG)
add_custom_command(TARGET example POST_BUILD COMMAND strip -u -r ${PROJECT_BINARY_DIR}/example.so)
endif()
......
......@@ -63,17 +63,19 @@ Without reference counting
Return the ``PyObject *`` underlying a :class:`handle`.
.. function:: void handle::inc_ref() const
.. function:: const handle& handle::inc_ref() const
Manually increase the reference count of the Python object. Usually, it is
preferable to use the :class:`object` class which derives from
:class:`handle` and calls this function automatically.
:class:`handle` and calls this function automatically. Returns a reference
to itself.
.. function:: void handle::dec_ref() const
.. function:: const handle& handle::dec_ref() const
Manually decrease the reference count of the Python object. Usually, it is
preferable to use the :class:`object` class which derives from
:class:`handle` and calls this function automatically.
:class:`handle` and calls this function automatically. Returns a reference
to itself.
.. function:: void handle::ref_count() const
......@@ -167,11 +169,11 @@ With reference counting
Move constructor; steals the object from ``other`` and preserves its
reference count.
.. function:: PyObject* object::release()
.. function:: handle object::release()
Release ownership of underlying ``PyObject *``. Returns raw Python object
pointer without decreasing its reference count and resets handle to
``nullptr``-valued pointer.
Resets the internal pointer to ``nullptr`` without without decreasing the
object's reference count. The function returns a raw handle to the original
Python object.
.. function:: object::~object()
......
......@@ -29,6 +29,11 @@ public:
void bark() const { std::cout << "Woof!" << std::endl; }
};
class Rabbit : public Pet {
public:
Rabbit(const std::string &name) : Pet(name, "parrot") {}
};
void pet_print(const Pet &pet) {
std::cout << pet.name() + " is a " + pet.species() << std::endl;
}
......@@ -62,9 +67,14 @@ void init_ex5(py::module &m) {
.def("name", &Pet::name)
.def("species", &Pet::species);
/* One way of declaring a subclass relationship: reference parent's class_ object */
py::class_<Dog>(m, "Dog", pet_class)
.def(py::init<std::string>());
/* Another way of declaring a subclass relationship: reference parent's C++ type */
py::class_<Rabbit>(m, "Rabbit", py::base<Pet>())
.def(py::init<std::string>());
m.def("pet_print", pet_print);
m.def("dog_bark", dog_bark);
......
......@@ -5,11 +5,15 @@ sys.path.append('.')
from example import Pet
from example import Dog
from example import Rabbit
from example import dog_bark
from example import pet_print
polly = Pet('Polly', 'parrot')
molly = Dog('Molly')
roger = Rabbit('Rabbit')
print(roger.name() + " is a " + roger.species())
pet_print(roger)
print(polly.name() + " is a " + polly.species())
pet_print(polly)
print(molly.name() + " is a " + molly.species())
......
Rabbit is a parrot
Polly is a parrot
Polly is a parrot
Molly is a dog
Molly is a dog
Woof!
The following error is expected: Incompatible function arguments. The following argument types are supported:
1. (Dog) -> NoneType
Callback function 1 called!
False
Callback function 2 called : Hello, x, True, 5
5
func(43) = 44
func(43) = 44
Payload constructor
Payload copy constructor
......@@ -18,3 +9,14 @@ Payload move constructor
Payload destructor
Payload destructor
Payload destructor
Rabbit is a parrot
Polly is a parrot
Molly is a dog
The following error is expected: Incompatible function arguments. The following argument types are supported:
1. (example.Dog) -> NoneType
Callback function 1 called!
False
Callback function 2 called : Hello, x, True, 5
5
func(43) = 44
......@@ -73,6 +73,7 @@
#include <memory>
#if PY_MAJOR_VERSION >= 3 /// Compatibility macros for various Python versions
#define PYBIND11_INSTANCE_METHOD_NEW(ptr, class_) PyInstanceMethod_New(ptr)
#define PYBIND11_BYTES_CHECK PyBytes_Check
#define PYBIND11_BYTES_FROM_STRING PyBytes_FromString
#define PYBIND11_BYTES_FROM_STRING_AND_SIZE PyBytes_FromStringAndSize
......@@ -89,6 +90,7 @@
#define PYBIND11_PLUGIN_IMPL(name) \
extern "C" PYBIND11_EXPORT PyObject *PyInit_##name()
#else
#define PYBIND11_INSTANCE_METHOD_NEW(ptr, class_) PyMethod_New(ptr, nullptr, class_)
#define PYBIND11_BYTES_CHECK PyString_Check
#define PYBIND11_BYTES_FROM_STRING PyString_FromString
#define PYBIND11_BYTES_FROM_STRING_AND_SIZE PyString_FromStringAndSize
......@@ -200,16 +202,6 @@ template <typename type, typename holder_type = std::unique_ptr<type>> struct in
holder_type holder;
};
/// Additional type information which does not fit into the PyTypeObject
struct type_info {
PyTypeObject *type;
size_t type_size;
void (*init_holder)(PyObject *, const void *);
std::vector<PyObject *(*)(PyObject *, PyTypeObject *)> implicit_conversions;
buffer_info *(*get_buffer)(PyObject *, void *) = nullptr;
void *get_buffer_data = nullptr;
};
struct overload_hash {
inline std::size_t operator()(const std::pair<const PyObject *, const char *>& v) const {
size_t value = std::hash<const void *>()(v.first);
......@@ -218,16 +210,6 @@ struct overload_hash {
}
};
/// Stores information about a keyword argument
struct argument_entry {
const char *name; ///< Argument name
const char *descr; ///< Human-readable version of the argument value
PyObject *value; ///< Associated Python object
argument_entry(const char *name, const char *descr, PyObject *value)
: name(name), descr(descr), value(value) { }
};
/// Internal data struture used to track registered instances and types
struct internals {
std::unordered_map<const void *, void*> registered_types_cpp; // std::type_info* -> type_info
......
......@@ -20,8 +20,8 @@ PYBIND11_DECL_FMT(std::complex<double>, "Zd");
NAMESPACE_BEGIN(detail)
template <typename T> class type_caster<std::complex<T>> {
public:
bool load(PyObject *src, bool) {
Py_complex result = PyComplex_AsCComplex(src);
bool load(handle src, bool) {
Py_complex result = PyComplex_AsCComplex(src.ptr());
if (result.real == -1.0 && PyErr_Occurred()) {
PyErr_Clear();
return false;
......@@ -30,7 +30,7 @@ public:
return true;
}
static PyObject *cast(const std::complex<T> &src, return_value_policy /* policy */, PyObject * /* parent */) {
static handle cast(const std::complex<T> &src, return_value_policy /* policy */, handle /* parent */) {
return PyComplex_FromDoubles((double) src.real(), (double) src.imag());
}
......
......@@ -18,14 +18,13 @@ NAMESPACE_BEGIN(detail)
template <typename Return, typename... Args> struct type_caster<std::function<Return(Args...)>> {
typedef std::function<Return(Args...)> type;
public:
bool load(PyObject *src_, bool) {
bool load(handle src_, bool) {
src_ = detail::get_function(src_);
if (!src_ || !(PyFunction_Check(src_) || PyCFunction_Check(src_)))
if (!src_ || !(PyFunction_Check(src_.ptr()) || PyCFunction_Check(src_.ptr())))
return false;
object src(src_, true);
value = [src](Args... args) -> Return {
object retval(pybind11::handle(src).call(std::move(args)...));
object retval(src.call(std::move(args)...));
/* Visual studio 2015 parser issue: need parentheses around this expression */
return (retval.template cast<Return>());
};
......@@ -33,10 +32,8 @@ public:
}
template <typename Func>
static PyObject *cast(Func &&f_, return_value_policy policy, PyObject *) {
cpp_function f(std::forward<Func>(f_), policy);
f.inc_ref();
return f.ptr();
static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) {
return cpp_function(std::forward<Func>(f_), policy).release();
}
PYBIND11_TYPE_CASTER(type, _("function<") +
......
......@@ -79,36 +79,36 @@ public:
API& api = lookup_api();
PyObject *descr = api.PyArray_DescrFromType_(npy_format_descriptor<Type>::value);
if (descr == nullptr)
throw std::runtime_error("NumPy: unsupported buffer format!");
pybind11_fail("NumPy: unsupported buffer format!");
Py_intptr_t shape = (Py_intptr_t) size;
object tmp = object(api.PyArray_NewFromDescr_(
api.PyArray_Type_, descr, 1, &shape, nullptr, (void *) ptr, 0, nullptr), false);
if (ptr && tmp)
tmp = object(api.PyArray_NewCopy_(tmp.ptr(), -1 /* any order */), false);
if (!tmp)
throw std::runtime_error("NumPy: unable to create array!");
m_ptr = tmp.release();
pybind11_fail("NumPy: unable to create array!");
m_ptr = tmp.release().ptr();
}
array(const buffer_info &info) {
API& api = lookup_api();
if ((info.format.size() < 1) || (info.format.size() > 2))
throw std::runtime_error("Unsupported buffer format!");
pybind11_fail("Unsupported buffer format!");
int fmt = (int) info.format[0];
if (info.format == "Zd") fmt = API::NPY_CDOUBLE_;
else if (info.format == "Zf") fmt = API::NPY_CFLOAT_;
PyObject *descr = api.PyArray_DescrFromType_(fmt);
if (descr == nullptr)
throw std::runtime_error("NumPy: unsupported buffer format '" + info.format + "'!");
pybind11_fail("NumPy: unsupported buffer format '" + info.format + "'!");
object 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), false);
if (info.ptr && tmp)
tmp = object(api.PyArray_NewCopy_(tmp.ptr(), -1 /* any order */), false);
if (!tmp)
throw std::runtime_error("NumPy: unable to create array!");
m_ptr = tmp.release();
pybind11_fail("NumPy: unable to create array!");
m_ptr = tmp.release().ptr();
}
protected:
......@@ -186,7 +186,7 @@ struct vectorize_helper {
/* Check if the parameters are actually compatible */
for (size_t i=0; i<N; ++i)
if (buffers[i].size != 1 && (buffers[i].ndim != ndim || buffers[i].shape != shape))
throw std::runtime_error("pybind11::vectorize: incompatible size/dimension of inputs!");
pybind11_fail("pybind11::vectorize: incompatible size/dimension of inputs!");
if (size == 1)
return cast(f(*((Args *) buffers[Index].ptr)...));
......
......@@ -45,17 +45,17 @@ template <op_id, op_type, typename B, typename L, typename R> struct op_impl { }
/// Operator implementation generator
template <op_id id, op_type ot, typename L, typename R> struct op_ {
template <typename Base, typename Holder, typename... Extra> void execute(pybind11::class_<Base, Holder> &class_, Extra&&... extra) const {
template <typename Base, typename Holder, typename... Extra> void execute(pybind11::class_<Base, Holder> &class_, const Extra&... extra) const {
typedef typename std::conditional<std::is_same<L, self_t>::value, Base, L>::type L_type;
typedef typename std::conditional<std::is_same<R, self_t>::value, Base, R>::type R_type;
typedef op_impl<id, ot, Base, L_type, R_type> op;
class_.def(op::name(), &op::execute, std::forward<Extra>(extra)...);
class_.def(op::name(), &op::execute, extra...);
}
template <typename Base, typename Holder, typename... Extra> void execute_cast(pybind11::class_<Base, Holder> &class_, Extra&&... extra) const {
template <typename Base, typename Holder, typename... Extra> void execute_cast(pybind11::class_<Base, Holder> &class_, const Extra&... extra) const {
typedef typename std::conditional<std::is_same<L, self_t>::value, Base, L>::type L_type;
typedef typename std::conditional<std::is_same<R, self_t>::value, Base, R>::type R_type;
typedef op_impl<id, ot, Base, L_type, R_type> op;
class_.def(op::name(), &op::execute_cast, std::forward<Extra>(extra)...);
class_.def(op::name(), &op::execute_cast, extra...);
}
};
......
......@@ -26,7 +26,7 @@ template <typename Value, typename Alloc> struct type_caster<std::vector<Value,
typedef std::vector<Value, Alloc> type;
typedef type_caster<Value> value_conv;
public:
bool load(PyObject *src, bool convert) {
bool load(handle src, bool convert) {
list l(src, true);
if (!l.check())
return false;
......@@ -34,21 +34,21 @@ public:
value.clear();
value_conv conv;
for (auto it : l) {
if (!conv.load(it.ptr(), convert))
if (!conv.load(it, convert))
return false;
value.push_back((Value) conv);
}
return true;
}
static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent) {
static handle cast(const type &src, return_value_policy policy, handle parent) {
list l(src.size());
size_t index = 0;
for (auto const &value: src) {
object value_(value_conv::cast(value, policy, parent), false);
object value_ = object(value_conv::cast(value, policy, parent), false);
if (!value_)
return nullptr;
PyList_SET_ITEM(l.ptr(), index++, value_.release()); // steals a reference
return handle();
PyList_SET_ITEM(l.ptr(), index++, value_.release().ptr()); // steals a reference
}
return l.release();
}
......@@ -59,26 +59,26 @@ template <typename Key, typename Compare, typename Alloc> struct type_caster<std
typedef std::set<Key, Compare, Alloc> type;
typedef type_caster<Key> key_conv;
public:
bool load(PyObject *src, bool convert) {
bool load(handle src, bool convert) {
pybind11::set s(src, true);
if (!s.check())
return false;
value.clear();
key_conv conv;
for (auto entry : s) {
if (!conv.load(entry.ptr(), convert))
if (!conv.load(entry, convert))
return false;
value.insert((Key) conv);
}
return true;
}
static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent) {
static handle cast(const type &src, return_value_policy policy, handle parent) {
pybind11::set s;
for (auto const &value: src) {
object value_(key_conv::cast(value, policy, parent), false);
object value_ = object(key_conv::cast(value, policy, parent), false);
if (!value_ || !s.add(value))
return nullptr;
return handle();
}
return s.release();
}
......@@ -91,7 +91,7 @@ public:
typedef type_caster<Key> key_conv;
typedef type_caster<Value> value_conv;
bool load(PyObject *src, bool convert) {
bool load(handle src, bool convert) {
dict d(src, true);
if (!d.check())
return false;
......@@ -107,13 +107,13 @@ public:
return true;
}
static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent) {
static handle cast(const type &src, return_value_policy policy, handle parent) {
dict d;
for (auto const &kv: src) {
object key(key_conv::cast(kv.first, policy, parent), false);
object value(value_conv::cast(kv.second, policy, parent), false);
object key = object(key_conv::cast(kv.first, policy, parent), false);
object value = object(value_conv::cast(kv.second, policy, parent), false);
if (!key || !value)
return nullptr;
return handle();
d[key] = value;
}
return d.release();
......
......@@ -15,6 +15,7 @@ setup(
packages=[],
license='BSD',
headers=[
'include/pybind11/attr.h',
'include/pybind11/cast.h',
'include/pybind11/complex.h',
'include/pybind11/descr.h',
......@@ -58,10 +59,10 @@ C++11-compatible compilers are widely available, this heavy machinery has
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 core
header files only require ~2.5K lines of code and depend on Python (2.7 or 3.x)
and the C++ standard library. This compact implementation was possible thanks
to some of the new C++11 language features (specifically: tuples, lambda
functions and variadic templates). Since its creation, this library has grown
beyond Boost.Python in many ways, leading to dramatically simpler binding code
in many common situations.""")
everything stripped away that isn't relevant for binding generation. Without
comments, the core header files only require ~2.5K lines of code and depend on
Python (2.7 or 3.x) and the C++ standard library. This compact implementation
was possible thanks to some of the new C++11 language features (specifically:
tuples, lambda functions and variadic templates). Since its creation, this
library has grown beyond Boost.Python in many ways, leading to dramatically
simpler binding code in many common situations.""")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment