Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cython cannot use operator()

Tags:

c++

numpy

cython

When I try to use the following Cython code, I get the error I posted at the end about operator() not being defined. It appears that when I try to use operators Cython does not interpret it as a member function (notice there is no member access in the C++ source). If I try to call prng.operator()() then Cython will fail translation.

Is something special needed to use operator overloading in Cython?

import numpy as np
cimport numpy as np

cdef extern from "ratchet.hpp" namespace "ratchet::detail":
    cdef cppclass Ratchet:
        Ratchet()
        unsigned long get64()

cdef extern from "float.hpp" namespace "prng":
    cdef cppclass FloatPRNG[T]:
        double operator()()



cdef FloatPRNG[Ratchet] prng

def ratchet_arr(np.ndarray[np.float64_t, ndim=1] A):
    cdef unsigned int i
    for i in range(len(A)):
        A[i] = prng()


def ratchet_arr(np.ndarray[np.float64_t, ndim=2] A):
    cdef unsigned int i, j
    for i in range(len(A)):
        for j in range(len(A[0])):
            A[i][j] = prng()

ratchet.cpp: In function ‘PyObject* __pyx_pf_7ratchet_ratchet_arr(PyObject*, PyArrayObject*)’: ratchet.cpp:1343:162: error: ‘operator()’ not defined *__Pyx_BufPtrStrided1d(__pyx_t_5numpy_float64_t *, __pyx_pybuffernd_A.rcbuffer->pybuffer.buf, __pyx_t_3, __pyx_pybuffernd_A.diminfo[0].strides) = operator()();

Some more information inspired by Ianh. It appears that operator() cannot be used when the object is stack allocated

cat thing.pyx
cdef extern from 'thing.hpp':
    cdef cppclass Thing:
        Thing(int)
        Thing()
        int operator()()

# When this function doesn't exist, thing.so compiles fine
cpdef ff():
    cdef Thing t
    return t()

cpdef gg(x=None):
    cdef Thing* t
    if x:
        t = new Thing(x)
    else:
        t = new Thing()
    try:
        return t[0]()
    finally:
        del t

cat thing.hpp
#pragma once

class Thing {
    int val;

    public:
    Thing(int v): val(v) {}
    Thing() : val(4) {}

    int operator()() { return val; }
};
like image 799
chew socks Avatar asked Sep 29 '22 20:09

chew socks


1 Answers

Update: This should be fixed as of Cython 0.24 and later. I've left the workaround here for completeness.


After looking through the C++ compiler errors of examples like yours a little more, what appears to be happening is that Cython has a bug when overloading operator() for a stack allocated object. It appears to be trying to call operator() as if it were some sort of function you had defined instead of as a method of the C++ object you have defined. There are two possible workarounds. You can either alias the call operator and give it a different name in Cython than in C. You can also just allocate the object on the heap instead.

Depending on your use case, it could be a good idea to just patch the C file generated by Cython. You'd basically just have to search for hanging calls to operator() change them to method calls on the proper C++ object. I tried this with my example below and it worked, and it wasn't terribly difficult to trace which objects I needed to insert into the code. This approach will work well if you are only trying to write Python bindings to the library and will not be making a large number of calls to operator() at the Cython level, but it could become an awful pain if you have a large amount of development you intend to do in Cython.

You could try bug reporting it too. That would be good regardless of which route you take to get it working. It seems like the sort of thing that should be easy to fix as well, but I'm not an expert on Cython's internals.

Here's a minimal working example of how to use operator() for a heap allocated object in Cython. It works for me on Cython 0.21.

Thing.hpp

#pragma once

class Thing{
    public:
        int val;
        Thing(int);
        int operator()(int);};

Thing.cpp

#include "Thing.hpp"

Thing::Thing(int val){
    this->val = val;}

int Thing::operator()(int num){
    return this->val + num;}

Thing.pxd

cdef extern from "Thing.hpp":
    cdef cppclass Thing:
        Thing(int val) nogil
        int operator()(int num) nogil

test_thing.pyx

from Thing cimport Thing

cpdef test_thing(int val, int num):
    cdef Thing* t = new Thing(val)
    print "initialized thing"
    print "called thing."
    print "value was: ", t[0](num)
    del t
    print "deleted thing"

setup.py

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
from os import system

# First compile an object file containing the Thing class.
system('g++ -c Thing.cpp -o Thing.o')

ext_modules = [Extension('test_thing',
                         sources=['test_thing.pyx'],
                         language='c++',
                         extra_link_args=['Thing.o'])]

# Build the extension.
setup(name = 'cname',
      packages = ['cname'],
      cmdclass = {'build_ext': build_ext},
      ext_modules = ext_modules)

After running the setup file, I start the Python interpreter in the same directory and run

from test_thing import test_thing
test_thing(1, 2)

and it prints the output

initialized thing
called thing.
value was:  3
deleted thing

showing that the operator is working properly.

Now, if you want to do this for a stack allocated object, you can change the Cython interface as follows:

Thing.pxd

cdef extern from "Thing.hpp":
    cdef cppclass Thing:
        Thing(int val) nogil
        int call "operator()"(int num) nogil

test_thing.pyx

from Thing cimport Thing

cpdef test_thing(int val, int num):
    cdef Thing t = Thing(val)
    print "initialized thing"
    print "called thing."
    print "value was: ", t.call(num)
    print "thing is deleted when it goes out of scope."

The C++ files and setup file can still be used as they are.

like image 133
IanH Avatar answered Oct 03 '22 00:10

IanH