diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 5f60895f9..374f5420b 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -385,8 +385,18 @@ public: pybind11_fail("Unable to extract string contents! (invalid type)"); return std::string(buffer, (size_t) length); } + + template + str format(Args &&...args) const { + return attr("format").cast()(std::forward(args)...); + } }; +inline namespace literals { +/// String literal version of str +inline str operator"" _s(const char *s, size_t size) { return {s, size}; } +} + inline pybind11::str handle::str() const { PyObject *strValue = PyObject_Str(m_ptr); #if PY_MAJOR_VERSION < 3 diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index d7c06b2ea..1b462aba0 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -210,5 +210,13 @@ test_initializer python_types([](py::module &m) { py::print("this goes to stderr", "file"_a=py_stderr); py::print("flush", "flush"_a=true); + + py::print("{a} + {b} = {c}"_s.format("a"_a="py::print", "b"_a="str.format", "c"_a="this")); + }); + + m.def("test_str_format", []() { + auto s1 = "{} + {} = {}"_s.format(1, 2, 3); + auto s2 = "{a} + {b} = {c}"_s.format("a"_a=1, "b"_a=2, "c"_a=3); + return py::make_tuple(s1, s2); }); }); diff --git a/tests/test_python_types.py b/tests/test_python_types.py index 4a25bfbf5..369606978 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -231,5 +231,14 @@ def test_print(capture): *args-and-a-custom-separator no new line here -- next print flush + py::print + str.format = this """ assert capture.stderr == "this goes to stderr" + + +def test_str_api(): + from pybind11_tests import test_str_format + + s1, s2 = test_str_format() + assert s1 == "1 + 2 = 3" + assert s1 == s2