Repository: matplotlib/matplotlib
Pull request: Update example and docstring to encourage the use of functools.partial in FuncAnimation
## PR Summary

Fixes #20326
Closes #20358
Closes #24139

Merged contents from #20358 and #24139 with comments. Probably have missed something...

## PR Checklist

<!-- Please mark any checkboxes that do not apply to this PR as [N/A]. -->
**Tests and Styling**
- [ ] Has pytest style unit tests (and `pytest` passes).
- [ ] Is [Flake 8](https://flake8.pycqa.org/en/latest/) compliant (install `flake8-docstrings` and run `flake8 --docstring-convention=all`).

**Documentation**
- [ ] New features are documented, with examples if plot related.
- [ ] New features have an entry in `doc/users/next_whats_new/` (follow instructions in README.rst there).
- [ ] API changes documented in `doc/api/next_api_changes/` (follow instructions in README.rst there).
- [ ] Documentation is sphinx and numpydoc compliant (the docs should [build](https://matplotlib.org/devel/documenting_mpl.html#building-the-docs) without error).

<!--
Thank you so much for your PR!  To help us review your contribution, please
consider the following points:

- A development guide is available at https://matplotlib.org/devdocs/devel/index.html.

- Help with git and github is available at
  https://matplotlib.org/devel/gitwash/development_workflow.html.

- Do not create the PR out of main, but out of a separate branch.

- The PR title should summarize the changes, for example "Raise ValueError on
  non-numeric input to set_xlim".  Avoid non-descriptive titles such as
  "Addresses issue #8576".

- The summary should provide at least 1-2 sentences describing the pull request
  in detail (Why is this change required?  What problem does it solve?) and
  link to any relevant issues.

- If you are contributing fixes to docstrings, please pay attention to
  http://matplotlib.org/devel/documenting_mpl.html#formatting.  In particular,
  note the difference between using single backquotes, double backquotes, and
  asterisks in the markup.

We understand that PRs can sometimes be overwhelming, especially as the
reviews start coming in.  Please let us know if the reviews are unclear or
the recommended next step seems overly demanding, if you would like help in
addressing a reviewer's comments, or if you have been waiting too long to hear
back on your PR.
-->


Problem statement:
FuncAnimation Named Arguments
### Problem

I would like to use named arguments in FuncAnimation.

Currently:
```
ani = matplotlib.animation.FuncAnimation(
  fig, update, frames=len(raw_history),
  interval=500, repeat=True, repeat_delay=1,
  fargs=(raw_history, action_history, style)
)
```
This would be slightly cumbersome when I change the order / type of arguments my function accepts. I could use `functools` to attach named parameters to the function `update`, but it would be nice if `FuncAnimation` can support this feature.

### Proposed Solution
```
ani = matplotlib.animation.FuncAnimation(
  fig, update, frames=len(raw_history),
  interval=500, repeat=True, repeat_delay=1,
  fkwargs={'raw_history': raw_history, 'action_history': action_history, 'style': style}
)
```
We keep the `fargs` parameter, but we also add an `fkwargs` for keyword parameters.

Hints from issue discussion:
I don't particularly like `fargs` to begin with (I prefer functools.partial), but given that it's here, adding fkwargs seems reasonable.
So why don‘t we discourage fargs and advertise partial instead, including the note that you can use kwargs there?
That works for me too.
This would just require adding fkwargs as keyword arguments right? I also noticed that `fargs` doesn't really have any sort of test written for it, can I give it a go?
Um, no. [functools.partial](https://docs.python.org/3/library/functools.html#functools.partial) lets you create new functions with some parameters bound.

This works for both positional
```
my_update = partial(update, raw_history, action_history, style)
```
and keyword argumetns
```
my_update = partial(update, raw_history=raw_history, action_history=action_history, style=style)
```
or any combination. This is a builtin capability of python and does not require any changes within matplotlib (except maybe that we should encourage this use in the `FuncAnimation` docs).

You can pass `my_update` to `FuncAnimation`, or use it inlined:
```
from functools import partial

ani = matplotlib.animation.FuncAnimation(
  fig,
  partial(update, raw_history=raw_history, action_history=action_history, style=style),
  frames=len(raw_history), interval=500, repeat=True, repeat_delay=1,
)
```
In defense of saying "use partial" rather than adding support for `fkwargs` is cite https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.call_later

> The optional positional args will be passed to the callback when it is called. If you want the callback to be called with keyword arguments use functools.partial().
@jeffreypaul15 What would be super helpful would be a note in the `FuncAnitamation` docstring pointing users at `functools.partial` and maybe updating / tweaking an existing example or adding a new example that make use of `functools.partial`.

Commit message: Apply suggestions from code review

Co-authored-by: Thomas A Caswell <tcaswell@gmail.com>

Patch to review:
diff --git a/doc/api/animation_api.rst b/doc/api/animation_api.rst
index 132590456763..13b0acf026c2 100644
--- a/doc/api/animation_api.rst
+++ b/doc/api/animation_api.rst
@@ -133,8 +133,37 @@ artist at a global scope and let Python sort things out.  For example ::
                        init_func=init, blit=True)
    plt.show()
 
-The second method is to use `functools.partial` to 'bind' artists to
-function.  A third method is to use closures to build up the required
+The second method is to use `functools.partial` to pass arguments to the
+function. ::
+
+   import numpy as np
+   import matplotlib.pyplot as plt
+   from matplotlib.animation import FuncAnimation
+   from functools import partial
+
+   fig, ax = plt.subplots()
+   line1, = plt.plot([], [], 'ro')
+
+   def init():
+       ax.set_xlim(0, 2*np.pi)
+       ax.set_ylim(-1, 1)
+       return ln,
+
+   def update(frame, ln, x, y):
+       x.append(frame)
+       y.append(np.sin(frame))
+       ln.set_data(xdata, ydata)
+       return ln,
+
+   xdata, ydata = [], []
+   ani = FuncAnimation(
+       fig, partial(update, ln=line1, x=xdata, y=ydata),
+       frames=np.linspace(0, 2 * np.pi, 128),
+       init_func=init, blit=True)
+
+   plt.show()
+
+A third method is to use closures to build up the required
 artists and functions.  A fourth method is to create a class.
 
 Examples
diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py
index 428e00904277..c9084ee7a0a8 100644
--- a/lib/matplotlib/animation.py
+++ b/lib/matplotlib/animation.py
@@ -1520,12 +1520,24 @@ class FuncAnimation(TimedAnimation):
     func : callable
         The function to call at each frame.  The first argument will
         be the next value in *frames*.   Any additional positional
-        arguments can be supplied via the *fargs* parameter.
+        arguments can be supplied using `functools.partial` or via the *fargs*
+        parameter.
 
         The required signature is::
 
             def func(frame, *fargs) -> iterable_of_artists
 
+        It is often more convenient to provide the arguments using
+        `functools.partial`. In this way it is also possible to pass keyword
+        arguments. To pass a function with both positional and keyword
+        arguments, set all arguments as keyword arguments, just leaving the
+        *frame* argument unset::
+
+            def func(frame, art, *, y=None):
+                ...
+
+            ani = FuncAnimation(fig, partial(func, art=ln, y='foo'))
+
         If ``blit == True``, *func* must return an iterable of all artists
         that were modified or created. This information is used by the blitting
         algorithm to determine which parts of the figure have to be updated.
@@ -1564,7 +1576,8 @@ def init_func() -> iterable_of_artists
         value is unused if ``blit == False`` and may be omitted in that case.
 
     fargs : tuple or None, optional
-        Additional arguments to pass to each call to *func*.
+        Additional arguments to pass to each call to *func*. Note: the use of
+        `functools.partial` is preferred over *fargs*. See *func* for details.
 
     save_count : int, default: 100
         Fallback for the number of values from *frames* to cache. This is


Code review comments:
diff_hunk: @@ -133,8 +133,37 @@ artist at a global scope and let Python sort things out.  For example ::
                        init_func=init, blit=True)
    plt.show()
 
-The second method is to use `functools.partial` to 'bind' artists to
-function.  A third method is to use closures to build up the required
+The second method is to use `functools.partial` to pass arguments to the
+function. ::
+
+   import numpy as np
+   import matplotlib.pyplot as plt
+   from matplotlib.animation import FuncAnimation
+   from functools import partial
+
+   fig, ax = plt.subplots()
+   line1, = plt.plot([], [], 'ro')
path: doc/api/animation_api.rst
text: @user1:
```suggestion
   line1, = ax.plot([], [], 'ro')
```

diff_hunk: @@ -133,8 +133,37 @@ artist at a global scope and let Python sort things out.  For example ::
                        init_func=init, blit=True)
    plt.show()
 
-The second method is to use `functools.partial` to 'bind' artists to
-function.  A third method is to use closures to build up the required
+The second method is to use `functools.partial` to pass arguments to the
+function. ::
+
+   import numpy as np
+   import matplotlib.pyplot as plt
+   from matplotlib.animation import FuncAnimation
+   from functools import partial
+
+   fig, ax = plt.subplots()
+   line1, = ax.plot([], [], 'ro')
+
+   def init():
+       ax.set_xlim(0, 2*np.pi)
+       ax.set_ylim(-1, 1)
+       return line1,
+
+   def update(frame, ln, x, y):
+       x.append(frame)
+       y.append(np.sin(frame))
+       ln.set_data(x, y)
+       return ln,
+
+   xdata, ydata = [], []
path: doc/api/animation_api.rst
text: @user1:
```suggestion
```

These variables are now unused, so this line can be omitted