PyO3/pyo3 · error · syn::Error

`get` may only be specified once

Error message

`get` may only be specified once

What it means

pyo3's #[pyclass]/field attribute parser rejects a `get` option that appears more than once on the same item. Each field attribute option is stored in an Option slot; when a duplicate `get` keyword is parsed, the `replace` on the slot returns Some and the macro emits this syn::Error pointing at the duplicate keyword's span.

Source

Thrown at pyo3-macros-backend/src/pyclass.rs:402

        } else {
            Err(lookahead.error())
        }
    }
}

impl FieldPyO3Options {
    fn take_pyo3_options(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
        let mut options = FieldPyO3Options {
            get: None,
            set: None,
            name: None,
        };

        for option in take_pyo3_options(attrs)? {
            match option {
                FieldPyO3Option::Get(kw) => {
                    if options.get.replace(Annotated::Field(kw)).is_some() {
                        return Err(syn::Error::new(kw.span(), UNIQUE_GET));
                    }
                }
                FieldPyO3Option::Set(kw) => {
                    if options.set.replace(Annotated::Field(kw)).is_some() {
                        return Err(syn::Error::new(kw.span(), UNIQUE_SET));
                    }
                }
                FieldPyO3Option::Name(name) => {
                    if options.name.replace(name).is_some() {
                        return Err(syn::Error::new(options.name.span(), UNIQUE_NAME));
                    }
                }
            }
        }

        Ok(options)
    }
}

View on GitHub (pinned to ac9b6899d3)

Solutions

  1. Remove the duplicate `get` keyword so it appears at most once per field
  2. If two attribute lists are being merged, de-duplicate options before passing them to the macro
  3. Check macro-generated or wrapper code that may inject `get` automatically

Example fix

// before
#[pyclass]
struct Point {
    #[pyo3(get, get)]
    x: i32,
}
// after
#[pyclass]
struct Point {
    #[pyo3(get)]
    x: i32,
}
Defensive patterns

Strategy: validation

Validate before calling

// Compile-time check: ensure each pyclass field lists each option once
fn validate_unique_options(attrs: &[&str]) -> Result<(), String> {
    let mut seen = std::collections::HashSet::new();
    for a in attrs {
        if !seen.insert(a) { return Err(format!("duplicate option: {}", a)); }
    }
    Ok(())
}

Prevention

When it happens

Trigger: Compiling Rust code where a #[pyclass] field (or getter) declares `#[pyo3(get)]` (or `get` inside the macro args) twice, e.g. `#[pyo3(get, get)]` or a duplicated option spread across merged attribute lists.

Common situations: Copy-paste of attribute lines when adding getters/refactor merges that concatenate two attribute sets; macro-generated code that blindly appends `get` to an existing attribute list.

Related errors


AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05). Data as JSON: /api/errors/813060d8b94691ef. Report an issue: GitHub.