我正在编写一个带有多个库的 Rust 项目。一些库导出类型由工作区中的其他库使用。除了 Rust crates,我还想向 Python 公开一些库,使用pyo3
crate生成 Python 绑定,这就是我遇到麻烦的地方。
问题如下。
假设我们有两个 Rust 库 crate producer
, 和consumer
。在producer
中,我们有一个简单的类型,MyClass
它是公开可用的,并且是 Python 模块的一部分。在consumer
crate 中,我有一些函数可以接受 type 的对象MyClass
,并对它们执行一些操作。这些函数在 Rust 中可用,并且也绑定到第二个 Python 模块中。
MyClass
我可以在 Python 和 Rust 中创建对象。我可以正确调用 Rust 代码中的函数(例如,来自另一个应用程序),这些函数接受MyClass
. 但是我不能从 Pythonconsumer
调用模块中接受 type 对象的函数。换句话说,虽然我可以在 Rust 或 Python 中创建类型的对象并在 Rust crate中使用它们,但我不能将对象从Python 模块传递给Python 模块。这样做会生成一个,尽管对象广告本身具有类型。为什么?MyClass
MyClass
consumer
producer
consumer
TypeError
MyClass
编辑:请参阅问题的底部以进行进一步调查。
我制作了一个 MCVE,可以从GitHub获得。Rust 和 Python 代码也包含在下面。
复制:
克隆 repo 后,您可以生成我得到的输出:
$ cargo build
$ python3 runme.py
你应该看到:
Object is of type: <class 'MyClass'>
isinstance(obj, MyClass): true
Could not convert object! PyErr { type: Py(0x10d79e5b0, PhantomData) }
Traceback (most recent call last):
File "./runme.py", line 32, in <module>
consumer.print_data(obj)
TypeError
平台详情:
- macOS 10.14.6
- 货物 1.44.0 (05d080faa 2020-05-06)
- rustc 1.44.0 (49cae5576 2020-06-01)
- Python 3.7.7
- pyo3 v0.11.1
代码:
/// producer.rs
use pyo3::prelude::*;
#[pyclass]
#[derive(Debug, Clone)]
pub struct MyClass {
data: u64,
}
#[pymethods]
impl MyClass {
#[new]
fn new(data: u64) -> Self {
MyClass { data }
}
pub fn get_data(&self) -> u64 {
self.data
}
}
#[pymodule]
fn producer(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<MyClass>()?;
Ok(())
}
/// consumer.rs
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
use producer::MyClass;
#[pyfunction]
fn print_data(cls: &MyClass) {
println!("{}", cls.get_data());
}
#[pyfunction]
fn convert_to_myclass(obj: &PyAny) -> PyResult<()> {
match obj.extract::<MyClass>() {
Ok(_) => println!("Converted to MyClass successfully"),
Err(err) => println!("Could not convert object! {:?}", err),
}
Ok(())
}
#[pyfunction]
fn print_type_info(obj: &PyAny) {
let typ = obj.get_type();
println!("Object is of type: {}", typ);
println!("isinstance(obj, MyClass): {}", typ.is_instance(obj).unwrap());
}
#[pymodule]
fn consumer(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(print_data))?;
m.add_wrapped(wrap_pyfunction!(print_type_info))?;
m.add_wrapped(wrap_pyfunction!(convert_to_myclass))?;
Ok(())
}
这个小的 Python 脚本演示了这个问题。第一个功能是确保构建的 crate 可以通过脚本导入。
#!/usr/bin/env python3
"""runme.py
MCVE showing showing type weirdness in Python/PyO3.
(C) 2020 Benjamin Naecker
"""
import os
import platform
def link_libraries():
names = ("libproducer", "libconsumer")
lib_extension = ".so" if platform.system() == "Linux" else ".dylib"
base_path = "./target/debug/"
for name in names:
source = os.path.join(base_path, f"{name}{lib_extension}")
new_name = name.replace("lib", "")
dest = f"./{new_name}.so"
if os.path.exists(dest):
os.remove(dest)
os.symlink(source, dest)
if __name__ == "__main__":
link_libraries()
import producer
import consumer
obj = producer.MyClass(10)
consumer.print_type_info(obj)
consumer.convert_to_myclass(obj)
consumer.print_data(obj)
更新:
我一直在深入研究这个问题,我开始怀疑这个问题是由 Rust 库的构建方式引起的。我对一般的库很熟悉,但对任何 Rust 细节都不太熟悉。似乎 Rust 将哈希编码到每个损坏的符号名称中。consumer
我目前的猜测是,这些哈希值在共享库和. 之间略有不同producer
,因此尽管类型具有相同的文本表示,但函数中MyClass
预期的实际类型略有不同。consumer
以下是一些具体细节。列出每个 crate 中的符号,然后用rustfilt
显示将它们分解:
$ nm producer.so | grep -e "MyClass.*type_object" | rustfilt -h
0000000000085fa8 d _<producer::MyClass as pyo3::type_object::PyTypeInfo>::type_object_raw::TYPE_OBJECT::h215179c585bab4ba
0000000000021810 t _<producer::MyClass as pyo3::type_object::PyTypeInfo>::type_object_raw::h115c96004643f7df
$ nm consumer.so | grep -e "MyClass.*type_object" | rustfilt -h
0000000000091430 d _<producer::MyClass as pyo3::type_object::PyTypeInfo>::type_object_raw::TYPE_OBJECT::h215179c585bab4ba
0000000000004260 t _<producer::MyClass as pyo3::type_object::PyTypeInfo>::type_object_raw::h0e4c5c91a2345444
0000000000027a70 t _<producer::MyClass as pyo3::type_object::PyTypeInfo>::type_object_raw::h115c96004643f7df
您可以看到箱子type_obect_raw
的符号中还有一个。consumer
我不确定如何验证这一点,但我怀疑这是用于将对象转换为在consumer
crate 中失败的函数的类型信息。这种类型的对象,虽然名称相同,但在某些方面必须有所不同,因为散列是不同的。
查看pyo3
文档,该方法type_object_raw
用于返回PyTypeObject
表示对象类型的实际值。MyClass
在我看来,当从producer
模块构造一个实例时,类型对象是从符号返回的,这似乎是合理的type_object_raw::h115c96004643f7df
。但是当函数像consumer::print_data
尝试转换传递的实例时MyClass
,它们使用符号type_object_raw::h0e4c5c91a2345444
来获取对象的类型。大概这些是不同的。
所以现在我的问题是,为什么有两个不同的符号来返回实例的类型MyClass
?