Экспорт хэш-карты хэш-карты в Python

#python #rust #serde-json #pyo3

#питон #Ржавчина #серде-джсон #pyo3

Вопрос:

У меня есть синтаксический анализатор текста, написанный на Rust, и я хочу предоставить ему интерфейс Python, используя pyo3 его .

Синтаксический анализатор возвращает a HashMap внутри a HashMap , и значения внутреннего HashMap имеют тип serde_json::Value . Когда я пытаюсь вернуть это как a PyObject , я получаю ошибку, которую не могу решить.

Это минимальный пример моей проблемы:

 use std::collections::HashMap;  use pyo3::prelude::*; use serde_json::Value;  #[pyfunction] pub fn parse() -gt; PyResultlt;PyObjectgt; {  let mapping: HashMaplt;i64, HashMaplt;String, Valuegt;gt; = 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: amp;PyModule ) -gt; PyResultlt;()gt; {  m.add_function( wrap_pyfunction!( parse, m )? )?;   return Ok( () ); }  

Выполнение этого приводит к ошибке

 error[E0599]: the method `to_object` exists for struct `HashMaplt;i64, HashMaplt;std::string::String, Valuegt;gt;`, but its trait bounds were not satisfied  --gt; src/lib.rs:22:15  | 22 | Ok( mapping.to_object( py ) )  | ^^^^^^^^^ method cannot be called on `HashMaplt;i64, HashMaplt;std::string::String, Valuegt;gt;` 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 HashMaplt;K, V, S = RandomStategt; {  | ----------------------------------------- doesn't satisfy `_: pyo3::ToPyObject`  |  = note: the following trait bounds were not satisfied:  `HashMaplt;std::string::String, Valuegt;: pyo3::ToPyObject`  which is required by `HashMaplt;i64, HashMaplt;std::string::String, Valuegt;gt;: pyo3::ToPyObject`  error[E0277]: the trait bound `Resultlt;PyDict, PyErrgt;: IntoPyCallbackOutputlt;_gt;` is not satisfied  --gt; src/lib.rs:8:1  | 8 | #[pyfunction]  | ^^^^^^^^^^^^^ the trait `IntoPyCallbackOutputlt;_gt;` is not implemented for `Resultlt;PyDict, PyErrgt;`  |  = help: the following implementations were found:  lt;Resultlt;T, Egt; as IntoPyCallbackOutputlt;Ugt;gt; note: required by a bound in `pyo3::callback::convert`  --gt; /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/pyo3-0.14.5/src/callback.rs:182:8  | 182 | T: IntoPyCallbackOutputlt;Ugt;,  | ^^^^^^^^^^^^^^^^^^^^^^^ 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)  

The goal is to call this function from Python and it returns a dict like this:

 {   1: {  "test11": "Foo",  "test12": 123,  },  2: {  "test21": "Bar",  "test22": 123.45,  }, }  

Edit: Implemented Solution

(based on the answer of @orlp)

 use std::collections::HashMap;  use pyo3::prelude::*; use serde_json::Value;  fn value_to_object( val: amp;Value, py: Pythonlt;'_gt; ) -gt; PyObject {  match val {  Value::Null =gt; py.None(),  Value::Bool( x ) =gt; x.to_object( py ),  Value::Number( x ) =gt; {  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 ) =gt; x.to_object( py ),  Value::Array( x ) =gt; {  let inner: Veclt;_gt; = x.iter().map(|x| value_to_object(x, py)).collect();  inner.to_object( py )  },  Value::Object( x ) =gt; {  let inner: HashMaplt;_, _gt; =  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( amp;self, py: Pythonlt;'_gt; ) -gt; PyObject {  value_to_object( amp;self.0, py )  } }  #[pyfunction] pub fn parse() -gt; PyResultlt;PyObjectgt; {  let mapping: HashMaplt;i64, HashMaplt;String, ParsedValuegt;gt; = 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: amp;PyModule ) -gt; PyResultlt;()gt; {  m.add_function( wrap_pyfunction!( parse, m )? )?;   return Ok( () ); }  

Ответ №1:

Проблема в том, что serde_json::Value эта черта не реализуется pyo3::conversion::ToPyObject . Вы также не можете реализовать это самостоятельно, так как вы не можете реализовать чужеродную черту на чужеродном объекте.

Что вы можете сделать, так это обернуть свою serde_json::Value черту и реализовать ее. Что-то вроде этого должно сработать (непроверено):

 use serde_json::Value; use pyo3::conversion::ToPyObject;  fn value_to_object(val: amp;Value, py: Pythonlt;'_gt;) -gt; PyObject {  match val {  Value::Null =gt; py.None(),  Value::Bool(b) =gt; b.to_object(py),  Value::Number(n) =gt; {  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) =gt; s.to_object(py),  Value::Array(v) =gt; {  let inner: Veclt;_gt; = v.iter().map(|x| value_to_object(x, py)).collect();  inner.to_object(py)  },  Value::Object(m) =gt; {  let inner: HashMaplt;_, _gt; =  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(amp;self, py: Pythonlt;'_gt;) -gt; PyObject {  value_to_object(self.0)  } }  

Тогда вместо этого вы должны хранить MyValue s.