Cython 具体化属性?

Cython reify attributes?

我的 project, which is a FOREX client. My question stems from this code review. The outcome of that review resulted in my class definitions that previously derived from tuple now derive form object. This was done to allow for simpler reifying than my suggested idea in the review 中有这个(下面的)代码模式(并且导致性能显着提高)。 实现方式与 class Foo(如下)类似。

reify = lambda x: x

class Foo(object):

    def __init__(self, value):
        self._value = value

    def __getattr__(self, item):
        print('Reifying')
        attr = object.__getattribute__(self, '_'+item)
        value = reify(attr)
        setattr(self, item, value)
        return value

示例 reifying

>>> foo = Foo(1)
>>> foo.value
Reifying
1
>>> foo.value
1

而且还允许进行属性分配。 (毕竟 python 中没有任何内容是私有的)

>>> foo.value = 2
>>> foo.value
2

我真的很想收回 tuple 个实例的安全性。知道来自服务器的信息不会被意外更改和采取行动。 (对于我自己和可能选择使用我的代码的其他人)

好的,这就是这个 问题的上下文:我如何在 cython 中实现上面的 Foo class 来呈现一个不可变的实例? 启发通过 this 问题。

我天真地试过这个:

cdef class Bar:
    cdef readonly int value
    cdef int _value

    def __init__(self, value):
        self._value = value

    def __getattr__(self, item):
        attr = object.__getattribute__(self, '_'+item)
        value = reify(attr)
        setattr(self, item, value)
        return value

但很快发现 __getattr__ 从未被调用,因为 value 是用 0

初始化的
>>> a = Bar(1)
>>> a.value
0
>>>

让我们来看看,为什么我们的方法不起作用。

第一件事:您无法从 python 访问 cdef-成员。这意味着 python 没有看到 cdef int _value,所以即使 __getattr__ 被调用,__getattribute__(self, '_value') 也会抛出。

其次:cdef readonly int value不止于此

通过声明一个成员 readonly value,您定义了一个 属性 value,它只有一个 getter(没有 setter),您可以看到在 cythonized C 代码中。

在 Cython 创建的 class Bar 的类型描述符(毕竟 Cython 创建了一个 C 扩展)中,您可以找到 class 的 setters/getters :

static PyTypeObject __pyx_type_10prop_class_Bar = {
   PyVarObject_HEAD_INIT(0, 0)
  "test.Bar", /*tp_name*/
  ....
  __pyx_getsets_10prop_class_Bar, /*tp_getset*/
  ...
};

您还可以查看属性:

static struct PyGetSetDef __pyx_getsets_10prop_class_Bar[] = {
  {(char *)"value", __pyx_getprop_10prop_class_3Bar_value, 0, (char *)0, 0},
  {0, 0, 0, 0, 0}
};

如您所见,仅定义了 getter(称为 __pyx_getprop_10prop_class_3Bar_value),但未定义 setter。

这意味着,在创建 class Bar 的对象之后,已经有一个名为 value 的属性,因此您的 __getattr__ 永远不会使用参数 value.

可以做什么?我会说,您要实现的是只读(缓存)属性。沿线的某个地方(不涉及 Cython):

class Foo:
    def __init__(self, value):
        self._value = value
        self._cached_value = None

    @property
    def value(self):
        if self._cached_value is None:
            print("calculating")
            self._cached_value=self._value # evaluate
        return self._cached_value

现在

>>> f=Foo(2)
>>> f.value 
calculating
2
>> f.value # no calculation!
2

好吧,并非一切都很好:

  1. 与您的原始解决方案相比,您有一些额外的 checks/indirection,这可能会稍微降低性能,但这是控制对您的属性的访问所付出的代价(成本略高或没有控制)。
  2. 有一些样板代码,但使用 python 的动态特性(例如装饰器)并不难减少。

但这取决于您做出权衡。


PS:它看起来很像您开始进入代码审查的代码。对我来说,属性的使用看起来比在运行时更改对象的 "interface" 更自然(更易于维护,更少混淆)。但是 __getattr__ 的提议用法肯定有其优点。您最了解项目的发展方向,因此您可以决定哪种工具最适合。