Repository: matplotlib/matplotlib

Issue:
[Bug]: Setting font.sans-serif is impossible by the intended way using matplotlib.rc because it contains a hyphen.
### Bug summary

The functions `matplotlib.rc` and `matplotlib.pyplot.rc` process settings given as named parameters. Named parameters can't contain hyphens; therefore, it is impossible to set `font.sans-serif` so because it contains a hyphen.

It is possible to set the parameter by different means, but this seems like the main way, so I consider that it does not work a significant shortcoming. How else this parameter can be set:
- `matplotlib.rcParams["font.sans-serif"] = "Fira Sans"`
- `matplotlib.rc("font", **{"sans-serif": "Fira Sans"})`


### Code for reproduction

```Python
from matplotlib import rc
rc("font", sans-serif="Fira Sans")
```

### Actual outcome

```
>>> from matplotlib import rc
>>> rc("font", sans-serif="Fira Sans")
  File "<python-input-1>", line 1
    rc("font", sans-serif="Fira Sans")
               ^^^^^^^^^^^
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
```

### Expected outcome

I think that that the parameter is named `font.sans-serif` is a mistake, and it should be named something like `font.sans_serif` with an underscore. It may be too late to change that, but an alias would work. `rc` already has aliases like `lw` → `linewidth`, `ls` → `linestyle`, `c` → `color`. I expect that there would be an alias like `sans` → `sans-serif` so I could use `rc("font", sans="Fira Sans")`.

### Additional information

_No response_

### Operating system

_No response_

### Matplotlib Version

3.10.1

### Matplotlib Backend

_No response_

### Python version

Python 3.13.3

### Jupyter version

_No response_

### Installation

None


Discussion:
I agree that adding an alias of `'sans': 'sans-serif'` is a reasonable feature to add (as is maybe `'sans_serif'`).  Any interest in working on that @matj1 ?

Changing the name of the rcparam is not something I think is should be on the table.  We have carried this name for 21+ years.  It is not worth the cost of thrashing all of the users who are setting it one of the other ways to ask them to change their code.  

Unfortunately given that this is a syntax error there is no way for us to inject a better error.

----

It appears we gained the `font.sans-serif` rcparam in 41f1c494852915c373ca3df7b0835edf3ce27af3 in April 2004 (it can now drink in the US) where as `def rf()` first shows up in e34a333d00814124d3e19d462b9d78ac35e7a49a in September 2004 (however due to a glitch in the migration from svn -> git that commit has a lot of code just "appear" so it is not definitive that the key existed before the function).

Personally I reach for `mpl.rcParams[...] = ...` first and view `rc` as a helper (as it a wrapper around the calling setitem).



Patch:
diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py
index ac71070e690a..9abc6c5a84dd 100644
--- a/lib/matplotlib/__init__.py
+++ b/lib/matplotlib/__init__.py
@@ -995,9 +995,9 @@ def rc(group, **kwargs):
 
     The following aliases are available to save typing for interactive users:
 
-    =====   =================
+    ======  =================
     Alias   Property
-    =====   =================
+    ======  =================
     'lw'    'linewidth'
     'ls'    'linestyle'
     'c'     'color'
@@ -1005,7 +1005,8 @@ def rc(group, **kwargs):
     'ec'    'edgecolor'
     'mew'   'markeredgewidth'
     'aa'    'antialiased'
-    =====   =================
+    'sans'  'sans-serif'
+    ======  =================
 
     Thus you could abbreviate the above call as::
 
@@ -1039,6 +1040,7 @@ def rc(group, **kwargs):
         'ec':  'edgecolor',
         'mew': 'markeredgewidth',
         'aa':  'antialiased',
+        'sans': 'sans-serif',
         }
 
     if isinstance(group, str):


Test patch:
diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py
index 1bc148a83a7e..2235f98b720f 100644
--- a/lib/matplotlib/tests/test_rcparams.py
+++ b/lib/matplotlib/tests/test_rcparams.py
@@ -654,3 +654,21 @@ def test_rcparams_path_sketch_from_file(tmp_path, value):
     rc_path.write_text(f"path.sketch: {value}")
     with mpl.rc_context(fname=rc_path):
         assert mpl.rcParams["path.sketch"] == (1, 2, 3)
+
+
+@pytest.mark.parametrize('group, option, alias, value', [
+    ('lines',  'linewidth',        'lw', 3),
+    ('lines',  'linestyle',        'ls', 'dashed'),
+    ('lines',  'color',             'c', 'white'),
+    ('axes',   'facecolor',        'fc', 'black'),
+    ('figure', 'edgecolor',        'ec', 'magenta'),
+    ('lines',  'markeredgewidth', 'mew', 1.5),
+    ('patch',  'antialiased',      'aa', False),
+    ('font',   'sans-serif',     'sans', ["Verdana"])
+])
+def test_rc_aliases(group, option, alias, value):
+    rc_kwargs = {alias: value,}
+    mpl.rc(group, **rc_kwargs)
+
+    rcParams_key = f"{group}.{option}"
+    assert mpl.rcParams[rcParams_key] == value