Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proc macro documentation #1585

Merged
merged 6 commits into from
May 3, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 140 additions & 2 deletions pyo3-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ use pyo3_macros_backend::{
use quote::quote;
use syn::parse_macro_input;

/// Internally, this proc macro create a new c function called `PyInit_{my_module}`
/// that then calls the init function you provided
/// A proc macro used to implement Python modules.
///
/// For more on creating Python modules
/// see the [module section of the guide](https://pyo3.rs/main/module.html).
#[proc_macro_attribute]
pub fn pymodule(attr: TokenStream, input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemFn);
Expand Down Expand Up @@ -43,6 +45,16 @@ pub fn pymodule(attr: TokenStream, input: TokenStream) -> TokenStream {
.into()
}

/// A proc macro used to implement Python's [dunder methods][1].
///
/// This atribute is required on blocks implementing [`PyObjectProtocol`][2],
/// [`PyNumberProtocol`][3], [`PyGCProtocol`][4] and [`PyIterProtocol`][5].
///
/// [1]: https://docs.python.org/3/reference/datamodel.html#special-method-names
/// [2]: ../class/basic/trait.PyObjectProtocol.html
/// [3]: ../class/number/trait.PyNumberProtocol.html
/// [4]: ../class/gc/trait.PyGCProtocol.html
/// [5]: ../class/iter/trait.PyIterProtocol.html
#[proc_macro_attribute]
pub fn pyproto(_: TokenStream, input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemImpl);
Expand All @@ -55,26 +67,152 @@ pub fn pyproto(_: TokenStream, input: TokenStream) -> TokenStream {
.into()
}

/// A proc macro used to expose Rust structs as Python objects.
///
/// `#[pyclass]` accepts the following [parameters][2]:
///
/// | Parameter | Description |
/// | :- | :- |
/// | `name = "python_name"` | Sets the name that Python sees this class as. Defaults to the name of the Rust struct. |
/// | `freelist = N` | Implements a [free list][10] of size N. This can improve performance for types that are often created and deleted in a row. Profile your code to see whether `freelist` is right for you. |
mejrs marked this conversation as resolved.
Show resolved Hide resolved
/// | `gc` | Participate in Python's [garbage collection][5]. Required if your type contains references to other Python objects. If you don't (or incorrectly) implement this, contained Python objects may be hidden from Python's garbage collector and you may leak memory. Note that leaking memory, while undesirable, [is safe behavior][7].|
/// | `weakref` | Allows this class to be [weakly referenceable][6]. |
/// | `extends = BaseType` | Use a custom baseclass. Defaults to [`PyAny`][4] |
/// | `subclass` | Allows Python classes to inherit from this class. |
mejrs marked this conversation as resolved.
Show resolved Hide resolved
/// | `unsendable`<!-- hack to stop the column from wrapping -->&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; | Required if your struct is not [`Send`][3]. Rather than using `unsendable`, consider implementing your struct in a threadsafe way by e.g. substituting [`Rc`][8] with [`Arc`][9]. By using `unsendable`, your class will panic when accessed by another thread.|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain how this hack works? 🧠

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're whitespaces that do not permit wrapping.

If something tries minimizing the table column, "ham spam eggs" can wrap around their spaces, but "ham spam eggs" (ham&#160;spam&#160;eggs) will not.

Copy link
Member

@davidhewitt davidhewitt May 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, understood.

I think it's slightly more intuitive (although admittedly more ugly) to do something like

<span style="white-space: pre">`name = "python_name"`</span>

on the rows that we don't want to wrap, but I'll do that in a separate PR.

/// | `module = "module_name"` | Python code will see the class as being defined in this module. Defaults to `builtins`. |
///
/// For more on creating Python classes,
/// see the [class section of the guide][1].
///
/// [1]: https://pyo3.rs/main/class.html
/// [2]: https://pyo3.rs/main/class.html#customizing-the-class
/// [3]: std::marker::Send
/// [4]: ../prelude/struct.PyAny.html
/// [5]: https://pyo3.rs/main/class/protocols.html#garbage-collector-integration
/// [6]: https://docs.python.org/3/library/weakref.html
/// [7]: https://doc.rust-lang.org/nomicon/leaking.html
/// [8]: std::rc::Rc
/// [9]: std::sync::Arc
/// [10]: https://en.wikipedia.org/wiki/Free_list
#[proc_macro_attribute]
pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream {
pyclass_impl(attr, input, PyClassMethodsType::Specialization)
}

/// A proc macro used to expose Rust structs as Python objects.
davidhewitt marked this conversation as resolved.
Show resolved Hide resolved
///
/// `#[pyclass]` accepts the following [parameters][2]:
///
/// | Parameter | Description |
/// | :- | :- |
/// | `name = "python_name"` | Sets the name that Python sees this class as. Defaults to the name of the Rust struct. |
/// | `freelist = N` | Implements a [free list][10] of size N. This can improve performance for types that are often created and deleted in a row. Profile your code to see whether `freelist` is right for you. |
/// | `gc` | Participate in Python's [garbage collection][5]. Required if your type contains references to other Python objects. If you don't (or incorrectly) implement this, contained Python objects may be hidden from Python's garbage collector and you may leak memory. Note that leaking memory, while undesirable, [is safe behavior][7].|
/// | `weakref` | Allows this class to be [weakly referenceable][6]. |
/// | `extends = BaseType` | Use a custom baseclass. Defaults to [`PyAny`][4] |
/// | `subclass` | Allows Python classes to inherit from this class. |
/// | `unsendable`<!-- hack to stop the column from wrapping -->&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; | Required if your struct is not [`Send`][3]. Rather than using `unsendable`, consider implementing your struct in a threadsafe way by e.g. substituting [`Rc`][8] with [`Arc`][9]. By using `unsendable`, your class will panic when accessed by another thread.|
/// | `module = "module_name"` | Python code will see the class as being defined in this module. Defaults to `builtins`. |
///
/// For more on creating Python classes,
/// see the [class section of the guide][1].
///
/// [1]: https://pyo3.rs/main/class.html
/// [2]: https://pyo3.rs/main/class.html#customizing-the-class
/// [3]: std::marker::Send
/// [4]: ../prelude/struct.PyAny.html
/// [5]: https://pyo3.rs/main/class/protocols.html#garbage-collector-integration
/// [6]: https://docs.python.org/3/library/weakref.html
/// [7]: https://doc.rust-lang.org/nomicon/leaking.html
/// [8]: std::rc::Rc
/// [9]: std::sync::Arc
/// [10]: https://en.wikipedia.org/wiki/Free_list
#[proc_macro_attribute]
pub fn pyclass_with_inventory(attr: TokenStream, input: TokenStream) -> TokenStream {
pyclass_impl(attr, input, PyClassMethodsType::Inventory)
}

/// A proc macro used to expose methods to Python.
///
/// Methods within a `#[pymethods]` block can be annotated with the following:
///
/// | Annotation | Description |
/// | :- | :- |
/// | [`#[new]`][4] | Defines the class constructor, like Python's `__new__` method. |
/// | [`#[getter]`][5] and [`#[setter]`][5] | These define getters and setters, similar to Python's `@property` decorator. This is useful for getters/setters that require computation or side effects; if that is not the case consider using [`#[pyo3(get, set)]`][11] on the struct's field(s).|
/// | [`#[staticmethod]`][6]| Defines the method as a staticmethod, like Python's `@staticmethod` decorator.|
/// | [`#[classmethod]`][7] | Defines the method as a classmethod, like Python's `@classmethod` decorator.|
/// | [`#[call]`][8] | Allows Python code to call a class instance as a function, like Python's `__call__` method. |
/// | [`#[classattr]`][9] | Defines a class variable. |
/// | [`#[args]`][10] | Define a method's default arguments and allows the function to receive `*args` and `**kwargs`. |
mejrs marked this conversation as resolved.
Show resolved Hide resolved
///
/// For more on creating class methods,
/// see the [class section of the guide][1].
///
/// If the [`multiple-pymethods`][2] feature is enabled, it is possible to implement
/// multiple `#[pymethods]` blocks for a single `#[pyclass]`.
/// This will add a transitive dependency on the [`inventory`][3] crate.
///
/// [1]: https://pyo3.rs/main/class.html#instance-methods
/// [2]: https://pyo3.rs/main/features.html#multiple-pymethods
/// [3]: https://docs.rs/inventory/
/// [4]: https://pyo3.rs/main/class.html#constructor
/// [5]: https://pyo3.rs/main/class.html#object-properties-using-getter-and-setter
/// [6]: https://pyo3.rs/main/class.html#static-methods
/// [7]: https://pyo3.rs/main/class.html#class-methods
/// [8]: https://pyo3.rs/main/class.html#callable-objects
/// [9]: https://pyo3.rs/main/class.html#class-attributes
/// [10]: https://pyo3.rs/main/class.html#method-arguments
/// [11]: https://pyo3.rs/main/class.html#object-properties-using-pyo3get-set
#[proc_macro_attribute]
pub fn pymethods(_: TokenStream, input: TokenStream) -> TokenStream {
pymethods_impl(input, PyClassMethodsType::Specialization)
}

/// A proc macro used to expose methods to Python.
///
/// Methods within a `#[pymethods]` block can be annotated with the following:
///
/// | Annotation | Description |
/// | :- | :- |
/// | [`#[new]`][4] | Defines the class constructor, like Python's `__new__` method. |
/// | [`#[getter]`][5] and [`#[setter]`][5] | These define getters and setters, similar to Python's `@property` decorator. This is useful for getters/setters that require computation or side effects; if that is not the case consider using [`#[pyo3(get, set)]`][11] on the struct's field(s).|
/// | [`#[staticmethod]`][6]| Defines the method as a staticmethod, like Python's `@staticmethod` decorator.|
/// | [`#[classmethod]`][7] | Defines the method as a classmethod, like Python's `@classmethod` decorator.|
/// | [`#[call]`][8] | Allows Python code to call a class instance as a function, like Python's `__call__` method. |
/// | [`#[classattr]`][9] | Defines a class variable. |
/// | [`#[args]`][10] | Define a method's default arguments and allows the function to receive `*args` and `**kwargs`. |
///
/// For more on creating class methods,
/// see the [class section of the guide][1].
///
/// If the [`multiple-pymethods`][2] feature is enabled, it is possible to implement
/// multiple `#[pymethods]` blocks for a single `#[pyclass]`.
/// This will add a transitive dependency on the [`inventory`][3] crate.
///
/// [1]: https://pyo3.rs/main/class.html#instance-methods
/// [2]: https://pyo3.rs/main/features.html#multiple-pymethods
/// [3]: https://docs.rs/inventory/
/// [4]: https://pyo3.rs/main/class.html#constructor
/// [5]: https://pyo3.rs/main/class.html#object-properties-using-getter-and-setter
/// [6]: https://pyo3.rs/main/class.html#static-methods
/// [7]: https://pyo3.rs/main/class.html#class-methods
/// [8]: https://pyo3.rs/main/class.html#callable-objects
/// [9]: https://pyo3.rs/main/class.html#class-attributes
/// [10]: https://pyo3.rs/main/class.html#method-arguments
/// [11]: https://pyo3.rs/main/class.html#object-properties-using-pyo3get-set
#[proc_macro_attribute]
pub fn pymethods_with_inventory(_: TokenStream, input: TokenStream) -> TokenStream {
pymethods_impl(input, PyClassMethodsType::Inventory)
}

/// A proc macro used to expose Rust functions to Python.
///
/// For more on exposing functions,
/// see the [function section of the guide][1].
///
/// [1]: https://pyo3.rs/main/function.html
#[proc_macro_attribute]
pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemFn);
Expand Down
6 changes: 4 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,13 @@ pub mod types;
#[cfg(feature = "serde")]
pub mod serde;

/// The proc macros, which are also part of the prelude.
/// The proc macros, all of which are part of the prelude.
///
/// Import these with `use pyo3::prelude::*;`
#[cfg(feature = "macros")]
pub mod proc_macro {
pub use pyo3_macros::pymodule;
/// The proc macro attributes

pub use pyo3_macros::{pyfunction, pyproto};

#[cfg(not(feature = "multiple-pymethods"))]
Expand Down