PyO3 / pyo3

Rust bindings for the Python interpreter
https://pyo3.rs
Other
11.47k stars 694 forks source link

Can't use rust keywords as python function names #4225

Open Databean opened 1 month ago

Databean commented 1 month ago

Bug Description

Using a rust keyword as the name of a function, e.g. #[pyo3(name = "<struct>")] results in a compilation error.

Steps to Reproduce

Sample program:

use pyo3::prelude::*;

#[pyclass(frozen, subclass)]
struct MyClass {
}

#[pymethods]
impl MyClass {
    #[new]
    fn new() -> Self {
        MyClass {}
    }

    #[pyo3(name = "struct")]
    fn struct_method(&self) -> usize {
        42
    }
}

#[pyfunction]
#[pyo3(name = "struct")]
fn struct_function() -> usize {
    42
}

/// A Python module implemented in Rust.
#[pymodule]
fn pyo3_example(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
    m.add_class::<MyClass>()?;
    m.add_function(wrap_pyfunction!(struct_function, m)?)?;
    Ok(())
}

Backtrace

$ RUSTFLAGS="-Zmacro-backtrace" cargo build
   Compiling pyo3_example v0.1.0 (/home/data/programming/pyo3_example)
error: expected a single identifier in double quotes
  --> src/lib.rs:14:19
   |
14 |     #[pyo3(name = "struct")]
   |                   ^^^^^^^^

error: expected a single identifier in double quotes
  --> src/lib.rs:21:15
   |
21 | #[pyo3(name = "struct")]
   |               ^^^^^^^^

error[E0433]: failed to resolve: function `wrapped_pyfunction` is not a crate or module
   --> /home/data/.cargo/registry/src/index.crates.io-6f17d22bba15001f/pyo3-0.21.2/src/macros.rs:153:14
    |
135 | macro_rules! wrap_pyfunction {
    | ---------------------------- in this expansion of `wrap_pyfunction!`
...
153 |             &wrapped_pyfunction::_PYO3_DEF,
    |              ^^^^^^^^^^^^^^^^^^ function `wrapped_pyfunction` is not a crate or module
    |
   ::: src/lib.rs:30:20
    |
30  |     m.add_function(wrap_pyfunction!(struct_function, m)?)?;
    |                    ------------------------------------ in this macro invocation

For more information about this error, try `rustc --explain E0433`.
error: could not compile `pyo3_example` (lib) due to 3 previous errors

Your operating system and version

$ uname -a
Linux data-desktop 6.9.1-arch1-2 #1 SMP PREEMPT_DYNAMIC Wed, 22 May 2024 13:47:07 +0000 x86_64 GNU/Linux

Your Python version (python --version)

Python 3.12.3

Your Rust version (rustc --version)

rustc 1.80.0-nightly (867900499 2024-05-23)

Your PyO3 version

0.21.2

How did you install python? Did you use a virtualenv?

$ pacman -S pyton
$ python3 -m venv .venv
$ source .venv/bin/activate
$ cargo build

Additional Info

struct is not a reserved word in Python, so it works normally as Python code:

class MyClass:
    def struct(self):
        return 42

def struct():
    return 42

m = MyClass()
print(m.struct())
print(struct())
Databean commented 1 month ago

Created #4226 to fix this issue.

Icxolu commented 1 month ago

This is also already possibly by using a raw identifier:

#[pyo3(name = "r#struct")]
fn struct_method(&self) -> usize {
    42
}
Databean commented 1 month ago

This is also already possibly by using a raw identifier:

#[pyo3(name = "r#struct")]
fn struct_method(&self) -> usize {
    42
}

This does work for the method, thanks:

>> import pyo3_example
>> m = pyo3_example.MyClass()
>> m.struct()
42

But it does not work for the standalone function:

>> import pyo3_example
>> pyo3_example.struct()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'pyo3_example' has no attribute 'struct'. Did you mean: 'r#struct'?
>> pyo3_example.r#struct
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'pyo3_example' has no attribute 'r'
>> pyo3_example["r#struct"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'module' object is not subscriptable

#[pyo3(name = r#struct)] doesn't work either, still giving

error: expected string literal
--> src/lib.rs:21:15
|
21 | #[pyo3(name = r#struct)]
|               ^^^^^^^^
Icxolu commented 1 month ago

It does work for standalone functions, if you use the raw identifier for the function itself:

#[pyfunction]
fn r#struct() -> usize {
    42
}

The syntax for the pyo3 attribute is (notice the quotes):

#[pyfunction]
#[pyo3(name = "r#struct")]
fn some_function -> usize {
    42
}

But this currently does also not work, but I would consider that a bug (I did track that down already, so fixing that should be easy).

davidhewitt commented 1 month ago

I'd hope that name = "r#struct" shouldn't be necessary, as I mostly view the r# bit as a syntax ugly that's necessary to escape the parser handling keywords when not wanted. So my personal take is that I'd probably prefer

#[pyfunction]
#[pyo3(name = "r#struct")]
fn some_function -> usize {
    42
}

to continue to be invalid.

Icxolu commented 1 month ago

Just for reference, your example is not invalid (in the sense that is does compile just fine), it just produces no usable function on the Python side.

davidhewitt commented 1 month ago

😬