diff --git a/guide/src/function.md b/guide/src/function.md
index b0d6c0b0e98..fe74e5df457 100644
--- a/guide/src/function.md
+++ b/guide/src/function.md
@@ -73,39 +73,7 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python
- `#[pyo3(text_signature = "...")]`
- Sets the function signature visible in Python tooling (such as via [`inspect.signature`]).
-
- The example below creates a function `add` which has a signature describing two positional-only
- arguments `a` and `b`.
-
- ```rust
- use pyo3::prelude::*;
-
- /// This function adds two unsigned 64-bit integers.
- #[pyfunction]
- #[pyo3(text_signature = "(a, b, /)")]
- fn add(a: u64, b: u64) -> u64 {
- a + b
- }
- #
- # fn main() -> PyResult<()> {
- # Python::with_gil(|py| {
- # let fun = pyo3::wrap_pyfunction!(add, py)?;
- #
- # let doc: String = fun.getattr("__doc__")?.extract()?;
- # assert_eq!(doc, "This function adds two unsigned 64-bit integers.");
- #
- # let inspect = PyModule::import(py, "inspect")?.getattr("signature")?;
- # let sig: String = inspect
- # .call1((fun,))?
- # .call_method0("__str__")?
- # .extract()?;
- # assert_eq!(sig, "(a, b, /)");
- #
- # Ok(())
- # })
- # }
- ```
+ Overrides the PyO3-generated function signature visible in Python tooling (such as via [`inspect.signature`]). See the [corresponding topic in the Function Signatures subchapter](./function/signature.md#making-the-function-signature-available-to-python).
- `#[pyo3(pass_module)]`
@@ -161,47 +129,6 @@ The `#[pyo3]` attribute can be used on individual arguments to modify properties
## Advanced function patterns
-### Making the function signature available to Python (old method)
-
-Alternatively, simply make sure the first line of your docstring is
-formatted like in the following example. Please note that the newline after the
-`--` is mandatory. The `/` signifies the end of positional-only arguments.
-
-`#[pyo3(text_signature)]` should be preferred, since it will override automatically
-generated signatures when those are added in a future version of PyO3.
-
-```rust
-# #![allow(dead_code)]
-use pyo3::prelude::*;
-
-/// add(a, b, /)
-/// --
-///
-/// This function adds two unsigned 64-bit integers.
-#[pyfunction]
-fn add(a: u64, b: u64) -> u64 {
- a + b
-}
-
-// a function with a signature but without docs. Both blank lines after the `--` are mandatory.
-
-/// sub(a, b, /)
-/// --
-#[pyfunction]
-fn sub(a: u64, b: u64) -> u64 {
- a - b
-}
-```
-
-When annotated like this, signatures are also correctly displayed in IPython.
-
-```text
->>> pyo3_test.add?
-Signature: pyo3_test.add(a, b, /)
-Docstring: This function adds two unsigned 64-bit integers.
-Type: builtin_function_or_method
-```
-
### Calling Python functions in Rust
You can pass Python `def`'d functions and built-in functions to Rust functions [`PyFunction`]
diff --git a/guide/src/function/signature.md b/guide/src/function/signature.md
index 39e733089d4..cf4f91bed55 100644
--- a/guide/src/function/signature.md
+++ b/guide/src/function/signature.md
@@ -188,3 +188,130 @@ impl MyClass {
}
}
```
+
+## Making the function signature available to Python
+
+The function signature is exposed to Python via the `__text_signature__` attribute. PyO3 automatically generates this for every `#[pyfunction]` and all `#[pymethods]`.
+
+This automatic generation has some limitations, which may be improved in the future:
+- It will not include the value of default arguments, replacing them all with `...`. (`.pyi` type stub files commonly also use `...` for all default arguments in the same way.)
+- Nothing is generated for the `#[new]` method of a `#[pyclass]`.
+
+In cases where the automatically-generated signature needs adjusting, it can [be overridden](#overriding-the-generated-signature) using the `#[pyo3(text_signature)]` option.)
+
+The example below creates a function `add` which accepts two positional-only arguments `a` and `b`, where `b` has a default value of zero.
+
+```rust
+use pyo3::prelude::*;
+
+/// This function adds two unsigned 64-bit integers.
+#[pyfunction]
+#[pyo3(signature = (a, b=0, /))]
+fn add(a: u64, b: u64) -> u64 {
+ a + b
+}
+#
+# fn main() -> PyResult<()> {
+# Python::with_gil(|py| {
+# let fun = pyo3::wrap_pyfunction!(add, py)?;
+#
+# let doc: String = fun.getattr("__doc__")?.extract()?;
+# assert_eq!(doc, "This function adds two unsigned 64-bit integers.");
+#
+# let inspect = PyModule::import(py, "inspect")?.getattr("signature")?;
+# let sig: String = inspect
+# .call1((fun,))?
+# .call_method0("__str__")?
+# .extract()?;
+# assert_eq!(sig, "(a, b, /)");
+#
+# Ok(())
+# })
+# }
+```
+
+The following IPython output demonstrates how this generated signature will be seen from Python tooling:
+
+```text
+>>> pyo3_test.add.__text_signature__
+'(a, b=..., /)'
+>>> pyo3_test.add?
+Signature: pyo3_test.add(a, b=Ellipsis, /)
+Docstring: This function adds two unsigned 64-bit integers.
+Type: builtin_function_or_method
+```
+
+### Overriding the generated signature
+
+The `#[pyo3(text_signature = "()")]` attribute can be used to override the default generated signature.
+
+In the snippet below, the text signature attribute is used to include the default value of `0` for the argument `b`, instead of the automatically-generated default value of `...`:
+
+```rust
+use pyo3::prelude::*;
+
+/// This function adds two unsigned 64-bit integers.
+#[pyfunction]
+#[pyo3(signature = (a, b=0, /), text_signature = "(a, b=0, /)")]
+fn add(a: u64, b: u64) -> u64 {
+ a + b
+}
+#
+# fn main() -> PyResult<()> {
+# Python::with_gil(|py| {
+# let fun = pyo3::wrap_pyfunction!(add, py)?;
+#
+# let doc: String = fun.getattr("__doc__")?.extract()?;
+# assert_eq!(doc, "This function adds two unsigned 64-bit integers.");
+# assert!(fun.getattr("__text_signature__")?.is_none());
+#
+# Ok(())
+# })
+# }
+```
+
+PyO3 will include the contents of the annotation unmodified as the `__text_signature`. Below shows how IPython will now present this (see the default value of 0 for b):
+
+```text
+>>> pyo3_test.add.__text_signature__
+'(a, b=0, /)'
+>>> pyo3_test.add?
+Signature: pyo3_test.add(a, b=0, /)
+Docstring: This function adds two unsigned 64-bit integers.
+Type: builtin_function_or_method
+```
+
+If no signature is wanted at all, `#[pyo3(text_signature = false)]` will disable the built-in signature. The snippet below demonstrates use of this:
+
+```rust
+use pyo3::prelude::*;
+
+/// This function adds two unsigned 64-bit integers.
+#[pyfunction]
+#[pyo3(signature = (a, b=0, /), text_signature = false)]
+fn add(a: u64, b: u64) -> u64 {
+ a + b
+}
+#
+# fn main() -> PyResult<()> {
+# Python::with_gil(|py| {
+# let fun = pyo3::wrap_pyfunction!(add, py)?;
+#
+# let doc: String = fun.getattr("__doc__")?.extract()?;
+# assert_eq!(doc, "This function adds two unsigned 64-bit integers.");
+# assert!(fun.getattr("__text_signature__")?.is_none());
+#
+# Ok(())
+# })
+# }
+```
+
+Now the function's `__text_signature__` will be set to `None`, and IPython will not display any signature in the help:
+
+```text
+>>> pyo3_test.add.__text_signature__ == None
+True
+>>> pyo3_test.add?
+Docstring: This function adds two unsigned 64-bit integers.
+Type: builtin_function_or_method
+```