duarteocarmo: had to add another commit (rst. and main function docstring..)
duarteocarmo: Alright! Patched everything, let me know what you think 
ianlini: LGTM now. I will release this with 0.2.0 recently.
Thanks for your contribution!
diff --git a/README.rst b/README.rst
index 7590f88..68a1910 100644
--- a/README.rst
+++ b/README.rst
@@ -30,11 +30,12 @@ Flatten
        ----------
        d : dict-like object
            The dict that will be flattened.
-       reducer : {'tuple', 'path', Callable}
+       reducer : {'tuple', 'path', 'underscore', Callable}
            The key joining method. If a `Callable` is given, the `Callable` will be
            used to reduce.
            'tuple': The resulting key will be tuple of the original keys.
            'path': Use `os.path.join` to join keys.
+           'underscore': Use underscores to join keys.
        inverse : bool
            Whether you want invert the resulting key and value.
        enumerate_types : Sequence[type]
@@ -154,11 +155,12 @@ Unflatten
        ----------
        d : dict-like object
            The dict that will be unflattened.
-       splitter : {'tuple', 'path', Callable}
+       splitter : {'tuple', 'path', 'underscore', Callable}
            The key splitting method. If a Callable is given, the Callable will be
            used to split.
            'tuple': Use each element in the tuple key as the key of the unflattened dict.
            'path': Use `pathlib.Path.parts` to split keys.
+           'underscore': Use underscores to split keys.
        inverse : bool
            Whether you want to invert the key and value before flattening.
 
diff --git a/flatten_dict/flatten_dict.py b/flatten_dict/flatten_dict.py
index 63a11e0..110ffbb 100644
--- a/flatten_dict/flatten_dict.py
+++ b/flatten_dict/flatten_dict.py
@@ -2,18 +2,20 @@ from collections import Mapping
 
 import six
 
-from .reducer import tuple_reducer, path_reducer
-from .splitter import tuple_splitter, path_splitter
+from .reducer import tuple_reducer, path_reducer, underscore_reducer
+from .splitter import tuple_splitter, path_splitter, underscore_splitter
 
 
 REDUCER_DICT = {
     'tuple': tuple_reducer,
     'path': path_reducer,
+    'underscore': underscore_reducer,
 }
 
 SPLITTER_DICT = {
     'tuple': tuple_splitter,
     'path': path_splitter,
+    'underscore': underscore_splitter,
 }
 
 
@@ -24,11 +26,12 @@ def flatten(d, reducer='tuple', inverse=False, enumerate_types=()):
     ----------
     d : dict-like object
         The dict that will be flattened.
-    reducer : {'tuple', 'path', Callable}
+    reducer : {'tuple', 'path', 'underscore', Callable}
         The key joining method. If a `Callable` is given, the `Callable` will be
         used to reduce.
         'tuple': The resulting key will be tuple of the original keys.
         'path': Use `os.path.join` to join keys.
+        'underscore': Use underscores to join keys.
     inverse : bool
         Whether you want invert the resulting key and value.
     enumerate_types : Sequence[type]
@@ -94,11 +97,12 @@ def unflatten(d, splitter='tuple', inverse=False):
     ----------
     d : dict-like object
         The dict that will be unflattened.
-    splitter : {'tuple', 'path', Callable}
+    splitter : {'tuple', 'path', 'underscore', Callable}
         The key splitting method. If a Callable is given, the Callable will be
         used to split.
         'tuple': Use each element in the tuple key as the key of the unflattened dict.
         'path': Use `pathlib.Path.parts` to split keys.
+        'underscore': Use underscores to join keys.
     inverse : bool
         Whether you want to invert the key and value before flattening.
 
diff --git a/flatten_dict/reducer.py b/flatten_dict/reducer.py
index 5eee3f3..bd81d3e 100644
--- a/flatten_dict/reducer.py
+++ b/flatten_dict/reducer.py
@@ -11,3 +11,10 @@ def path_reducer(k1, k2):
         return k2
     else:
         return os.path.join(k1, k2)
+
+
+def underscore_reducer(k1, k2):
+    if k1 is None:
+        return k2
+    else:
+        return k1 + "_" + k2
diff --git a/flatten_dict/splitter.py b/flatten_dict/splitter.py
index d36faf2..5cf9f7e 100644
--- a/flatten_dict/splitter.py
+++ b/flatten_dict/splitter.py
@@ -8,3 +8,8 @@ def tuple_splitter(flat_key):
 def path_splitter(flat_key):
     keys = PurePath(flat_key).parts
     return keys
+
+
+def underscore_splitter(flat_key):
+    keys = tuple(flat_key.split("_"))
+    return keys

Feature Proposition: Underscores as native separators
Hey there!

Can I make a pull request for native inclusion of the underscores as a separator? 

Something like: 

`
flatten(normal_dict, reducer="_")
`

which would output the expanded dict with underscores as separators directly. 

What do you think? 

Cheers and thanks for a great little utility 😄 


diff --git a/flatten_dict/tests.py b/flatten_dict/tests.py
index 89af964..a66d7fd 100644
--- a/flatten_dict/tests.py
+++ b/flatten_dict/tests.py
@@ -57,6 +57,11 @@ def test_flatten_dict_path():
     assert flatten(normal_dict, reducer='path') == flat_path_dict
 
 
+def test_flatten_dict_underscore():
+    flat_underscore_dict = {'_'.join(k): v for k, v in six.viewitems(flat_normal_dict)}
+    assert flatten(normal_dict, reducer='underscore') == flat_underscore_dict
+
+
 def test_flatten_dict_inverse_with_duplicated_value():
     dup_val_dict = normal_dict.copy()
     dup_val_dict['a'] = '2.1.1'
@@ -82,6 +87,11 @@ def test_unflatten_dict_path():
     assert unflatten(flat_path_dict, splitter='path') == normal_dict
 
 
+def test_unflatten_dict_underscore():
+    flat_underscore_dict = {'_'.join(k): v for k, v in six.viewitems(flat_normal_dict)}
+    assert unflatten(flat_underscore_dict, splitter='underscore') == normal_dict
+
+
 def test_unflatten_dict_inverse_with_duplicated_value():
     inv_flat_normal_dict = {v: k for k, v in six.viewitems(flat_normal_dict)}
     inv_flat_normal_dict['2.1.1'] = ('c', 'b', 'a')
@@ -128,6 +138,11 @@ def test_flatten_dict_with_list_path():
     assert flatten(dict_with_list, reducer='path') == flat_path_dict
 
 
+def test_flatten_dict_with_list_underscore():
+    flat_underscore_dict = {'_'.join(k): v for k, v in six.viewitems(flat_dict_with_list)}
+    assert flatten(dict_with_list, reducer='underscore') == flat_underscore_dict
+
+
 def test_unflatten_dict_with_list():
     assert unflatten(flat_dict_with_list) == dict_with_list
 
@@ -141,6 +156,11 @@ def test_unflatten_dict_with_list_path():
     assert unflatten(flat_path_dict, splitter='path') == dict_with_list
 
 
+def test_unflatten_dict_with_list_underscore():
+    flat_underscore_dict = {'_'.join(k): v for k, v in six.viewitems(flat_dict_with_list)}
+    assert unflatten(flat_underscore_dict, splitter='underscore') == dict_with_list
+
+
 flat_dict_with_enumerated_list = {
     ('a',): '0',
     ('b', 'a'): '1.0',

pip install -e .[dev]
parse_log_pytest
Pipfile
nose coverage pytest
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi @ file:///croot/certifi_1671487769961/work/certifi
coverage==7.2.7
exceptiongroup==1.2.2
-e git+https://github.com/ianlini/flatten-dict.git@edec4e2d427b98d699760ce358b14c2304c82480#egg=flatten_dict
importlib-metadata==6.7.0
iniconfig==2.0.0
nose==1.3.7
packaging==24.0
pathlib2==2.3.7.post1
pipfile==0.0.2
pluggy==1.2.0
pytest==7.4.4
six==1.17.0
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
tomli==2.0.1
typing_extensions==4.7.1
zipp==3.15.0

name: flatten-dict
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - pipfile=0.0.2=py_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.2.7
      - exceptiongroup==1.2.2
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - nose==1.3.7
      - packaging==24.0
      - pathlib2==2.3.7.post1
      - pluggy==1.2.0
      - pytest==7.4.4
      - six==1.17.0
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - zipp==3.15.0
prefix: /opt/conda/envs/flatten-dict

flatten_dict/tests.py::test_flatten_dict_underscore flatten_dict/tests.py::test_unflatten_dict_underscore flatten_dict/tests.py::test_flatten_dict_with_list_underscore flatten_dict/tests.py::test_unflatten_dict_with_list_underscore
flatten_dict/tests.py::test_flatten_dict flatten_dict/tests.py::test_flatten_nonflattenable_type flatten_dict/tests.py::test_flatten_dict_inverse flatten_dict/tests.py::test_flatten_dict_with_reducer flatten_dict/tests.py::test_flatten_dict_path flatten_dict/tests.py::test_flatten_dict_inverse_with_duplicated_value flatten_dict/tests.py::test_unflatten_dict flatten_dict/tests.py::test_unflatten_dict_inverse flatten_dict/tests.py::test_unflatten_dict_with_splitter flatten_dict/tests.py::test_unflatten_dict_path flatten_dict/tests.py::test_unflatten_dict_inverse_with_duplicated_value flatten_dict/tests.py::test_flatten_dict_with_list flatten_dict/tests.py::test_flatten_dict_with_list_with_reducer flatten_dict/tests.py::test_flatten_dict_with_list_path flatten_dict/tests.py::test_unflatten_dict_with_list flatten_dict/tests.py::test_unflatten_dict_with_list_with_splitter flatten_dict/tests.py::test_unflatten_dict_with_list_path flatten_dict/tests.py::test_flatten_dict_with_list_with_enumerate_types flatten_dict/tests.py::test_flatten_list
README.rst flatten_dict/reducer.py flatten_dict/flatten_dict.py flatten_dict/splitter.py
README.rst flatten_dict/reducer.py flatten_dict/flatten_dict.py flatten_dict/splitter.py