Repository: pandas-dev/pandas
Pull request: ENH: Added dict support for `pd.set_option`
- [x] closes #61093
- [x] Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [x] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [x] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [x] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.


Problem statement:
ENH: Support dict to `pd.set_option` for cleaner code
### Feature Type

- [x] Adding new functionality to pandas

- [ ] Changing existing functionality in pandas

- [ ] Removing existing functionality in pandas


### Problem Description

I wish that I could use dict for `pd.set_option`, just like other packages typically accept dict when there are lots of option. Current pandas style follow what old programming language do, such as MATLAB.

### Feature Description

```python
import pandas as pd

options = {
    'display.precision': 2,
    'display.max_columns': 100,
    'styler.format.precision': 2,
}

# like this
pd.set_option(**options)

# or like this:
pd.set_option(options)
```

### Alternative Solutions

Current implementations is:

```python
pd.set_option('display.precision', 2)
pd.set_option('display.max_columns', 100)
pd.set_option('styler.format.precision', 2)

# or

options = {
    'display.precision': 2,
    'display.max_columns': 100,
    'styler.format.precision': 2,
}

for key, value in options.items():
    pd.set_option(key, value)
```

### Additional Context

Because if I write it like this:

```python
pd.set_option(
    'display.precision', 2,
    'display.max_columns', 100,
    'styler.format.precision', 2,
)
```

An auto formatter like `ruff` will make it into:

```python
pd.set_option(
    'display.precision',
    2,
    'display.max_columns',
    100,
    'styler.format.precision',
    2,
)
```

Hints from issue discussion:
take
working on this

aah i missed it maybe 

if i have solution can i propose it @arthurlw 
Oh sorry @himanshumahajan138 I just saw your message. If you have a solution too we can discuss and compare!
Hi @arthurlw  and @himanshumahajan138. Thanks for making the PR.

I think https://github.com/pandas-dev/pandas/pull/61152 is cleaner, but has a higher risk of bug (if exist). Also, I don't think we need to support the `**` approach as it is not based on args(?).
@yasirroni actually i am struggling with the Tight code formatting fixtures 😅

Fixing the failing doc tests and type tests after that we can have discussions on approach suitability
cc @pandas-dev/pandas-core for any thoughts.
I think it's a good idea to support a `dict` as an argument to `set_option()` although maybe we should introduce `set_options()` to support a dict so there is a difference between setting a single option versus multiple options in a single call?
I think it's a good addition,  the same time makes things a bit complicated. @Dr-Irv makes the API clearer in my opinion, but I would prefer to not have to add and maintain one more method being almoat identical to the existing.

An idea could be since we are addimg a way to pass multiple options, to allow only one option and remove `pd.set_option('s1': 1, 's2': 2)` in favour of pasaing a dictionary instead. Not perfect, but feels like it'll male both the API and the code slightly aimpler.

In any case, I'm ok with any option.
Good wishes Members, 

Actually my PR consists both approaches first one is only for `pd.set_option(options)` and second is for both of the cases `pd.set_option(options)` & `pd.set_option(**options)`

PR : #61152 
For my PR (#61151), I implemented the first approach only, which is `pd.set_options(options)`, where `options` is a dictionary. 

I think this would be simpler, but I am okay with either option.

Commit message: updated test_set_option_multiple to catch deprecation warning

Patch to review:
diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst
index b4aa6447c0a1b..c312eff4ecc2b 100644
--- a/doc/source/whatsnew/v3.0.0.rst
+++ b/doc/source/whatsnew/v3.0.0.rst
@@ -52,6 +52,7 @@ Other enhancements
 - :class:`Rolling` and :class:`Expanding` now support ``pipe`` method (:issue:`57076`)
 - :class:`Series` now supports the Arrow PyCapsule Interface for export (:issue:`59518`)
 - :func:`DataFrame.to_excel` argument ``merge_cells`` now accepts a value of ``"columns"`` to only merge :class:`MultiIndex` column header header cells (:issue:`35384`)
+- :func:`set_option` now accepts a dictionary of options, simplifying configuration of multiple settings at once (:issue:`61093`)
 - :meth:`DataFrame.corrwith` now accepts ``min_periods`` as optional arguments, as in :meth:`DataFrame.corr` and :meth:`Series.corr` (:issue:`9490`)
 - :meth:`DataFrame.cummin`, :meth:`DataFrame.cummax`, :meth:`DataFrame.cumprod` and :meth:`DataFrame.cumsum` methods now have a ``numeric_only`` parameter (:issue:`53072`)
 - :meth:`DataFrame.ewm` now allows ``adjust=False`` when ``times`` is provided (:issue:`54328`)
diff --git a/pandas/_config/config.py b/pandas/_config/config.py
index ce53e05608ba7..f8a62ac036d8f 100644
--- a/pandas/_config/config.py
+++ b/pandas/_config/config.py
@@ -199,9 +199,9 @@ def set_option(*args) -> None:
 
     Parameters
     ----------
-    *args : str | object
+    *args : str | object | dict
         Arguments provided in pairs, which will be interpreted as (pattern, value)
-        pairs.
+        pairs, or as a single dictionary containing multiple option-value pairs.
         pattern: str
         Regexp which should match a single option
         value: object
@@ -248,11 +248,51 @@ def set_option(*args) -> None:
     [2 rows x 5 columns]
     >>> pd.reset_option("display.max_columns")
     """
-    # must at least 1 arg deal with constraints later
+    # Handle dictionary input
+    if len(args) == 1 and isinstance(args[0], dict):
+        options_dict = args[0]
+        for k, v in options_dict.items():
+            key = _get_single_key(k)
+            opt = _get_registered_option(key)
+            if opt and opt.validator:
+                opt.validator(v)
+            # walk the nested dict
+            root, k_root = _get_root(key)
+            root[k_root] = v
+            if opt.cb:
+                opt.cb(key)
+        return
+
+    # Handle single option-value pair
+    if len(args) == 2:
+        key = _get_single_key(args[0])
+        v = args[1]
+
+        opt = _get_registered_option(key)
+        if opt and opt.validator:
+            opt.validator(v)
+
+        # walk the nested dict
+        root, k_root = _get_root(key)
+        root[k_root] = v
+
+        if opt.cb:
+            opt.cb(key)
+        return
+
+    # Deprecated (# GH 61093): multiple option-value pairs as separate arguments
     nargs = len(args)
     if not nargs or nargs % 2 != 0:
         raise ValueError("Must provide an even number of non-keyword arguments")
 
+    warnings.warn(
+        "Setting multiple options using multiple arguments is deprecated and will be "
+        "removed in a future version. Use a dictionary instead.",
+        FutureWarning,
+        stacklevel=2,
+    )
+
+    # Backward compatibility
     for k, v in zip(args[::2], args[1::2]):
         key = _get_single_key(k)
 
diff --git a/pandas/tests/config/test_config.py b/pandas/tests/config/test_config.py
index aaf6178866ecd..2e5293b514746 100644
--- a/pandas/tests/config/test_config.py
+++ b/pandas/tests/config/test_config.py
@@ -189,7 +189,28 @@ def test_set_option_multiple(self):
         assert cf.get_option("b.c") == "hullo"
         assert cf.get_option("b.b") is None
 
-        cf.set_option("a", "2", "b.c", None, "b.b", 10.0)
+        # Expect the deprecation warning
+        with tm.assert_produces_warning(
+            FutureWarning,
+            match="Setting multiple options using multiple arguments is deprecated",
+        ):
+            cf.set_option("a", "2", "b.c", None, "b.b", 10.0)
+
+        assert cf.get_option("a") == "2"
+        assert cf.get_option("b.c") is None
+        assert cf.get_option("b.b") == 10.0
+
+    def test_set_option_dict(self):
+        cf.register_option("a", 1, "doc")
+        cf.register_option("b.c", "hullo", "doc2")
+        cf.register_option("b.b", None, "doc2")
+
+        assert cf.get_option("a") == 1
+        assert cf.get_option("b.c") == "hullo"
+        assert cf.get_option("b.b") is None
+
+        options_dict = {"a": "2", "b.c": None, "b.b": 10.0}
+        cf.set_option(options_dict)
 
         assert cf.get_option("a") == "2"
         assert cf.get_option("b.c") is None


Code review comments:
diff_hunk: @@ -189,7 +189,28 @@ def test_set_option_multiple(self):
         assert cf.get_option("b.c") == "hullo"
         assert cf.get_option("b.b") is None
 
-        cf.set_option("a", "2", "b.c", None, "b.b", 10.0)
+        # Expect the deprecation warning
+        with tm.assert_produces_warning(
path: pandas/tests/config/test_config.py
text: @user1:
I don't think this comment adds anything, can you remove.