Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keyword arguments and generalized unpacking for C++ API #372

Merged
merged 10 commits into from
Sep 6, 2016
70 changes: 61 additions & 9 deletions docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1622,24 +1622,76 @@ It is also possible to call python functions via ``operator()``.
py::object result_py = f(1234, "hello", some_instance);
Copy link
Member

@wjakob wjakob Sep 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

py::print not mentioned in docs (except changelog)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about creating a new page dedicated to the Python C++ API? The wrapper classes (str, tuple, dict) are also a bit underdocumented, but the the Advanced page is already getting quite long and hard to navigate. (It's probably worth splitting of additional sections which form a logical unit (like buffer/numpy/eigen) but that's a topic for a different PR.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds good to me.

Sent from my iPhone

On 05 Sep 2016, at 18:06, Dean Moldovan notifications@github.com wrote:

In docs/advanced.rst:

@@ -1589,24 +1589,56 @@ It is also possible to call python functions via operator().
py::object result_py = f(1234, "hello", some_instance);
What do you think about creating a new page dedicated to the Python C++ API? The wrapper classes (str, tuple, dict) are also a bit underdocumented, but the the Advanced page is already getting quite long and hard to navigate. (It's probably worth splitting of additional sections which form a logical unit (like buffer/numpy/eigen) but that's a topic for a different PR.)


You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.

{"api_version":"1.0","publisher":{"api_key":"05dde50f1d1a384dd78767c55493e4bb","name":"GitHub"},"entity":{"external_key":"github/pybind/pybind11","title":"pybind/pybind11","subtitle":"GitHub repository","main_image_url":"https://cloud.githubusercontent.com/assets/143418/17495839/a5054eac-5d88-11e6-95fc-7290892c7bb5.png","avatar_image_url":"https://cloud.githubusercontent.com/assets/143418/15842166/7c72db34-2c0b-11e6-9aed-b52498112777.png","action":{"name":"Open in GitHub","url":"https://github.com/pybind/pybind11"}},"updates":{"snippets":[{"icon":"PERSON","message":"@dean0x7d in #372: What do you think about creating a new page dedicated to the Python C++ API? The wrapper classes (str, tuple, dict) are also a bit underdocumented, but the the Advanced page is already getting quite long and hard to navigate. (It's probably worth splitting of additional sections which form a logical unit (like buffer/numpy/eigen) but that's a topic for a different PR.)"}],"action":{"name":"View Pull Request","url":"https://github.com/pybind/pybind11/pull/372/files/1f13b0c69e27b520b319fdcc170e0e52fb957644#r77490851"}}}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, agreed, most of this library now falls under "Advanced", and things like objects in pytypes.h are scarcely mentioned.

Splitting into sections could be good -- eg wrapper types (dict, tuple etc), handle/object, utility (print), eigen, NumPy (I'd be willing to rewrite the docs on this one).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pybind11 has three different ways of passing values between C++ and Python. Custom C++ classes mapped to Python (class_<>, convenience wrappers around it like stl_binder.h), Python type wrappers (py::object, py::array, etc.), and conversion type casters which convert C++ types into equivalent Python types and vice versa while creating copies in the process (for int, std::string, all stuff in stl.h, eigen.h). I was thinking that it might make sense to split the advanced documentation into three different parts corresponding to each. Thoughts?

MyClass &result = result_py.cast<MyClass>();

The special ``f(*args)`` and ``f(*args, **kwargs)`` syntax is also supported to
supply arbitrary argument and keyword lists, although these cannot be mixed
with other parameters.
Keyword arguments are also supported. In Python, there is the usual call syntax:

.. code-block:: python

def f(number, say, to):
... # function code

f(1234, say="hello", to=some_instance) # keyword call in Python

In C++, the same call can be made using:

.. code-block:: cpp

py::function f = <...>;
using pybind11::literals; // to bring in the `_a` literal
f(1234, "say"_a="hello", "to"_a=some_instance); // keyword call in C++

Unpacking of ``*args`` and ``**kwargs`` is also possible and can be mixed with
other arguments:

.. code-block:: cpp

// * unpacking
py::tuple args = py::make_tuple(1234, "hello", some_instance);
f(*args);

// ** unpacking
py::dict kwargs = py::dict("number"_a=1234, "say"_a="hello", "to"_a=some_instance);
f(**kwargs);

// mixed keywords, * and ** unpacking
py::tuple args = py::make_tuple(1234);
py::dict kwargs;
kwargs["y"] = py::cast(5678);
py::object result = f(*args, **kwargs);
py::dict kwargs = py::dict("to"_a=some_instance);
f(*args, "say"_a="hello", **kwargs);

Generalized unpacking according to PEP448_ is also supported:

.. code-block:: cpp

py::dict kwargs1 = py::dict("number"_a=1234);
py::dict kwargs2 = py::dict("to"_a=some_instance);
f(**kwargs1, "say"_a="hello", **kwargs2);

.. seealso::

The file :file:`tests/test_python_types.cpp` contains a complete
example that demonstrates passing native Python types in more detail. The
file :file:`tests/test_kwargs_and_defaults.cpp` discusses usage
of ``args`` and ``kwargs``.
file :file:`tests/test_callbacks.cpp` presents a few examples of calling
Python functions from C++, including keywords arguments and unpacking.

.. _PEP448: https://www.python.org/dev/peps/pep-0448/

Using Python's print function in C++
====================================

The usual way to write output in C++ is using ``std::cout`` while in Python one
would use ``print``. Since these methods use different buffers, mixing them can
lead to output order issues. To resolve this, pybind11 modules can use the
:func:`py::print` function which writes to Python's ``sys.stdout`` for consistency.

Python's ``print`` function is replicated in the C++ API including optional
keyword arguments ``sep``, ``end``, ``file``, ``flush``. Everything works as
expected in Python:

.. code-block:: cpp

py::print(1, 2.0, "three"); // 1 2.0 three
py::print(1, 2.0, "three", "sep"_a="-"); // 1-2.0-three

auto args = py::make_tuple("unpacked", true);
py::print("->", *args, "end"_a="<-"); // -> unpacked True <-

Default arguments revisited
===========================
Expand Down
7 changes: 7 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ Breaking changes queued for v2.0.0 (Not yet released)
* Added constructors for ``str`` and ``bytes`` from zero-terminated char pointers,
and from char pointers and length.
* Added ``memoryview`` wrapper type which is constructible from ``buffer_info``.
* New syntax to call a Python function from C++ using keyword arguments and unpacking,
e.g. ``foo(1, 2, "z"_a=3)`` or ``bar(1, *args, "z"_a=3, **kwargs)``.
* Added ``py::print()`` function which replicates Python's API and writes to Python's
``sys.stdout`` by default (as opposed to C's ``stdout`` like ``std::cout``).
* Added ``py::dict`` keyword constructor:``auto d = dict("number"_a=42, "name"_a="World");``
* Added ``py::str::format()`` method and ``_s`` literal:
``py::str s = "1 + 2 = {}"_s.format(3);``
* Various minor improvements of library internals (no user-visible changes)

1.8.1 (July 12, 2016)
Expand Down
54 changes: 5 additions & 49 deletions include/pybind11/attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,6 @@

NAMESPACE_BEGIN(pybind11)

template <typename T> struct arg_t;

/// Annotation for keyword arguments
struct arg {
constexpr explicit arg(const char *name) : name(name) { }

template <typename T>
constexpr arg_t<T> operator=(const T &value) const { return {name, value}; }
template <typename T, size_t N>
constexpr arg_t<const T *> operator=(T const (&value)[N]) const {
return operator=((const T *) value);
}

const char *name;
};

/// Annotation for keyword arguments with default values
template <typename T> struct arg_t : public arg {
constexpr arg_t(const char *name, const T &value, const char *descr = nullptr)
: arg(name), value(value), descr(descr) { }
T value;
const char *descr;
};

inline namespace literals {
/// String literal version of arg
constexpr arg operator"" _a(const char *name, size_t) { return arg(name); }
}

/// Annotation for methods
struct is_method { handle class_; is_method(const handle &c) : class_(c) { } };

Expand Down Expand Up @@ -238,21 +209,14 @@ template <> struct process_attribute<arg> : process_attribute_default<arg> {
};

/// Process a keyword argument attribute (*with* a default value)
template <typename T>
struct process_attribute<arg_t<T>> : process_attribute_default<arg_t<T>> {
static void init(const arg_t<T> &a, function_record *r) {
template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
static void init(const arg_v &a, function_record *r) {
if (r->class_ && r->args.empty())
r->args.emplace_back("self", nullptr, handle());

/* Convert keyword value into a Python object */
object o = object(detail::type_caster<typename detail::intrinsic_type<T>::type>::cast(
a.value, return_value_policy::automatic, handle()), false);

if (!o) {
if (!a.value) {
#if !defined(NDEBUG)
std::string descr(typeid(T).name());
detail::clean_type_id(descr);
descr = "'" + std::string(a.name) + ": " + descr + "'";
auto descr = "'" + std::string(a.name) + ": " + a.type + "'";
if (r->class_) {
if (r->name)
descr += " in method '" + (std::string) r->class_.str() + "." + (std::string) r->name + "'";
Expand All @@ -269,7 +233,7 @@ struct process_attribute<arg_t<T>> : process_attribute_default<arg_t<T>> {
"Compile in debug mode for more information.");
#endif
}
r->args.emplace_back(a.name, a.descr, o.release());
r->args.emplace_back(a.name, a.descr, a.value.inc_ref());
}
};

Expand Down Expand Up @@ -301,9 +265,6 @@ template <int Nurse, int Patient> struct process_attribute<keep_alive<Nurse, Pat
static void postcall(handle args, handle ret) { keep_alive_impl(Nurse, Patient, args, ret); }
};

/// Ignore that a variable is unused in compiler warnings
inline void ignore_unused(const int *) { }

/// Recursively iterate over variadic template arguments
template <typename... Args> struct process_attributes {
static void init(const Args&... args, function_record *r) {
Expand All @@ -324,11 +285,6 @@ template <typename... Args> struct process_attributes {
}
};

/// Compile-time integer sum
constexpr size_t constexpr_sum() { return 0; }
template <typename T, typename... Ts>
constexpr size_t constexpr_sum(T n, Ts... ns) { return n + constexpr_sum(ns...); }

/// Check the number of named arguments at compile time
template <typename... Extra,
size_t named = constexpr_sum(std::is_base_of<arg, Extra>::value...),
Expand Down
Loading