Open nilday opened 6 years ago
Hi, @nilday. To get call
and call_method
on a PyOjbect
you need the trait cpython::ObjectProtocol
, so use cpython::ObjectProtocol
is necessary.
That being said, to call a python function, first you have to import the module:
let gil = cpython::Python::acquire_gil();
let py = gil.python();
let module = cpython::PyModule::import(py, "path.to.python.module")
.unwrap();
module.call(py, "function_name", cpython::NoArgs, None).unwrap()
I wrote the below code:
myapp/src/main.rs
extern crate cpython;
use cpython::{Python};
fn main() {
let gil = Python::acquire_gil();
println!("Hello from Rust!");
let py = gil.python();
let module = cpython::PyModule::import(py, "fibo").unwrap();
module.call(py, "fib", (1,), None).unwrap();
}
And saved the python module as myapp/pyth/fibo.py
But got the below error:
thread 'main' panicked at 'called
Result::unwrap()
on anErr
value: PyErr { ptype: <class 'ModuleNotFoundEr ror'>, pvalue: Some(ModuleNotFoundError("No module named 'fibo'",)), ptraceback: None }', libcore/result.rs:945 :5
I'm not sure why you'd think that myapp/pyth
would automatically be in your Python import path.
This is no different from getting an ImportError
in Python because a file isn't in your import path.
Try adding these before the attempt to import fibo
and you'll see what I mean:
println!("");
let exe_path = ::std::env::current_exe().unwrap();
println!("Root for relative imports if the contents of Python's os.curdir (usually '.') is in the import path:\n\t{}", exe_path.display());
let sys = cpython::PyModule::import(py, "sys").unwrap();
let sys_path = sys.get(py, "path").unwrap();
println!("Import Path:\n\t{:?}\n", sys_path);
In case you don't mind having to recompile the Rust every time you change the Python, bundling the Python code within your Rust source at compile time avoids the whole "import path" mess.
You can do that by using this code instead:
// Load the contents of ../pyth/fibo.py into a string literal named "fibo_str" at compile time
let fibo_str = include_str!("../pyth/fibo.py");
// Create a new empty module named "fibo"
let module = cpython::PyModule::new(py, "fibo").unwrap();
// Run the contents of `fibo_str` in the context of `module` to populate the module
py.run(fibo_str, Some(&module.dict(py)), None).unwrap();
This is the rust-cpython
equivalent to the Python "import a string as a module" example on this StackOverflow answer.
If you want a rust-cpython equivalent to the "To ignore any next attempt to import, add the module to sys
" line, here it is:
let sys = cpython::PyModule::import(py, "sys").unwrap();
sys.get(py, "modules").unwrap().set_item(py, "fibo", &module).unwrap();
Thanks @ssokolow
I wrote the below code:
extern crate cpython;
use cpython::{Python, PyResult, PyModule};
// Load the contents of ../pyth/fibo.py into a string literal named "fibo_str" at compile time
const FIBO_STR: &'static str = include_str!("../pyth/fibo.py");
fn main() {
let gil = Python::acquire_gil();
let py = gil.python();
example(py).unwrap();
}
fn example(py: Python<'_>) -> PyResult<()> {
let m = module_from_str(py, "fibo", FIBO_STR)?;
let out: Vec<i32> = m.call(py, "fib", (2,), None)?.extract(py)?;
println!("successfully found fibo.py at compiletime. Output: {:?}", out);
Ok(())
}
fn module_from_str(py: Python<'_>, name: &str, source: &str) -> PyResult<PyModule> {
// Create a new empty module named "fibo"
let module = cpython::PyModule::new(py, name).unwrap();
// Run the contents of `fibo_str` in the context of `module` to populate the module
py.run(source, Some(&module.dict(py)), None).unwrap();
Ok(module)
}
But got the below error:
thread 'main' panicked at 'called
Result::unwrap()
on anErr
value: PyErr { ptype: <class 'NameError'>, pvalue: Some("name 'print' is not defined"), ptraceback: Some(<traceback object at 0x1031a5cc8>) }', libcore/result.rs:945:5
The fibo.py
is:
def fib(n): # return Fibonacci series up to n
print('Hello from python!')
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
Moreover, when I tried using sys.get(py, "modules").unwrap().set_item(py, "fibo", &module).unwrap();
I got that no method named
set_itemfound for type
cpython::PyObjectin the current scope
Moreover, when I tried using sys.get(py, "modules").unwrap().set_item(py, "fibo", &module).unwrap(); I got that no method namedset_itemfound for typecpython::PyObjectin the current scope
Oops. Sorry about that. I forgot to mention this line in the code I pasted for that snip:
use cpython::ObjectProtocol;
But got the below error:
thread 'main' panicked at 'called Result::unwrap() on an Err value: PyErr { ptype: <class 'NameError'>, pvalue: Some("name 'print' is not defined"), ptraceback: Some(<traceback object at 0x1031a5cc8>) }', libcore/result.rs:945:5
Yeah. It looks like exposing the contents of the builtins
module as globals is optional and non-default in rust-cpython. I'll get back to you on that some time after I've had lunch.
I figured it out while waiting for the pot to boil.
According to the Python docs for exec()...
If the globals dictionary does not contain a value for the key
__builtins__
, a reference to the dictionary of the built-in module builtins is inserted under that key. That way you can control what builtins are available to the executed code by inserting your own__builtins__
dictionary into globals before passing it to exec().
It turned out all that was necessary was to add this line after the let module = ...
line:
let builtins = cpython::PyModule::import(py, "builtins").unwrap();
module.dict(py).set_item(py, "__builtins__", &builtins).unwrap();
...though, given the way you're using it, I'll have to see if I can use lazy_static
for the builtins
object after I've had my food.
Thanks @ssokolow , I'll check it once I be home, hope you enjoyed your breakfast :)
Thanks a lot, it is working as expected, may you need to add simple example in the codes for the new users :)
Never mind. I forgot about the need to pass the GIL lock into PyModule::import
. That makes every use of lazy_static
I could think of quite ugly.
I want to pass a python function to rust code and use rust to call it. As there is no PyFunction struct in rust-cpython, I think I should use PyObject to do it. But every time i use a pyObject.call(py, args, None), I got: no method named
call
found for typecpython::PyObject
in the current scopeHow should I solve this problem?