将 HashMap 的 HashMap 导出到 Python
Exporting HashMap of HashMap to Python
我有一个用 Rust 编写的文本解析器,我想使用 pyo3
为它提供一个 Python 接口。
解析器 return 在 HashMap
中包含一个 HashMap
,内部 HashMap
的值是 serde_json::Value
类型。当我尝试将此 return 作为 PyObject
时,我收到一个我无法解决的错误。
这是我的问题的一个最小示例:
use std::collections::HashMap;
use pyo3::prelude::*;
use serde_json::Value;
#[pyfunction]
pub fn parse() -> PyResult<PyObject> {
let mapping: HashMap<i64, HashMap<String, Value>> = HashMap::from( [
( 1, HashMap::from( [
( "test11".to_string(), "Foo".into() ),
( "test12".to_string(), 123.into() ),
] ) ),
( 2, HashMap::from( [
( "test21".to_string(), "Bar".into() ),
( "test22".to_string(), 123.45.into() ),
] ) ),
] );
return pyo3::Python::with_gil( |py| {
Ok( mapping.to_object( py ) )
} );
}
#[pymodule]
fn parser( _py: Python, m: &PyModule ) -> PyResult<()> {
m.add_function( wrap_pyfunction!( parse, m )? )?;
return Ok( () );
}
运行 这导致错误
error[E0599]: the method `to_object` exists for struct `HashMap<i64, HashMap<std::string::String, Value>>`, but its trait bounds were not satisfied
--> src/lib.rs:22:15
|
22 | Ok( mapping.to_object( py ) )
| ^^^^^^^^^ method cannot be called on `HashMap<i64, HashMap<std::string::String, Value>>` due to unsatisfied trait bounds
|
::: /home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/collections/hash/map.rs:209:1
|
209 | pub struct HashMap<K, V, S = RandomState> {
| ----------------------------------------- doesn't satisfy `_: pyo3::ToPyObject`
|
= note: the following trait bounds were not satisfied:
`HashMap<std::string::String, Value>: pyo3::ToPyObject`
which is required by `HashMap<i64, HashMap<std::string::String, Value>>: pyo3::ToPyObject`
error[E0277]: the trait bound `Result<PyDict, PyErr>: IntoPyCallbackOutput<_>` is not satisfied
--> src/lib.rs:8:1
|
8 | #[pyfunction]
| ^^^^^^^^^^^^^ the trait `IntoPyCallbackOutput<_>` is not implemented for `Result<PyDict, PyErr>`
|
= help: the following implementations were found:
<Result<T, E> as IntoPyCallbackOutput<U>>
note: required by a bound in `pyo3::callback::convert`
--> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/pyo3-0.14.5/src/callback.rs:182:8
|
182 | T: IntoPyCallbackOutput<U>,
| ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `pyo3::callback::convert`
= note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)
目标是从 Python 调用此函数,它 return 是一个 dict
,如下所示:
{
1: {
"test11": "Foo",
"test12": 123,
},
2: {
"test21": "Bar",
"test22": 123.45,
},
}
编辑:实施解决方案
(根据@orlp的回答)
use std::collections::HashMap;
use pyo3::prelude::*;
use serde_json::Value;
fn value_to_object( val: &Value, py: Python<'_> ) -> PyObject {
match val {
Value::Null => py.None(),
Value::Bool( x ) => x.to_object( py ),
Value::Number( x ) => {
let oi64 = x.as_i64().map( |i| i.to_object( py ) );
let ou64 = x.as_u64().map( |i| i.to_object( py ) );
let of64 = x.as_f64().map( |i| i.to_object( py ) );
oi64.or( ou64 ).or( of64 ).expect( "number too large" )
},
Value::String( x ) => x.to_object( py ),
Value::Array( x ) => {
let inner: Vec<_> = x.iter().map(|x| value_to_object(x, py)).collect();
inner.to_object( py )
},
Value::Object( x ) => {
let inner: HashMap<_, _> =
x.iter()
.map( |( k, v )| ( k, value_to_object( v, py ) ) ).collect();
inner.to_object( py )
},
}
}
#[repr(transparent)]
#[derive( Clone, Debug )]
struct ParsedValue( Value );
impl ToPyObject for ParsedValue {
fn to_object( &self, py: Python<'_> ) -> PyObject {
value_to_object( &self.0, py )
}
}
#[pyfunction]
pub fn parse() -> PyResult<PyObject> {
let mapping: HashMap<i64, HashMap<String, ParsedValue>> = HashMap::from( [
( 1, HashMap::from( [
( "test11".to_string(), ParsedValue( "Foo".into() ) ),
( "test12".to_string(), ParsedValue( 123.into() ) ),
] ) ),
( 2, HashMap::from( [
( "test21".to_string(), ParsedValue( "Bar".into() ) ),
( "test22".to_string(), ParsedValue( 123.45.into() ) ),
] ) ),
] );
Ok( pyo3::Python::with_gil( |py| {
mapping.to_object( py )
} ) )
}
#[pymodule]
fn parser( _py: Python, m: &PyModule ) -> PyResult<()> {
m.add_function( wrap_pyfunction!( parse, m )? )?;
return Ok( () );
}
问题是 serde_json::Value
没有实现 pyo3::conversion::ToPyObject
特性。你也不能自己实现它,因为你不能在外部对象上实现外部特征。
你可以做的是包装你的 serde_json::Value
并在其上实现特征。这样的东西应该可以工作(未经测试):
use serde_json::Value;
use pyo3::conversion::ToPyObject;
fn value_to_object(val: &Value, py: Python<'_>) -> PyObject {
match val {
Value::Null => py.None(),
Value::Bool(b) => b.to_object(py),
Value::Number(n) => {
let oi64 = n.as_i64().map(|i| i.to_object(py));
let ou64 = n.as_u64().map(|i| i.to_object(py));
let of64 = n.as_f64().map(|i| i.to_object(py));
oi64.or(ou64).or(of64).expect("number too large")
},
Value::String(s) => s.to_object(py),
Value::Array(v) => {
let inner: Vec<_> = v.iter().map(|x| value_to_object(x, py)).collect();
inner.to_object(py)
},
Value::Object(m) => {
let inner: HashMap<_, _> =
m.iter().map(|(k, v)| (k, value_to_object(v, py))).collect();
inner.to_object(py)
},
}
}
#[repr(transparent)]
#[derive(Clone, Debug)]
struct MyValue(Value);
impl ToPyObject for MyValue {
fn to_object(&self, py: Python<'_>) -> PyObject {
value_to_object(self.0)
}
}
那么您应该存储 MyValue
s。
我有一个用 Rust 编写的文本解析器,我想使用 pyo3
为它提供一个 Python 接口。
解析器 return 在 HashMap
中包含一个 HashMap
,内部 HashMap
的值是 serde_json::Value
类型。当我尝试将此 return 作为 PyObject
时,我收到一个我无法解决的错误。
这是我的问题的一个最小示例:
use std::collections::HashMap;
use pyo3::prelude::*;
use serde_json::Value;
#[pyfunction]
pub fn parse() -> PyResult<PyObject> {
let mapping: HashMap<i64, HashMap<String, Value>> = HashMap::from( [
( 1, HashMap::from( [
( "test11".to_string(), "Foo".into() ),
( "test12".to_string(), 123.into() ),
] ) ),
( 2, HashMap::from( [
( "test21".to_string(), "Bar".into() ),
( "test22".to_string(), 123.45.into() ),
] ) ),
] );
return pyo3::Python::with_gil( |py| {
Ok( mapping.to_object( py ) )
} );
}
#[pymodule]
fn parser( _py: Python, m: &PyModule ) -> PyResult<()> {
m.add_function( wrap_pyfunction!( parse, m )? )?;
return Ok( () );
}
运行 这导致错误
error[E0599]: the method `to_object` exists for struct `HashMap<i64, HashMap<std::string::String, Value>>`, but its trait bounds were not satisfied
--> src/lib.rs:22:15
|
22 | Ok( mapping.to_object( py ) )
| ^^^^^^^^^ method cannot be called on `HashMap<i64, HashMap<std::string::String, Value>>` due to unsatisfied trait bounds
|
::: /home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/collections/hash/map.rs:209:1
|
209 | pub struct HashMap<K, V, S = RandomState> {
| ----------------------------------------- doesn't satisfy `_: pyo3::ToPyObject`
|
= note: the following trait bounds were not satisfied:
`HashMap<std::string::String, Value>: pyo3::ToPyObject`
which is required by `HashMap<i64, HashMap<std::string::String, Value>>: pyo3::ToPyObject`
error[E0277]: the trait bound `Result<PyDict, PyErr>: IntoPyCallbackOutput<_>` is not satisfied
--> src/lib.rs:8:1
|
8 | #[pyfunction]
| ^^^^^^^^^^^^^ the trait `IntoPyCallbackOutput<_>` is not implemented for `Result<PyDict, PyErr>`
|
= help: the following implementations were found:
<Result<T, E> as IntoPyCallbackOutput<U>>
note: required by a bound in `pyo3::callback::convert`
--> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/pyo3-0.14.5/src/callback.rs:182:8
|
182 | T: IntoPyCallbackOutput<U>,
| ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `pyo3::callback::convert`
= note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)
目标是从 Python 调用此函数,它 return 是一个 dict
,如下所示:
{
1: {
"test11": "Foo",
"test12": 123,
},
2: {
"test21": "Bar",
"test22": 123.45,
},
}
编辑:实施解决方案
(根据@orlp的回答)
use std::collections::HashMap;
use pyo3::prelude::*;
use serde_json::Value;
fn value_to_object( val: &Value, py: Python<'_> ) -> PyObject {
match val {
Value::Null => py.None(),
Value::Bool( x ) => x.to_object( py ),
Value::Number( x ) => {
let oi64 = x.as_i64().map( |i| i.to_object( py ) );
let ou64 = x.as_u64().map( |i| i.to_object( py ) );
let of64 = x.as_f64().map( |i| i.to_object( py ) );
oi64.or( ou64 ).or( of64 ).expect( "number too large" )
},
Value::String( x ) => x.to_object( py ),
Value::Array( x ) => {
let inner: Vec<_> = x.iter().map(|x| value_to_object(x, py)).collect();
inner.to_object( py )
},
Value::Object( x ) => {
let inner: HashMap<_, _> =
x.iter()
.map( |( k, v )| ( k, value_to_object( v, py ) ) ).collect();
inner.to_object( py )
},
}
}
#[repr(transparent)]
#[derive( Clone, Debug )]
struct ParsedValue( Value );
impl ToPyObject for ParsedValue {
fn to_object( &self, py: Python<'_> ) -> PyObject {
value_to_object( &self.0, py )
}
}
#[pyfunction]
pub fn parse() -> PyResult<PyObject> {
let mapping: HashMap<i64, HashMap<String, ParsedValue>> = HashMap::from( [
( 1, HashMap::from( [
( "test11".to_string(), ParsedValue( "Foo".into() ) ),
( "test12".to_string(), ParsedValue( 123.into() ) ),
] ) ),
( 2, HashMap::from( [
( "test21".to_string(), ParsedValue( "Bar".into() ) ),
( "test22".to_string(), ParsedValue( 123.45.into() ) ),
] ) ),
] );
Ok( pyo3::Python::with_gil( |py| {
mapping.to_object( py )
} ) )
}
#[pymodule]
fn parser( _py: Python, m: &PyModule ) -> PyResult<()> {
m.add_function( wrap_pyfunction!( parse, m )? )?;
return Ok( () );
}
问题是 serde_json::Value
没有实现 pyo3::conversion::ToPyObject
特性。你也不能自己实现它,因为你不能在外部对象上实现外部特征。
你可以做的是包装你的 serde_json::Value
并在其上实现特征。这样的东西应该可以工作(未经测试):
use serde_json::Value;
use pyo3::conversion::ToPyObject;
fn value_to_object(val: &Value, py: Python<'_>) -> PyObject {
match val {
Value::Null => py.None(),
Value::Bool(b) => b.to_object(py),
Value::Number(n) => {
let oi64 = n.as_i64().map(|i| i.to_object(py));
let ou64 = n.as_u64().map(|i| i.to_object(py));
let of64 = n.as_f64().map(|i| i.to_object(py));
oi64.or(ou64).or(of64).expect("number too large")
},
Value::String(s) => s.to_object(py),
Value::Array(v) => {
let inner: Vec<_> = v.iter().map(|x| value_to_object(x, py)).collect();
inner.to_object(py)
},
Value::Object(m) => {
let inner: HashMap<_, _> =
m.iter().map(|(k, v)| (k, value_to_object(v, py))).collect();
inner.to_object(py)
},
}
}
#[repr(transparent)]
#[derive(Clone, Debug)]
struct MyValue(Value);
impl ToPyObject for MyValue {
fn to_object(&self, py: Python<'_>) -> PyObject {
value_to_object(self.0)
}
}
那么您应该存储 MyValue
s。