Cython reify属性?

时间:2018-01-11 23:51:39

标签: python attributes cython immutability

我的project中有这个(下面)代码模式,这是一个外汇客户端。我的问题源于this代码审查。该审核的结果导致我之前从tuple派生的类定义现在派生为object形式。这样做是为了在review中实现比我建议的更简单的 reifying (并且导致大的性能改进)。 实现的方式与类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

示例 reify

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

但也允许属性赋值。 (毕竟python中没有任何东西是私有的)

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

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

好的,这个问题的上下文:我如何在cython中实现上面提出不可变实例的Foo类? 受到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
>>>

1 个答案:

答案 0 :(得分:1)

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

首先:你无法访问cdef - 来自python的成员。这意味着python看不到cdef int _value,所以即使调用__getattr____getattribute__(self, '_value')也会抛出。

第二:cdef readonly int value不仅仅是眼睛。

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

在Cython创建的类Bar的类型描述符(在所有Cython创建C扩展名之后)中,您可以找到类的setter / 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。

这意味着,在创建类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. 与原始解决方案相比,您有一些额外的检查/间接,这可能会略微恶化性能,但这是控制对您的属性的访问所需支付的代价(要么成本稍高或无法控制)。
  2. 有一些样板代码,但使用python的动态特性(例如装饰器)并不难减少。
  3. 但这取决于你做出权衡。

    PS:它看起来很像您在代码审查中开始使用的代码。对我来说,属性的使用看起来比在运行时更改对象的“接口”更自然(更易于维护,更少混淆)。但是__getattr__的使用建议肯定有用。您最了解项目所处的位置,因此您决定哪种工具最适合。