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

PyO3构建兼容Pydantic的类时,类方法无法引用Self的问题

解决PyO3与Pydantic自定义类型验证兼容问题

核心问题拆解

你遇到的本质矛盾是:Pydantic要求__get_validators__作为类方法返回验证器迭代器,但PyO3的#[classmethod]无法接收实例引用,导致无法访问MyRustStruct的实例字段。下面是几个可行的解决方案:

具体实现方案

方案1:将验证依赖转为类级属性

把原本需要从实例获取的配置,改成类的静态属性(用#[classattr]标记),这样类方法就能直接读取这些数据,无需实例引用。

use pyo3::prelude::*;
use pyo3::types::PyIterator;

#[pyclass]
struct MyRustStruct {
    // 把实例字段转为类属性
    #[classattr]
    validation_rule: String,
}

#[pymethods]
impl MyRustStruct {
    #[new]
    fn new(validation_rule: String) -> Self {
        MyRustStruct { validation_rule }
    }

    #[classmethod]
    fn __get_validators__(cls: &PyType) -> PyResult<Py<PyIterator>> {
        // 直接从类属性读取验证规则
        let rule = cls.getattr("validation_rule")?.extract::<String>()?;
        
        // 返回包含__call__验证方法的迭代器
        let validators = vec![cls.getattr("__call__")?];
        Python::with_gil(|py| Ok(PyIterator::from_object(py, &validators.into_py(py))?))
    }

    fn __call__(&self, value: &PyAny) -> PyResult<Py<PyAny>> {
        // 用类属性的规则做验证
        if value.extract::<String>()?.contains(&self.validation_rule) {
            Ok(value.into())
        } else {
            Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
                format!("值必须包含'{}'", self.validation_rule),
            ))
        }
    }
}

方案2:提前创建单例实例供验证器使用

如果必须保留实例级别的灵活性,可以在类中提前初始化一个单例实例,然后在__get_validators__中引用这个实例的字段。

use pyo3::prelude::*;
use pyo3::types::PyIterator;
use pyo3::PyCell;

#[pyclass]
struct MyRustStruct {
    validation_rule: String,
}

#[pymethods]
impl MyRustStruct {
    #[new]
    fn new(validation_rule: String) -> Self {
        MyRustStruct { validation_rule }
    }

    #[classmethod]
    fn __get_validators__(cls: &PyType) -> PyResult<Py<PyIterator>> {
        Python::with_gil(|py| {
            // 创建类实例(可改为从类属性缓存的单例)
            let instance = cls.call1((String::from("required_substr"),))?;
            let cell = PyCell::from_ref(py, &instance);
            
            // 定义捕获实例的验证闭包
            let validator = move |value: &PyAny| -> PyResult<Py<PyAny>> {
                let instance = cell.borrow();
                let rule = &instance.validation_rule;
                if value.extract::<String>()?.contains(rule) {
                    Ok(value.into())
                } else {
                    Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
                        format!("值必须包含'{}'", rule),
                    ))
                }
            };

            // 把闭包转为Python可调用对象并返回迭代器
            let py_validator = PyFunction::new_closure(py, None, validator)?;
            let validators = vec![py_validator.into()];
            Ok(PyIterator::from_object(py, &validators.into_py(py))?)
        })
    }
}

方案3:直接返回验证器列表(无需自定义迭代器)

Pydantic只要求__get_validators__返回可迭代对象,没必要自定义迭代器容器,直接返回包含验证函数的列表即可,大幅简化实现。

use pyo3::prelude::*;

#[pyclass]
struct MyRustStruct {
    validation_rule: String,
}

#[pymethods]
impl MyRustStruct {
    #[new]
    fn new(validation_rule: String) -> Self {
        MyRustStruct { validation_rule }
    }

    #[classmethod]
    fn __get_validators__(cls: &PyType) -> PyResult<Vec<Py<PyAny>>> {
        // 直接返回包含__call__方法的列表
        Ok(vec![cls.getattr("__call__")?])
    }

    fn __call__(&self, value: &PyAny) -> PyResult<Py<PyAny>> {
        if value.extract::<String>()?.len() >= 5 {
            Ok(value.into())
        } else {
            Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
                "值长度至少为5",
            ))
        }
    }
}

关键注意事项

  • Pydantic调用__get_validators__时是通过类而非实例,所以类方法无法获取实例状态,必须把验证依赖的配置提升到类级别,或者提前创建可复用的实例。
  • 频繁创建实例会影响性能,建议用类属性缓存已初始化的实例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 04:55:05