From 07c1319b99d2248af70e23a34d2a6765a51cb002 Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Mon, 4 Aug 2003 17:46:48 +0000 Subject: [PATCH] Added the new arg class from "nickm-at-sitius.com" (Nikolay Mladenov) which supplies the ability to wrap functions that can be called with ommitted arguments in the middle. [SVN r19441] --- doc/v2/args.html | 134 +++++++++++++++++++---- include/boost/python/args.hpp | 44 +++++++- include/boost/python/args_fwd.hpp | 4 + include/boost/python/object/function.hpp | 1 + src/object/function.cpp | 110 +++++++++++++------ test/Jamfile | 2 + 6 files changed, 241 insertions(+), 54 deletions(-) diff --git a/doc/v2/args.html b/doc/v2/args.html index c2f29269..8519b3bc 100644 --- a/doc/v2/args.html +++ b/doc/v2/args.html @@ -3,7 +3,7 @@ + "HTML Tidy for Cygwin (vers 1st April 2002), see www.w3.org"> @@ -35,7 +35,31 @@
keyword-expressions
-
Functions
+
Classes
+ +
+
+
class arg
+ +
+
+
class arg + synopsis
+ +
class arg + constructor
+ +
class arg template + operator =
+
+
+
+
+ +
Keyword-expression + operator ,
+ +
Functions (deprecated)
@@ -57,27 +81,95 @@

A keyword-expression results in an object which holds a sequence of ntbses, and whose type - encodes the number of keywords specified.

+ encodes the number of keywords specified. The keyword-expression + may contain default values for some or all of the keywords it holds

-

Functions

+

Classes

-

args(...)

+

class arg;

+ +

The objects of class arg are keyword-expressions holding one keyword ( + size one )

+ +

Class arg synopsis

-unspecified1 args(char const*);
-unspecified2 args(char const*, char const*);
-   .
-   .
-   .
-unspecifiedN args(char const*, char const*, ... char const*);
+namespace boost { namespace python
+{
+        struct arg 
+        {
+          template <class T>
+                  arg &perator = (T const &value);
+          explicit arg (char const *name){elements[0].name = name;}
+        };
+
+}}
+
+ +

Class arg constructor

+
+arg(char const* name);
 
-
Requires: Every argument must be a Requires: The argument must be a ntbs.
-
Returns: an object representing a keyword-expression encapsulating the - arguments passed.
+
Effects: Constructs an arg object holding a + keyword with name name.
+
+ +

Class arg operator =

+
+template <class T> arg &operator = (T const &value);
+
+ +
+
Requires: The argument must convertible to python.
+ +
Effects: Assigns default value for the keyword.
+ +
Returns: Reference to this.
+
+ +

Keyword-expression + operator ,

+
+      keyword-expression operator , (keyword-expression, const arg &kw) const
+      keyword-expression operator , (keyword-expression, const char *name) const;
+
+ +
+
Requires: The argument name must be a ntbs.
+ +
Effects: Extends the keyword-expression argument with + one more keyword.
+ +
Returns: The extended keyword-expression.
+
+ +

Functions + (deprecated)

+ +

args(...)

+
+  unspecified1 args(char const*);
+    unspecified2 args(char const*, char const*);
+       .
+       .
+       .
+    unspecifiedN args(char const*, char const*, ... char const*);
+
+
+ +
+
Requires: Every argument must be a ntbs.
+ +
Returns: an object representing a keyword-expression encapsulating + the arguments passed.

Example

@@ -85,19 +177,21 @@ #include <boost/python/def.hpp> using namespace boost::python; -int f(int x, int y, int z); +int f(double x, double y, double z=0.0, double w=1.0); BOOST_PYTHON_MODULE(xxx) { - def("f", f, args("x", "y", "z")); + def("f", f + , ( arg("x"), "y", arg("z")=0.0, arg("w")=1.0 ) + ); } -

Revised 05 November, 2001

+

Revised 01 August, 2003

© Copyright Dave Abrahams 2002. All Rights - Reserved.

+ "../../../../people/dave_abrahams.htm">Dave Abrahams 2002-2003. All + Rights Reserved.

diff --git a/include/boost/python/args.hpp b/include/boost/python/args.hpp index a3cd6934..1d0576f0 100644 --- a/include/boost/python/args.hpp +++ b/include/boost/python/args.hpp @@ -23,11 +23,12 @@ # include # include +# include # include # include - +# include namespace boost { namespace python { @@ -42,6 +43,16 @@ namespace detail { return keyword_range(elements, elements + nkeywords); } + + keywords operator,(const keywords<1> &k) const + { + python::detail::keywords res; + std::copy(elements, elements+size, res.elements); + res.elements[size] = k.elements[0]; + return res; + } + + keywords operator,(const char *name) const; keyword elements[nkeywords]; }; @@ -97,6 +108,37 @@ namespace detail # endif } +struct arg : detail::keywords<1> +{ + explicit arg(char const *name) + { + elements[0].name = name; + } + + template + arg& operator=(T const& value) + { + object z(value); + elements[0].default_value = handle<>(python::borrowed(object(value).ptr())); + return *this; + } + + operator detail::keyword const&() const + { + return elements[0]; + } +}; + +namespace detail +{ + template + inline keywords + keywords::operator,(const char *name) const + { + return this->operator,(arg(name)); + } +} + # define BOOST_PYTHON_ASSIGN_NAME(z, n, _) result.elements[n].name = name##n; # define BOOST_PP_LOCAL_MACRO(n) \ inline detail::keywords args(BOOST_PP_ENUM_PARAMS_Z(1, n, char const* name)) \ diff --git a/include/boost/python/args_fwd.hpp b/include/boost/python/args_fwd.hpp index bfc45881..f45592ad 100644 --- a/include/boost/python/args_fwd.hpp +++ b/include/boost/python/args_fwd.hpp @@ -19,6 +19,10 @@ namespace detail { struct keyword { + keyword(char const* name_=0) + : name(name_) + {} + char const* name; handle<> default_value; }; diff --git a/include/boost/python/object/function.hpp b/include/boost/python/object/function.hpp index ec08edba..9a3e0818 100644 --- a/include/boost/python/object/function.hpp +++ b/include/boost/python/object/function.hpp @@ -51,6 +51,7 @@ struct BOOST_PYTHON_DECL function : PyObject object m_namespace; object m_doc; object m_arg_names; + unsigned m_nkeyword_values; }; // diff --git a/src/object/function.cpp b/src/object/function.cpp index c4ce1565..5594ef7a 100644 --- a/src/object/function.cpp +++ b/src/object/function.cpp @@ -43,10 +43,11 @@ extern PyTypeObject function_type; function::function( py_function const& implementation - , python::detail::keyword const* names_and_defaults + , python::detail::keyword const* const names_and_defaults , unsigned num_keywords ) : m_fn(implementation) + , m_nkeyword_values(0) { if (names_and_defaults != 0) { @@ -66,12 +67,23 @@ function::function( for (unsigned i = 0; i < num_keywords; ++i) { + tuple kv; + + python::detail::keyword const* const p = names_and_defaults + i; + if (p->default_value) + { + kv = make_tuple(p->name, p->default_value); + ++m_nkeyword_values; + } + else + { + kv = make_tuple(p->name); + } + PyTuple_SET_ITEM( m_arg_names.ptr() , i + keyword_offset - , expect_non_null( - PyString_FromString(const_cast(names_and_defaults[i].name)) - ) + , incref(kv.ptr()) ); } } @@ -91,9 +103,9 @@ function::~function() PyObject* function::call(PyObject* args, PyObject* keywords) const { - std::size_t nargs = PyTuple_GET_SIZE(args); - std::size_t nkeywords = keywords ? PyDict_Size(keywords) : 0; - std::size_t total_args = nargs + nkeywords; + std::size_t n_unnamed_actual = PyTuple_GET_SIZE(args); + std::size_t n_keyword_actual = keywords ? PyDict_Size(keywords) : 0; + std::size_t n_actual = n_unnamed_actual + n_keyword_actual; function const* f = this; @@ -101,56 +113,88 @@ PyObject* function::call(PyObject* args, PyObject* keywords) const do { // Check for a plausible number of arguments - if (total_args >= f->m_fn.min_arity() - && total_args <= f->m_fn.max_arity()) + unsigned min_arity = f->m_fn.min_arity(); + unsigned max_arity = f->m_fn.max_arity(); + + if (n_actual + f->m_nkeyword_values >= min_arity + && n_actual <= max_arity) { // This will be the args that actually get passed - handle<> args2(allow_null(borrowed(args))); + handle<>inner_args(allow_null(borrowed(args))); - if (nkeywords > 0) // Keyword arguments were supplied - { - if (f->m_arg_names.ptr() == Py_None) // this overload doesn't accept keywords + if (n_keyword_actual > 0 // Keyword arguments were supplied + || n_actual < min_arity) // or default keyword values are needed + { + if (f->m_arg_names.ptr() == Py_None) { - args2 = handle<>(); // signal failure + // this overload doesn't accept keywords + inner_args = handle<>(); } else { - std::size_t max_args - = static_cast(PyTuple_Size(f->m_arg_names.ptr())); - // "all keywords are none" is a special case // indicating we will accept any number of keyword // arguments - if (max_args == 0) + if (PyTuple_Size(f->m_arg_names.ptr()) == 0) { // no argument preprocessing } - else if (max_args < total_args) + else if (n_actual > max_arity) { - args2 = handle<>(); + // too many arguments + inner_args = handle<>(); } else { - // build a new arg tuple - args2 = handle<>(PyTuple_New(total_args)); + // build a new arg tuple, will adjust its size later + inner_args = handle<>(PyTuple_New(max_arity)); // Fill in the positional arguments - for (std::size_t i = 0; i < nargs; ++i) - PyTuple_SET_ITEM(args2.get(), i, incref(PyTuple_GET_ITEM(args, i))); + for (std::size_t i = 0; i < n_unnamed_actual; ++i) + PyTuple_SET_ITEM(inner_args.get(), i, incref(PyTuple_GET_ITEM(args, i))); // Grab remaining arguments by name from the keyword dictionary - for (std::size_t j = nargs; j < total_args; ++j) + std::size_t n_actual_processed = n_unnamed_actual; + + for (std::size_t arg_pos = n_unnamed_actual; arg_pos < max_arity ; ++arg_pos) { - PyObject* value = PyDict_GetItem( - keywords, PyTuple_GET_ITEM(f->m_arg_names.ptr(), j)); - + // Get the keyword[, value pair] corresponding + PyObject* kv = PyTuple_GET_ITEM(f->m_arg_names.ptr(), arg_pos); + + // If there were any keyword arguments, + // look up the one we need for this + // argument position + PyObject* value = n_keyword_actual + ? PyDict_GetItem(keywords, PyTuple_GET_ITEM(kv, 0)) + : 0; + if (!value) { - PyErr_Clear(); - args2 = handle<>(); - break; + // Not found; check if there's a default value + if (PyTuple_GET_SIZE(kv) > 1) + value = PyTuple_GET_ITEM(kv, 1); + + if (!value) + { + // still not found; matching fails + PyErr_Clear(); + inner_args = handle<>(); + break; + } } - PyTuple_SET_ITEM(args2.get(), j, incref(value)); + else + { + ++n_actual_processed; + } + + PyTuple_SET_ITEM(inner_args.get(), arg_pos, incref(value)); + } + + if (inner_args.get()) + { + //check if we proccessed all the arguments + if(n_actual_processed < n_actual) + inner_args = handle<>(); } } } @@ -158,7 +202,7 @@ PyObject* function::call(PyObject* args, PyObject* keywords) const // Call the function. Pass keywords in case it's a // function accepting any number of keywords - PyObject* result = args2 ? f->m_fn(args2.get(), keywords) : 0; + PyObject* result = inner_args ? f->m_fn(inner_args.get(), keywords) : 0; // If the result is NULL but no error was set, m_fn failed // the argument-matching test. diff --git a/test/Jamfile b/test/Jamfile index 8812ed66..a1eb1cfc 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -84,6 +84,8 @@ bpl-test exception_translator ; bpl-test pearu1 : test_cltree.py cltree.cpp ; bpl-test try : newtest.py m1.cpp m2.cpp ; +bpl-test keywords : keywords.cpp keywords_test.py ; + extension builtin_converters : test_builtin_converters.cpp ../build/boost_python ; boost-python-runtest builtin_converters : test_builtin_converters.py builtin_converters ;