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..b96da4c24a2d7 100644 --- a/pandas/_config/config.py +++ b/pandas/_config/config.py @@ -199,9 +199,9 @@ def set_option(*args) -> None: Parameters ---------- - *args : str | object - Arguments provided in pairs, which will be interpreted as (pattern, value) - pairs. + *args : str | object | dict + Arguments provided in pairs, which will be interpreted as (pattern, value), + or as a single dictionary containing multiple option-value pairs. pattern: str Regexp which should match a single option value: object @@ -239,6 +239,8 @@ def set_option(*args) -> None: Examples -------- + Option-Value Pair Input: + >>> pd.set_option("display.max_columns", 4) >>> df = pd.DataFrame([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]) >>> df @@ -247,8 +249,40 @@ def set_option(*args) -> None: 1 6 7 ... 9 10 [2 rows x 5 columns] >>> pd.reset_option("display.max_columns") + + Dictionary Input: + + >>> pd.set_option({"display.max_columns": 4, "display.precision": 1}) + >>> df = pd.DataFrame([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]) + >>> df + 0 1 ... 3 4 + 0 1 2 ... 4 5 + 1 6 7 ... 9 10 + [2 rows x 5 columns] + >>> pd.reset_option("display.max_columns") + >>> pd.reset_option("display.precision") """ - # must at least 1 arg deal with constraints later + # Handle dictionary input + if len(args) == 1 and isinstance(args[0], dict): + args = tuple(kv for item in args[0].items() for kv in item) + + # 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 + nargs = len(args) if not nargs or nargs % 2 != 0: raise ValueError("Must provide an even number of non-keyword arguments") @@ -440,9 +474,10 @@ def option_context(*args) -> Generator[None]: Parameters ---------- - *args : str | object + *args : str | object | dict An even amount of arguments provided in pairs which will be - interpreted as (pattern, value) pairs. + interpreted as (pattern, value) pairs. Alternatively, a single + dictionary of {pattern: value} may be provided. Returns ------- @@ -471,7 +506,12 @@ def option_context(*args) -> Generator[None]: >>> from pandas import option_context >>> with option_context("display.max_rows", 10, "display.max_columns", 5): ... pass + >>> with option_context({"display.max_rows": 10, "display.max_columns": 5}): + ... pass """ + if len(args) == 1 and isinstance(args[0], dict): + args = tuple(kv for item in args[0].items() for kv in item) + if len(args) % 2 != 0 or len(args) < 2: raise ValueError( "Provide an even amount of arguments as " diff --git a/pandas/tests/config/test_config.py b/pandas/tests/config/test_config.py index aaf6178866ecd..a6bc40469cada 100644 --- a/pandas/tests/config/test_config.py +++ b/pandas/tests/config/test_config.py @@ -195,6 +195,24 @@ def test_set_option_multiple(self): assert cf.get_option("b.c") is None assert cf.get_option("b.b") == 10.0 + def test_set_option_dict(self): + # GH 61093 + + 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 + assert cf.get_option("b.b") == 10.0 + def test_validation(self): cf.register_option("a", 1, "doc", validator=cf.is_int) cf.register_option("d", 1, "doc", validator=cf.is_nonnegative_int) @@ -377,6 +395,33 @@ def f(): f() + def test_set_ContextManager_dict(self): + def eq(val): + assert cf.get_option("a") == val + assert cf.get_option("b.c") == val + + cf.register_option("a", 0) + cf.register_option("b.c", 0) + + eq(0) + with cf.option_context({"a": 15, "b.c": 15}): + eq(15) + with cf.option_context({"a": 25, "b.c": 25}): + eq(25) + eq(15) + eq(0) + + cf.set_option("a", 17) + cf.set_option("b.c", 17) + eq(17) + + # Test that option_context can be used as a decorator too + @cf.option_context({"a": 123, "b.c": 123}) + def f(): + eq(123) + + f() + def test_attribute_access(self): holder = []