Cython 扩展类型是否支持 class 属性?

Do Cython extension types support class attributes?

Python classes 可以有 class 个属性:

class Foo(object):
   bar = 4

是否有用于在 Cython 扩展类型中定义 class 属性的类似结构?例如,当我尝试编译以下 cython 代码时

cdef class Foo:
    cdef int bar
    bar = 4

我收到这个错误:

thing.c:773:3: error: use of undeclared identifier 'bar'
  bar = 4;
  ^
1 error generated.
error: command 'cc' failed with exit status 1

你不能那样做。我不知道是否支持静态属性,但是必须从方法访问 "normal" 属性,例如构造函数:

cdef class Foo:
    cdef int bar
    def __init__(self):
        self.bar = 4

虽然似乎不可能具有 C 类型的静态属性,但 Cython 扩展类型可以具有常规的 Python 静态属性,这些属性也可以在 Python 中自动访问。就像在 Python:

中声明它们一样声明它们
cdef class Foo:
    bar = 4

生成的代码显示这些静态属性作为 Python 对象存储在 class 对象的属性字典中,即如果您在使用 C 类型的上下文中使用它们,它们从 Python 个对象转换回来。

简短的回答是和否。

不,没有方便的句法习语可用于在 cdef class 中快速插入 class 属性。然而....

cython 的全部意义在于它为您提供较低级别的访问权限。额外努力的通常动机是性能,但您也可以用额外的自由来做 C 之类的事情。困难在于,有很多陷阱,在这种情况下,如果不进行大量工作,您将无法获得纯 python class 属性。尽管如此,对于简单的用例来说,还是很容易获得所需的东西。

例如,假设我正在将一些计算引擎作为 class 并且我希望全局设置所有实例的 return 值的精度。我想要一个编译时的默认值,有时我可能想将它调低以快速处理一些试验,然后在我的最终工作中将它调高。 class 属性是定制的,但您可以在 cython 中获得所需的功能,如下所示:

首先,在模块级别定义以下内容:

cdef int _precision[1]  # storage for my class 'attribute'
_precision[0]=8         # my default value, set during compilation

使用数组允许我们使用 cython 习语 precision[0],它等同于 C *precisioncdef 名称 precision 隐式是一个指针,因为数据项是一个数组。这允许使用 cython 语法习语将 cython 存储位置转换为 python 引用。如果您想要的只是一个可以被模块中任何 class 中的 cdef 代码访问的全局常量,那么您就完成了。如果您想严格将其用作 class 属性,则必须强制执行该规则 - 编译器不关心。

现在,如果您还想调整来自 python 代码的值,您将需要一对 cdef 模块中的 python 代码可以调用的函数来访问 'attribute':

cdef int*  get_precision(): return _precision
cdef void* set_precision(int i): _precision[0]=i

在这一点上,语义会与纯粹的 python 有所不同,除非你真的想出汗。你需要一个 python setter 和 getter 函数,我发现属性实现的 python 描述符协议是最简单的:

cdef class SomeCalculator:
  ...

  property precision:
    def __get__(self):
      """Get or set calculation precision, default == 8.
         This is like a class attribute: setting affects all instances,
         however, it also affects all subclasses."""
      return get_precision()[0]
    def __set__(self,int integer): set_precision(min(30,max(0,integer)))

第一个获得对 'attribute' 的 python 引用。第二个将 'attribute' 设置为 python 整数值,限制在限制范围内。 cython 函数调用和 return 界面自动处理转换,这比看起来更复杂。

例如,get_precision return 是 C-pointer。如果您在 get_precision 中进行了取消引用,您会在尝试 return 一个 __get__ 中的 C-int 时遇到错误,就好像它是 python 一样。相反,如果您只是省略 __get__ 中的 [0] 取消引用,您将在尝试 return 一个 C-pointer 时遇到一个错误,就好像它是一个 python int 一样。正如所写,自动转换正确匹配类型。 cython 对这类事情非常挑剔,可以默默 return 不正确的值,只能在运行时发现。可能需要一些实验才能推断出正确的咒语。

文档字符串告诉您不要期待纯 python class 属性。如果你想要 sub-class,并且让 sub-classes 使用不同的全局设置,你需要付出更多的努力。在 python 中,所有这些都是自动完成的。

即便如此,还是有其他的区别。可以在 class 或实例上引用真实的 class 属性。此 属性 只能在实例上引用。在实例上设置真实的 class 属性会创建实例特定的副本,使 class 属性保持不变,但对更改后的实例不可见。

对于给定的用例,这可行。不需要真正的 class 属性。由于 cython 代码通常不那么抽象和计算密集,因此这种最小的方法通常就足够了。