如何在本机回调中使用Cython cdef类成员方法 [英] How to use a Cython cdef class member method in a native callback

查看:79
本文介绍了如何在本机回调中使用Cython cdef类成员方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个C ++库,该库主要由回调驱动。回调被注册为 std :: function 实例。

I have a C++ library that is heavily callback driven. The callbacks are registered as std::function instances.

回调注册函数可能类似于:

A callback registration function might look something like:

void register(std::function<void(int)> callback);

我可以通过注册简单的 cdef 函数 libcpp.functional.function 对象。说我有这个回调函数:

I can register plain cdef functions by making libcpp.functional.function objects. Say I have this callback:

cdef void my_callback(int n):
    # do something interesting with n

我可以成功注册:

cdef function[void(int)]* f_my_callback = new function[void(int)](my_callback)
register(deref(f_my_callback))

问题是我要注册 cdef类 方法 作为回调。假设我有一个类:

The problem is that I want to register cdef class methods as the callback. Let's say I have a class:

cdef class MyClass:

    cdef function[void(int)]* f_callback
    py_callback = lambda x: None

    # some more init/implementation

    def __init__(self):
        # ** this is where the problem is **
        self.f_callback = new function[void(int)](self.member_callback)
        register(deref(self.f_callback))

    cdef void member_callback(self, int n) with gil:
        self.py_callback(n)

    def register_py(self, py_callback):
        self.py_callback = py_callback

self.f_callback =新函数[void(int)](self.member_callback)行不起作用,因为 function [T] 构造函数看到了 MyClass 参数( code> self )。在常规python中,执行 self.member_callback 基本上等效于将 self 部分应用于 MyClass。 member_callback ,所以我认为可以,但是不是。

the self.f_callback = new function[void(int)](self.member_callback) line doesn't work because the function[T] constructor is seeing the MyClass parameter (self). In regular python, doing self.member_callback is basically equivalent to partially applying self to MyClass.member_callback, so I thought that this would be fine, but it is not.

我输入了typedef:

I made a typedef:

ctypedef void (*member_type)(int)

然后如果我进行转换,我可以将其编译:

Then if I cast, I can get it to compile:

self.f_callback = new function[void(int)](<member_type>self.member_callback)

但是当回调真正进入时,一切破碎。用 self 进行任何操作都会导致段错误。

But when callbacks actually come in, everything is broken. Doing anything with self results in a segfault.

通过赛昂类成员的正确方法是什么?作为C / C ++回调方法?

What is the 'right' way to pass a cython class member method as a C/C++ callback?

EDIT

,这个问题专门是关于将成员方法(即以 self 作为第一个参数的函数)传递给C ++函数,期望其参数类型为 std :: function

Just to clarify, this question is specifically about passing member methods (i.e. function with self as the first argument) to C++ functions expecting a parameter of type std::function.

如果我将 member_callback 装饰为 @staticmethod 并删除对 self 的所有引用,所有这些均按预期工作。不幸的是,该方法需要访问 self 才能正确执行类实例的逻辑。

If I decorate the member_callback with @staticmethod and remove all references to self it all works as expected. Unfortunately, the method needs to have access to self to correctly execute the logic for the class instance.

推荐答案

std :: function 可以接受一系列参数。以最简单的形式,它需要一个函数指针,该函数指针直接映射到其模板类型,而这正是Cython包装器真正要处理的所有内容。对于包装C ++成员函数,通常使用 std :: mem_fun std :: mem_fun_ref 相关类实例的副本或对相关实例的引用(在现代C ++中,您可能还会选择一个lambda函数,但这实际上只是为您提供了相同的选项)。

std::function can take a range of arguments. In its simplest form it takes a function pointer that directly maps to its template type, and this is all that the Cython wrappers for it are really set up to cope with. For wrapping a C++ member function you'd typically use std::mem_fun or std::mem_fun_ref to either take a copy of, or a reference to, the relevant class instance (in modern C++ you might also choose a lambda function, but that really just gives you the same options).

将其转换为Python / Cython,您需要在要调用其成员的对象上保留一个 PyObject * ,并负责处理自己计算的引用。很遗憾,Cython目前无法为您生成包装器,因此您需要自己编写一个支架。基本上,您需要一个可处理的对象来处理引用计数。

Converting this to Python/Cython you need to hold a PyObject* to the object whose member you are calling, and with that comes a responsibility to handle the reference counting yourself. Unfortunately Cython can't currently generate the wrappers for you, and so you need to write a holder yourself. Essentially you need a callable object that handles the reference counting.

先前的答案我展示了创建这种包装器的两种方法(一种是手动的,另一种是通过使用Boost Python来节省精力的,已经实现了非常相似的功能)。我在此处显示的方案适用于与相关签名匹配的任何Python可调用。尽管我用标准的Python模块级函数进行了说明,但是它对于成员函数也同样有效,因为 instance.memberfunction 会生成绑定成员函数-一个可调用的Python可调用函数

In a previous answer I showed two ways of creating this kind of wrapper (one of which was manual, and the second of which saved effort by using Boost Python, which has already implemented something very similar). The scheme I showed there will work for any Python callable matching the relevant signature. Although I illustrated it with a standard Python module level function it would work equally well for a member function since instance.memberfunction generates a bound member function - a Python callable which will work just as well.

对于您的问题,唯一的区别是您使用的是 cdef 成员函数而不是 def 成员函数。看起来应该可以,但不能(最近版本的Cython尝试将其自动转换为可调用的Python,但由于运行时错误而失败)。您可以将lambda函数创建为非常简单的包装器。从速度的角度来看,这比理想情况稍差一些,但具有使用现有代码的优势。

For your problem the only difference is that you are using a cdef member function rather than a def member function. This looks like it should work but doesn't (recent versions of Cython attempt to do an automatic conversion to a Python callable but it fails with a runtime error). You can create a lambda function though as a very simple wrapper. This is slightly less-than-ideal from a speed point of view but has the advantage of working with the existing code.

您需要对较早的答案进行修改如下:

The modifications you will need to make to earlier answer are as follows:


  • 如果您尝试使用手册 PyObjectWrapper 版本,然后更改参数类型以匹配您的签名(即,将 int,string& 更改为 int )。

  • If you are trying to use the manual PyObjectWrapper version then change the argument types to match your signature (i.e. change int, string& to int). You don't have to do this for the boost version.

您用于 void的包装器void register(std :: function< void( int)>回调); 必须为:

cdef extern from "whatever.hpp":
  void register(PyObjWrapper)
  # or
  void register(bpo)

取决于您使用的版本。这是Cython关于签名的谎言,但是由于这两个对象都是可调用的C ++对象,因此它们可以由C ++编译器自动转换为 std :: function

depending on which version you're using. This is lying to Cython about the signature, but since both of these objects are callable C++ objects they are automatically convertable to std::function by the C++ compiler.

调用寄存器作为寄存器(PyObjWrapper(lambda x:self.member_callback(x)) ) register(get_as_bpo(lambda x:self.member_callback(x)))

有可能创建一个更有效的版本,专门针对使用 cdef 函数。它将基于与 PyObjWrapper 非常相似的对象为基础。但是,鉴于我已经编写的代码可以使用,因此我不愿意在没有明确理由的情况下执行此操作。

It would be possible to create a more efficient version specifically targeted at using cdef functions. It would be based around an object that is pretty similar to a PyObjWrapper. However, I'm reluctant to do this without a clear justification given that the code I've already written will work.

这篇关于如何在本机回调中使用Cython cdef类成员方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆