You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Cython:如何防护子类未调用父类__init__的cdef类内存错误?

问题背景

我在使用Cython封装Fortran/C代码时,cdef类的__init__方法需要根据输入(比如输入文件)在C层分配内存,因此必须在__init__而非__cinit__中执行内存分配操作。但继承场景存在内存错误风险:继承该Cython类的子类可能未调用父类的__init__方法,导致所需的C内存未被分配。我想了解通过定义__getattribute__和__setattr__方法、检查__init__是否已调用(未调用则抛出错误)的方案是否可行?


完整示例

待封装的C代码

#include "stdlib.h"

typedef struct
{
  int n;
  double *data;
  int count;
} Example;

void example_allocate(void **ptr)
{
  *ptr = (void *) malloc(sizeof(Example));
}

void example_deallocate(void **ptr)
{
  Example *p = (Example *) *ptr;
  free(p);
}

void example_create(void **ptr, int *n)
{
  Example *p = (Example *) *ptr;
  p->count = 3;
  p->n = *n;
  p->data = (double *) malloc(sizeof(double)*p->n);
  for (int i = 0; i < p->n; i++){
    p->data[i] = i;
  }
}

void example_sum_data(void **ptr, double *val)
{
  Example *p = (Example *) *ptr;

  *val = 0.0;
  for (int i = 0; i < p->n; i++){
    *val += p->data[i];
  }
}

void example_count_get(void **ptr, int *count)
{
  Example *p = (Example *) *ptr;
  *count = p->count;
}

void example_count_set(void **ptr, int *count)
{
  Example *p = (Example *) *ptr;
  p->count = *count;
}

Cython封装代码

from libcpp cimport bool as cbool
from cpython.object cimport PyObject_GenericSetAttr

cdef extern void example_allocate(void **ptr);
cdef extern void example_deallocate(void **ptr)
cdef extern void example_create(void **ptr, int *n)
cdef extern void example_sum_data(void **ptr, double *val)
cdef extern void example_count_get(void **ptr, int *count)
cdef extern void example_count_set(void **ptr, int *count)

cdef class Example:

  cdef void *_ptr
  cdef cbool _init_called

  def __cinit__(self):
    self._init_called = False
    example_allocate(&self._ptr)

  def __dealloc__(self):
    example_deallocate(&self._ptr)

  def __getattribute__(self, name):
    if not self._init_called:
      raise Exception('The "__init__" method of Example has not been called.')
    return super().__getattribute__(name)

  def __setattr__(self, name, value):
    if not self._init_called:
      raise Exception('The "__init__" method of Example has not been called.')
    PyObject_GenericSetAttr(self, name, value)

  def __init__(self, int n):           
    self._init_called = True
    example_create(&self._ptr, &n)

  def sum_data(self):
    cdef double val;
    example_sum_data(&self._ptr, &val)
    return val

  property count:
    def __get__(self):
      cdef int val
      example_count_get(&self._ptr, &val)
      return val
    def __set__(self, int val):
      example_count_set(&self._ptr, &val)

Python使用示例

e1 = Example(10)
a = e1.sum_data()
print(a)

class ExampleChild(Example):
    def __init__(self, n):
        # 忘记调用父类__init__
        pass

e2 = ExampleChild(9)
a = e2.sum_data() # 抛出异常
print(a)

方案可行性分析

这个方案完全可行,能有效规避子类未调用父类__init__导致的内存未初始化问题,核心优势如下:

  1. 主动拦截风险操作:通过重写__getattribute__和__setattr__,任何对实例属性(包括方法、property)的访问都会先检查_init_called标记,未完成初始化时直接抛出明确异常,避免访问未分配的C内存导致程序崩溃。
  2. 标记逻辑严谨可靠:__cinit__中初始化_init_called为False,只有父类__init__执行时才会将其设为True,确保只有完成内存分配后,实例才能被正常使用。
  3. 符合Python继承规范:即使子类重写__init__时遗漏父类调用,只要访问实例的任何成员,都会触发检查并报错,提前暴露问题而非引发隐性内存错误。

注意事项

  • 避免递归调用:__getattribute__中使用super().__getattribute__(name)是正确写法,不会触发自身逻辑的递归循环。
  • 异常提示清晰:当前的异常信息能让开发者快速定位到“未调用父类__init__”的问题根源。
  • 子类必须显式调用父类初始化:若子类需要自定义初始化逻辑,必须执行super().__init__(n),否则无法正常使用父类的任何功能,这也符合Python继承的最佳实践。

内容的提问来源于stack exchange,提问作者nicholaswogan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 19:20:01