10

いくつかのライブラリを使用して Rust プロジェクトを作成しています。一部のライブラリは、ワークスペース内の他のライブラリによって使用されるタイプをエクスポートします。Rust クレートに加えて、いくつかのライブラリを Python に公開し、pyo3クレートを使用してPython バインディングを生成したいと考えていますが、ここで問題が発生しています。

問題は次のとおりです。

2 つの Rust ライブラリ クレート とproducerがあるとしconsumerます。には、一般に公開されproducerている単純な型があり、Python モジュールの一部になっています。MyClassクレートにはconsumer、 type のオブジェクトを受け取りMyClass、それらに対していくつかの操作を実行する関数がいくつかあります。これらの関数は Rust で利用でき、2 つ目の Python モジュールにもバインドされています。

MyClassPython と Rust の両方でオブジェクトを作成できます。のオブジェクトを受け入れる Rust コードの関数を (たとえば、別のアプリケーションから) 正しく呼び出すことができますMyClass。しかし、タイプ のオブジェクトを受け入れるconsumerモジュール内の関数をPython から呼び出すことはできませんMyClass。つまり、Rust または Python で型のオブジェクトを作成MyClassして Rustconsumerクレートで使用することはできますが、オブジェクトをPython モジュールから Python モジュールに渡すことはできませんproducerconsumer。そうすることTypeErrorで、オブジェクト自体が type を持っていると宣伝しているにもかかわらず、が生成されますMyClass。なんで?

編集:さらに調査するには、質問の下部を参照してください。

MCVE を作成しました。これはGitHub から入手できます。Rust と Python のコードも以下に含まれています。

再現:

リポジトリを複製した後、次の出力を生成できます。

$ 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)
  • パイソン 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 スクリプトは、この問題を示しています。最初の機能は、構築されたクレートがスクリプトによってインポートできるようにすることです。

#!/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型にもかかわらず、関数で期待される実際の型はわずかに異なります。MyClassconsumer

これを具体化するための詳細を次に示します。各クレートのシンボルを一覧表示し、それらをデマングルすると、次のようになり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のシンボルに1 つ追加があることがわかります。consumerこれを確認する方法はわかりませんが、これは、consumerクレートで失敗した関数に渡されたオブジェクトを変換するために使用される型情報であると思われます。この型オブジェクトは同じ名前ですが、ハッシュが異なるため、何らかの形で異なる必要があります。

pyo3docsを見ると、このメソッドは、オブジェクトの型を表すtype_object_raw実際の値を返すために使用されます。モジュールから のインスタンスを構築するとき、型オブジェクトがシンボルから返されることはPyTypeObject私にはもっともらしいようです。しかし、 のような関数が渡された のインスタンスを変換しようとすると、シンボルを使用してオブジェクトの型を取得します。これらは異なるものと考えられます。MyClassproducertype_object_raw::h115c96004643f7dfconsumer::print_dataMyClasstype_object_raw::h0e4c5c91a2345444

だから今私の質問は、インスタンスの型を返すために2つの異なるシンボルがあるのはなぜMyClassですか?

4

1 に答える 1