尝试定义 cython 方法导致“此处不允许使用 cdef 语句”错误

Trying to define a cython method results in `cdef statement not allowed here` error

我正在尝试使用 EasyCython 模块将纯 Python 模块转换为与 Cython 兼容的模块。
然而,问题是,在尝试执行 EasyCython 时,它失败并显示错误:

G:\Proc\python\FV>easycython F_V.pyx
Compiling F_V.pyx because it changed.
[1/1] Cythonizing F_V.pyx
C:\Users\Rika\Anaconda3\Lib\site-packages\Cython\Compiler\Main.py:369: FutureWarning: Cython directive 'language_level' not set, using 2 for now (Py2). This will change in a later release! File: G:\Proc\python\FV\F_V.pyx
  tree = Parsing.p_module(s, pxd, full_module_name)

Error compiling Cython file:
------------------------------------------------------------
...
    # or the network would mistake some one else for someother people! (for example someone has a profile image
    # while the other doesnt, when confronted with the profile image, the network most likely find more features
    # from the person that has already a profile image of him in the fbank (unless the change is noticeable
    # this really can be a major issue)))
    # @benchmark
    cpdef _identify_id(self, input_img_embedding, list embedding_list, bint short_circut=True, bint accumulate_score=False):
         ^
------------------------------------------------------------

F_V.pyx:929:10: cdef statement not allowed here
Traceback (most recent call last):
  File "C:\Users\Rika\Anaconda3\Lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "C:\Users\Rika\Anaconda3\Lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\Users\Rika\Anaconda3\Scripts\easycython.exe\__main__.py", line 7, in <module>
  File "C:\Users\Rika\Anaconda3\Lib\site-packages\begin\main.py", line 54, in start
    collector=self._collector)
  File "C:\Users\Rika\Anaconda3\Lib\site-packages\begin\cmdline.py", line 253, in apply_options
    return_value = call_function(func, signature(ext), opts)
  File "C:\Users\Rika\Anaconda3\Lib\site-packages\begin\cmdline.py", line 236, in call_function
    return func(*pargs, **kwargs)
  File "C:\Users\Rika\Anaconda3\Lib\site-packages\easycython\easycython.py", line 77, in main
    ext_modules = cythonize(ext_modules),
  File "C:\Users\Rika\Anaconda3\Lib\site-packages\Cython\Build\Dependencies.py", line 1102, in cythonize
    cythonize_one(*args)
  File "C:\Users\Rika\Anaconda3\Lib\site-packages\Cython\Build\Dependencies.py", line 1225, in cythonize_one
    raise CompileError(None, pyx_file)
Cython.Compiler.Errors.CompileError: F_V.pyx

这是用 cython 类型注释的两个方法。

#@benchmark
cpdef _identify_id(self, input_img_embedding, list embedding_list, bint short_circut=True, bint accumulate_score=False):
    # These are the underlying types for the arguments and local variables used here
    # input_img_embedding_type: <class 'torch.Tensor'> shape:torch.Size([1, 512])
    # feature1 type: <class 'torch.Tensor'> shape: torch.Size([1, 512])
    # x1 type: <class 'torch.Tensor'> shape: torch.Size([1, 512])
    # cosine type: <class 'numpy.float32'> shape: ()
    # np.clip(cosine) type: <class 'numpy.float64'> shape: ()
    #
    cdef float min_theta = 1000.0
    cdef float total_theta = 0.0
    cdef char* id_name = 'None' #None
    for (name, feature1) in embedding_list:
        id_name = name
        x1 = feature1 / np.linalg.norm(feature1)
        cosine = np.dot(input_img_embedding.squeeze(0), x1.squeeze(0))
        cdef float cosine = np.clip(cosine, -1.0, 1.0)
        cdef float theta = math.acos(cosine)
        cdef float theta = theta * 180 / math.pi

        if short_circut:
            if theta < self._threshold:
                return id_name, theta

        if theta < min_theta:
            min_theta = theta

        total_theta += theta

    # the elses from now on are for debugging purposes
    if not short_circut and not accumulate_score:
        if min_theta < self._threshold:
            return id_name, min_theta
        else:
            return 'unknown', min_theta

    if accumulate_score:
        final_score = total_theta/len(embedding_list)
        if final_score < self._threshold:
            return id_name, final_score
        else:
            return 'unknown', final_score

    return 'unknown', theta # min_theta

#@benchmark
cpdef _check_in_fbank(self, img):
    """Checks whether a given image is represented in the face bank.

    Arguments:
        img {torch.tensor} -- input image to be verified

    Returns:
        tuple(name, theta)
    """
        # These are the underlying python types
        # img type: <class 'torch.Tensor'> shape: torch.Size([3, 112, 112])
        # feature0 type: <class 'torch.Tensor'> shape: torch.Size([1, 512])
        # x0 type: <class 'torch.Tensor'> shape: torch.Size([1, 512])

    with Benchmark_Block("model frwd took: "):
         feature0 = self.model(img.unsqueeze(0)).cpu().detach()
         x0 = feature0 / np.linalg.norm(feature0)

    cdef list lst = []
    for img_list in self._fbank_embeddings:
        cdef tuple f = self._identify_id(x0, img_list, short_circut=self.short_circut, accumulate_score=self.accumulate_score)
        lst.append(f)

    cdef tuple min_val  = min(lst, key=lambda t: t[1])
    print(f'lst of returned results : {lst}. The minimum is: {min_val} in {len(self._fbank_embeddings)} enteries')
    return min_val

我在这里错过了什么?

问题恰好是当使用 python class 时,也应该使用 cdef。这种 class 称为 Extension Type.
来自 Cython 文档:

... a Cython extension type definition looks a lot like a Python class definition. Within it, you use the def statement to define methods that can be called from Python code. You can even define many of the special methods such as init() as you would in Python.

注:

这样做之后,为了能够在 Python 中使用我的 class,我不得不继承它,而不是使用继承的class。那是我必须做的:

cdef class MyClass():
    def __init__(self, args)
        ... 

    cpdef func1(self,...):
        ...

    cpdef func2(self, ....):
        ...
    ...

class MyClass2(MyClass):
    def __init__(self, args):
       super().__init__(args)
       pass 

在您的客户端代码中:

# use MyClass2 from now on
obj = MyClass2(args)
...