[Python] Is it sound to use .as_slice() on a PyReadonlyArray and access the slice inside...

Discussão em 'Python' iniciado por Stack, Junho 2, 2025.

  1. Stack

    Stack Membro Participativo

    My goal is to implement an efficient (and safe) way to wrap a Rust function with zero-copy and GIL release during computation.

    My question is general, but here is a concrete example from my EMA function binding:

    use crate::indicators::ema::core_ema;
    use numpy::{PyArray1, PyArrayMethods};
    use pyo3::pyfunction;

    #[pyfunction(signature = (data, window_size = 14, alpha = None))]
    pub(crate) fn ema<'py>(
    py: pyo3::python<'py>,
    data: numpy::pyReadonlyArray1<'py, f64>,
    window_size: usize,
    alpha: Option<f64>,
    ) -> pyo3::pyResult<pyo3::py<numpy::pyArray1<f64>>> {
    let slice = data.as_slice()?;

    let py_array_out = unsafe { PyArray1::<f64>::new(py, [slice.len()], false) };
    let py_array_ptr = unsafe { py_array_out.as_slice_mut()? };

    py.allow_threads(|| core_ema(slice, window_size, alpha.into(), py_array_ptr))
    .map_err(|e| pyo3::exceptions::pyValueError::new_err(format!("{:?}", e)))?;

    Ok(py_array_out.into())
    }


    I want to release the GIL to enable multithreading. I wonder how to do it safely. Or if it's possible.

    First, in my understanding, I get a reference to input data which is owned by Python and can, in theory, be changed from the outside (e.g. by another Python thread). So in my understanding it's probably game over ?

    By discussing with ChatGPT-4(o3), he is arguing that :

    • as_slice() gives me a &[f64] with lifetime 'py, which is bound to the current GIL token. (I'm not entirely sure how about the GIL token and how it relate with the 'py lifetime)
    • allow_threads() releases the GIL. (I agree with that)
    • Holding a &[f64] tied to the GIL and using it while the GIL is not held might be undefined behavior. (I'm not sure about that)

    It was also suggested that using a *const [f64] would be safer in this context because we don't incorrectly inform the compiler (via a Rust reference) that the data is safe to access, we take full responsibility instead.

    In conclusion, my questions are:

    • Is it actually undefined behavior to hold and use a &[f64] from .as_slice() inside a Python::allow_threads() block?
    • If so, why exactly — is it because of the 'py lifetime, aliasing, or the GIL assumptions?
    • Is using raw pointers (*const f64) or creating the slice inside the allow_threads block a legitimate and recommended workarounds?

    Context: I'm developing a high-performance technical analysis library in Rust technicalysis (with a Python binding). It's open source, you can find it here: . Rust version : 1.84, PyO3 version: 0.24.2.

    Continue reading...

Compartilhe esta Página