【问题标题】:Passing specific arguments to Boost Python function with default arguments使用默认参数将特定参数传递给 Boost Python 函数
【发布时间】:2016-03-09 08:39:38
【问题描述】:

我正在尝试创建一个很好的接口,以便通过 Python 在我的游戏引擎中进行 HTTP 调用,但是我遇到了一些问题。

我有一个函数,get_async,它为指定的 URL 启动 请求。该函数在 http_manager 类中定义,如下所示:

struct http_manager
{
    typedef function<void(boost::shared_ptr<http_response>)> response_callback_type;
    typedef function<void(size_t)> write_callback_type;

    void get_async(
        const string& url,
        const http_headers_type& headers = http_headers_type(),
        const http_data_type& data = http_data_type(),
        response_callback_type on_response = nullptr,
        write_callback_type on_write = nullptr
        );
};

我能够在 Python 中成功进行此调用:

http.get_async('http://www.google.ca')

但是,我想拨打这样的电话:

http.get_async('http://www.google.ca', on_response=f)

这里的关键是我想通过名称显式指定参数并让所有其他参数成为默认值,就像在常规 Python 中一样。

不幸的是,当我这样做时,我从 Python 收到以下错误:

ArgumentError: Python argument types in
    HttpManager.get_async(HttpManager, str)
did not match C++ signature:
    get_async(struct naga::http_manager {lvalue}, class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > url)
    get_async(struct naga::http_manager {lvalue}, class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > url, class std::vector<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > > > headers)
    get_async(struct naga::http_manager {lvalue}, class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > url, class std::vector<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > > > headers, class std::map<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > > > data)
    get_async(struct naga::http_manager {lvalue}, class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > url, class std::vector<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > > > headers, class std::map<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > > > data, class std::function<void __cdecl(class boost::shared_ptr<struct naga::http_response>)> on_response)
    get_async(struct naga::http_manager {lvalue}, class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > url, class std::vector<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > > > headers, class std::map<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > > > data, class std::function<void __cdecl(class boost::shared_ptr<struct naga::http_response>)> on_response, class std::function<void __cdecl(unsigned int)> on_write)

当我清楚地传递 3 个参数(selfurlon_response)时,我很困惑为什么它认为参数签名是 HttpManager.get_async(HttpManager, str)

以下是我的 BOOST_PYTHON_MODULE 块中的相关位:

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(http_manager_get_async_overloads, http_manager::get_async, 1, 5)

class_<http_manager, noncopyable>("HttpManager", no_init)
    .def("get_async", &http_manager::get_async, http_manager_get_async_overloads(args("url", "headers", "data", "on_response", "on_write")))
    ;

感谢您的阅读,非常感谢您的帮助!

【问题讨论】:

  • 哦,天哪——我完全错过了你问题的底部,你说你已经在使用 Boost.Python。我的错!

标签: curl python c++ boost boost-python


【解决方案1】:

总的问题是 C++ 只使用默认参数来缺少尾随参数,而 Boost.Python 没有为非尾随缺失参数提供值。


错误信息

当 Boost.Python 在内部无法调度函数时,异常消息仅捕获位置参数。异常消息试图捕捉调用者的意图,但以 C++ 函数调用格式显示。由于 C++ 仅支持位置参数,因此没有明显的格式来显示非位置参数。考虑这个简单的example

#include <boost/python.hpp>

void f() {}

BOOST_PYTHON_MODULE(example)
{
  boost::python::def("f", &f);
}

调用example.f(a=0) 会抛出Boost.Python.ArgumentError 并显示以下消息:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
Boost.Python.ArgumentError: Python argument types in
    example.f()
did not match C++ signature:
    f(void)

尽管 Boost.Python 完全知道调用者提供了一个参数,但该异常只捕获了位置参数。在上面的例子中,没有位置参数,所以输出是example.f()。在最初的问题中,调用 http.get_async('http://www.google.ca', on_response=f) 会导致异常显示 HttpManager.get_async(HttpManager, str),因为只提供了两个位置参数:HttpManager 类型的隐式 self 参数和 str 对象。

调度失败

在 C++ 中,默认参数不是函数类型的一部分。此外,在调用具有默认参数的函数时,默认参数仅在缺少尾随参数的情况下使用。例如,考虑:

void f(int a, int b=1, int c=2);

上述函数的类型为void(int, int, int),无法调用函数f,只能为参数ac提供参数。如果希望使用参数c 的非默认值调用f,则必须提供所有三个参数。

但是,如果在 Python 中声明了类似的函数,则可以调用 f,只为参数 ac 提供参数,因为 Python 支持关键字参数:

def f(a, b=1, c=2):
    pass

f(0, c=42) # Invokes f(0, b=1, c=42)

考虑一下example,其中一个带有默认参数的 C++ 函数作为重载的 Python 函数提供:

#include <boost/python.hpp>

void f(int a, int b=1, int c=2) {}

BOOST_PYTHON_FUNCTION_OVERLOADS(f_overloads, f, 1, 3)

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;    
  python::def("f", &f, f_overloads(
    python::args("a", "b", "c")));
}

Boost.Python 被告知 C++ 函数 f 的类型为 void(int, int, int),并且可以用最少 1 个参数和最多 3 个参数调用。Boost.Python 也不知道默认参数是什么值是。因此,Boost.Python 创建了重载来支持调用:

  • f(int a)
  • f(int a, int b)
  • f(int, a, int b, int c)

因此,当尝试在 Python 中调用 example.f(0, c=42) 时,函数调度将失败,因为 Boost.Python 需要在为 c 提供值时为 b 提供值,并为 @ 提供值987654355@尚未指定。

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
Boost.Python.ArgumentError: Python argument types in
    example.f(int)
did not match C++ signature:
    f(int a)
    f(int a, int b)
    f(int a, int b, int c)

这种行为是 http.get_async('http://www.google.ca', on_response=f) 失败的原因,因为 Boost.Python 无法调用:

http_manager.get_async(url, ???, ???, on_response);

为 Python 提供默认参数

继续这个例子,如果 Python 提供了默认参数,那么它可以为非尾随参数提供值。公开函数时,可以通过分配给boost::python::arg 对象来为参数提供默认值。以下example 公开了带有默认参数的函数f

#include <boost/python.hpp>

void f(int a, int b=1, int c=2) {}

BOOST_PYTHON_FUNCTION_OVERLOADS(f_overloads, f, 1, 3)

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;    
  python::def("f", &f, f_overloads(
    (python::arg("a"), 
     python::arg("b")=1,
     python::arg("c")=2)));
}

Boost.Python 知道默认参数,它可以成功调用example.f(0, c=42)

【讨论】:

  • 鉴于函数签名、参数类型和名称,我建议阅读 this 答案以了解有关在 Boost.Python 中使用 Python 回调处理多线程的详细信息。
  • 我的回调系统将相当简单。所有on_responseon_write 回调都将延迟到每一帧的开始并由主线程调用,所以我认为我不需要处理管理 GIL。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-10
  • 1970-01-01
  • 1970-01-01
  • 2019-04-02
  • 2015-01-28
相关资源
最近更新 更多