【问题标题】:rust: py03 how to get reference to struct memberrust: py03 如何获取对结构成员的引用
【发布时间】:2022-07-29 18:37:51
【问题描述】:

如何通过 Packet.Header.a 编辑 Header.a?

#![allow(dead_code)]
use pyo3::prelude::*;

#[pyclass]
#[derive(Clone)]
pub struct Header {
    #[pyo3(get, set)]
    a: u32,
    #[pyo3(get, set)]
    b: u32,
}
#[pymethods]
impl Header {
    #[new]
    fn new(a: u32, b: u32) -> Self {
        Header { a, b }
    }
}

#[pyclass]
/// Structure used to hold an ordered list of headers
pub struct Packet {
    #[pyo3(get, set)]
    pub h: Header,
}
#[pymethods]
impl Packet {
    #[new]
    fn new() -> Self {
        Packet {
            h: Header { a: 0, b: 0 },
        }
    }
}

#[pymodule]
fn pyo3test(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<Header>()?;
    m.add_class::<Packet>()?;
    Ok(())
}

在 python 中运行“maturin develop”后

from pyo3test import *
p = Packet()
print(p.h.a) # prints 0
h = p.h
h.a = 1
print(h.a) -> # prints 1
print(p.h.a) -> # still prints 0
p.h.a = 1
print(p.h.a) # still prints 0

这似乎违反了 python 语义。 h 是对 p.h 的引用。对 h 的更新应该更新了 p.h。 如何实现 get trait 以返回对 Packet.Header 的引用?

【问题讨论】:

  • 这能回答你的问题吗? Return reference to member field in PyO3
  • 虽然这不是完全相同的问题,但我认为答案一针见血。问题是它们有不同的内存模型——Rust 是基于所有权的,而 Python 是引用计数的。这是唯一可能的解决方案是妥协的情况之一。从技术上讲,可以使用 Arc&lt;Mutex&lt;...&gt;&gt; 对 Python 的所有权模型(以某种方式)进行建模,但我认为这目前不在 PyO3 中。
  • 要表明这实际上是 PyO3 的预期行为,请阅读 this chapter of their documentation,其中声明 “对于 get,字段类型必须同时实现 IntoPy&lt;PyObject&gt;Clone。”.

标签: rust pyo3


【解决方案1】:

免责声明:我不是 PyO3 方面的专家。我只是对 Rust 和 Python 的工作原理有所了解。所以,我说的每一句话都信以为真。

这里的问题是 Rust 和 Python 有一个非常不同的内存模型。 Rust 是基于所有权的,而 Python 是引用计数的。这给实现可在两种语言中使用的类带来了一些挑战。

专门针对getter/setter,似乎PyO3 decided to clone()而不是引用计数:

对于get,字段类型必须同时实现IntoPy&lt;PyObject&gt;Clone

还有进一步的不兼容性:Python 不是类型安全的。这意味着任何项目都可以是任何类型,例如,您可以编写: p.h.a = "Test",虽然 Rust 中的定义 Header::a 显然是 u32。这也“似乎违反了 python 语义”。


也就是说,您可以通过在内部使用引用计数器来实现类似的效果。但是,您不能(在撰写本文时以及据我所知)将这些引用计数器公开给 Python。 (意思是,你不能使用 Arc&lt;Header&gt; 之类的东西并从 getter 中返回)

但是你可以让Header 本身有点像一个引用计数器:

use std::sync::{
    atomic::{AtomicU32, Ordering},
    Arc,
};

use pyo3::prelude::*;

#[pyclass]
#[derive(Clone)]
pub struct Header {
    a: Arc<AtomicU32>,
    b: Arc<AtomicU32>,
}
#[pymethods]
impl Header {
    #[new]
    fn new(a: u32, b: u32) -> Self {
        Header {
            a: Arc::new(AtomicU32::new(a)),
            b: Arc::new(AtomicU32::new(b)),
        }
    }

    #[getter]
    fn get_a(&self) -> PyResult<u32> {
        Ok(self.a.load(Ordering::Acquire))
    }

    #[setter]
    fn set_a(&mut self, value: u32) -> PyResult<()> {
        self.a.store(value, Ordering::Release);
        Ok(())
    }

    #[getter]
    fn get_b(&self) -> PyResult<u32> {
        Ok(self.b.load(Ordering::Acquire))
    }

    #[setter]
    fn set_b(&mut self, value: u32) -> PyResult<()> {
        self.b.store(value, Ordering::Release);
        Ok(())
    }
}

#[pyclass]
/// Structure used to hold an ordered list of headers
pub struct Packet {
    #[pyo3(get, set)]
    pub h: Header,
}
#[pymethods]
impl Packet {
    #[new]
    fn new() -> Self {
        Packet {
            h: Header::new(0, 0),
        }
    }
}

/// A Python module implemented in Rust.
#[pymodule]
#[pyo3(name = "rust_python_test")]
fn rust_python_test(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<Header>()?;
    m.add_class::<Packet>()?;
    Ok(())
}
#!/usr/bin/env python3

from rust_python_test import Packet

p = Packet()
print(p.h.a) # prints 0
h = p.h
h.a = 1
print(h.a) # prints 1
print(p.h.a) # prints 1
p.h.a = 1
print(p.h.a) # prints 1

【讨论】:

    猜你喜欢
    • 2023-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-12
    • 1970-01-01
    • 2019-06-10
    • 2023-03-10
    • 2020-08-27
    相关资源
    最近更新 更多