Repository: SethMMorton/natsort

Issue:
Feature request natsorted():  Sort by letters, then numbers
I'd like to submit a feature request to add an option that would allow natsorted() to sort by letters first, then numbers instead of numbers first, then letters.  Here is an example of the list that I am looking to sort - I am sorting by index 2 ('ABCDE', 'BCDEF' and '10001'):

```python
from natsort import natsorted
a = [
	['192.168.1.1', 'generic_description_1', 'ABCDE', 'apples', '172.16.1.1', u'\nCircuit is up\n'],
	['192.168.1.1', 'generic_description_2', 'BCDEF', 'oranges', '172.16.2.1', u'\nCircuit is up\n'],
	['192.168.2.1', 'generic_description_3', '10001', 'bananas', '172.16.3.1', u'\nCircuit is up\n']
]
print(natsorted(a, key = lambda x: x[2]))
```

Here are the results that are printed:

```
[['192.168.2.1', 'generic_description_3', '10001', 'bananas', '172.16.3.1', u'\nCircuit is up\n'],
 ['192.168.1.1', 'generic_description_1', 'ABCDE', 'apples', '172.16.1.1', u'\nCircuit is up\n'],
 ['192.168.1.1', 'generic_description_2', 'BCDEF', 'oranges', '172.16.2.1', u'\nCircuit is up\n']]
```

And as you can see, it sorts by index 2 by numbers first, then letters:

10001
ABCDE
BCDEF


The option I am requesting would allow the sorting by letters first, then numbers - like this:

ABCDE
BCDEF
10001

Thanks for the consideration!

-Matt

Discussion:

Patch:
diff --git a/natsort/compat/locale.py b/natsort/compat/locale.py
index 4cbe5f5..ab392ee 100644
--- a/natsort/compat/locale.py
+++ b/natsort/compat/locale.py
@@ -6,8 +6,21 @@ from __future__ import (
     absolute_import
 )
 
+# Std. lib imports.
+import sys
+
 # Local imports.
-from natsort.compat.py23 import PY_VERSION, cmp_to_key
+from natsort.compat.py23 import (
+    PY_VERSION,
+    cmp_to_key,
+    py23_unichr,
+)
+
+# This string should be sorted after any other byte string because
+# it contains the max unicode character repeated 20 times.
+# You would need some odd data to come after that.
+null_string = ''
+null_string_max = py23_unichr(sys.maxunicode) * 20
 
 # Make the strxfrm function from strcoll on Python2
 # It can be buggy (especially on BSD-based systems),
@@ -16,7 +29,12 @@ try:
     import icu
     from locale import getlocale
 
-    null_string = b''
+    null_string_locale = b''
+
+    # This string should in theory be sorted after any other byte
+    # string because it contains the max byte char repeated many times.
+    # You would need some odd data to come after that.
+    null_string_locale_max = b'x7f' * 50
 
     def dumb_sort():
         return False
@@ -43,11 +61,28 @@ except ImportError:
     import locale
     if PY_VERSION < 3:
         from locale import strcoll
-        strxfrm = cmp_to_key(strcoll)
-        null_string = strxfrm('')
+        sentinel = object()
+
+        def custom_strcoll(a, b, last=sentinel):
+            """strcoll that can handle a sentinel that is always last."""
+            if a is last:
+                return 0 if a is b else 1
+            elif b is last:  # a cannot also be sentinel b/c above logic
+                return -1
+            else:  # neither are sentinel
+                return strcoll(a, b)
+
+        strxfrm = cmp_to_key(custom_strcoll)
+        null_string_locale = strxfrm('')
+        null_string_locale_max = strxfrm(sentinel)
     else:
         from locale import strxfrm
-        null_string = ''
+        null_string_locale = ''
+
+        # This string should be sorted after any other byte string because
+        # it contains the max unicode character repeated 20 times.
+        # You would need some odd data to come after that.
+        null_string_locale_max = py23_unichr(sys.maxunicode) * 20
 
     # On some systems, locale is broken and does not sort in the expected
     # order. We will try to detect this and compensate.
diff --git a/natsort/natsort.py b/natsort/natsort.py
index be887c7..557e07b 100644
--- a/natsort/natsort.py
+++ b/natsort/natsort.py
@@ -207,13 +207,24 @@ def natsort_keygen(key=None, alg=0, **_kwargs):
         alg |= ns._DUMB
 
     # Set some variables that will be passed to the factory functions
-    sep = natsort.compat.locale.null_string if alg & ns.LOCALEALPHA else ''
+    if alg & ns.NUMAFTER:
+        if alg & ns.LOCALEALPHA:
+            sep = natsort.compat.locale.null_string_locale_max
+        else:
+            sep = natsort.compat.locale.null_string_max
+        pre_sep = natsort.compat.locale.null_string_max
+    else:
+        if alg & ns.LOCALEALPHA:
+            sep = natsort.compat.locale.null_string_locale
+        else:
+            sep = natsort.compat.locale.null_string
+        pre_sep = natsort.compat.locale.null_string
     regex = _regex_chooser[alg & ns._NUMERIC_ONLY]
 
     # Create the functions that will be used to split strings.
     input_transform = _input_string_transform_factory(alg)
     component_transform = _string_component_transform_factory(alg)
-    final_transform = _final_data_transform_factory(alg, sep)
+    final_transform = _final_data_transform_factory(alg, sep, pre_sep)
 
     # Create the high-level parsing functions for strings, bytes, and numbers.
     string_func = _parse_string_factory(
@@ -223,7 +234,7 @@ def natsort_keygen(key=None, alg=0, **_kwargs):
     if alg & ns.PATH:
         string_func = _parse_path_factory(string_func)
     bytes_func = _parse_bytes_factory(alg)
-    num_func = _parse_number_factory(alg, sep)
+    num_func = _parse_number_factory(alg, sep, pre_sep)
 
     # Return the natsort key with the parsing path pre-chosen.
     return partial(
diff --git a/natsort/ns_enum.py b/natsort/ns_enum.py
index 37a00de..4216ddd 100644
--- a/natsort/ns_enum.py
+++ b/natsort/ns_enum.py
@@ -42,6 +42,9 @@ class ns(object):
         Tell `natsort` to not search for exponents as part of a float number.
         For example, with `NOEXP` the number "5.6E5" would be interpreted
         as `5.6`, `"E"`, and `5` instead of `560000`.
+    NUMAFTER, NA
+        Tell `natsort` to sort numbers after non-numbers. By default
+        numbers will be ordered before non-numbers.
     PATH, P
         Tell `natsort` to interpret strings as filesystem paths, so they
         will be split according to the filesystem separator
@@ -151,6 +154,7 @@ class ns(object):
     CAPITALFIRST           = C  = UNGROUPLETTERS
     NANLAST                = NL = 1 << 10
     COMPATIBILITYNORMALIZE = CN = 1 << 11
+    NUMAFTER               = NA = 1 << 12
 
     # The below are private options for internal use only.
     _NUMERIC_ONLY    = REAL | NOEXP
diff --git a/natsort/utils.py b/natsort/utils.py
index c33de1d..b6484b0 100644
--- a/natsort/utils.py
+++ b/natsort/utils.py
@@ -197,7 +197,7 @@ def _parse_bytes_factory(alg):
         return lambda x: (x,)
 
 
-def _parse_number_factory(alg, sep):
+def _parse_number_factory(alg, sep, pre_sep):
     """Create a function that will properly format a number in a tuple."""
     nan_replace = float('+inf') if alg & ns.NANLAST else float('-inf')
 
@@ -207,9 +207,9 @@ def _parse_number_factory(alg, sep):
 
     # Return the function, possibly wrapping in tuple if PATH is selected.
     if alg & ns.PATH and alg & ns.UNGROUPLETTERS and alg & ns.LOCALEALPHA:
-        return lambda x: ((('',), func(x)),)
+        return lambda x: (((pre_sep,), func(x)),)
     elif alg & ns.UNGROUPLETTERS and alg & ns.LOCALEALPHA:
-        return lambda x: (('',), func(x))
+        return lambda x: ((pre_sep,), func(x))
     elif alg & ns.PATH:
         return lambda x: (func(x),)
     else:
@@ -363,7 +363,7 @@ def _string_component_transform_factory(alg):
         return partial(fast_int, **kwargs)
 
 
-def _final_data_transform_factory(alg, sep):
+def _final_data_transform_factory(alg, sep, pre_sep):
     """
     Given a set of natsort algorithms, return the function to operate
     on the post-parsed strings according to the user's request.
@@ -383,7 +383,7 @@ def _final_data_transform_factory(alg, sep):
             if not split_val:
                 return (), ()
             elif split_val[0] == sep:
-                return ('',), split_val
+                return (pre_sep,), split_val
             else:
                 return (transform(val[0]),), split_val
         return func


Test patch:
diff --git a/test_natsort/test_final_data_transform_factory.py b/test_natsort/test_final_data_transform_factory.py
index bfd3dfd..f0207e6 100644
--- a/test_natsort/test_final_data_transform_factory.py
+++ b/test_natsort/test_final_data_transform_factory.py
@@ -20,38 +20,38 @@ from hypothesis.strategies import (
 
 
 def test_final_data_transform_factory_with_iterable_returns_tuple_with_no_options_example():
-    assert _final_data_transform_factory(0, '')(iter([7]), '') == (7,)
+    assert _final_data_transform_factory(0, '', '')(iter([7]), '') == (7,)
 
 
 @given(text())
 def test_final_data_transform_factory_with_iterable_returns_tuple_with_no_options(x):
-    assert _final_data_transform_factory(0, '')(iter([x]), '') == (x,)
+    assert _final_data_transform_factory(0, '', '')(iter([x]), '') == (x,)
     # UNGROUPLETTERS without LOCALE does nothing, as does LOCALE without UNGROUPLETTERS
-    assert _final_data_transform_factory(ns.UNGROUPLETTERS, '')(iter([x]), '') == _final_data_transform_factory(0, '')(iter([x]), '')
-    assert _final_data_transform_factory(ns.LOCALE, '')(iter([x]), '') == _final_data_transform_factory(0, '')(iter([x]), '')
+    assert _final_data_transform_factory(ns.UNGROUPLETTERS, '', '')(iter([x]), '') == _final_data_transform_factory(0, '', '')(iter([x]), '')
+    assert _final_data_transform_factory(ns.LOCALE, '', '')(iter([x]), '') == _final_data_transform_factory(0, '', '')(iter([x]), '')
 
 
 def test_final_data_transform_factory_with_empty_tuple_returns_double_empty_tuple():
-    assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS, '')((), '') == ((), ())
+    assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS, '', '')((), '') == ((), ())
 
 
 def test_final_data_transform_factory_with_null_string_first_element_adds_empty_string_on_first_tuple_element():
-    assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS, '')(('', 60), '') == (('',), ('', 60))
+    assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS, '', 'xx')(('', 60), '') == (('xx',), ('', 60))
 
 
 def test_final_data_transform_factory_returns_first_element_in_first_tuple_element_example():
-    assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS, '')(('this', 60), 'this60') == (('t',), ('this', 60))
+    assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS, '', '')(('this', 60), 'this60') == (('t',), ('this', 60))
 
 
 @given(x=text().filter(bool), y=floats(allow_nan=False, allow_infinity=False) | integers())
 def test_final_data_transform_factory_returns_first_element_in_first_tuple_element(x, y):
-    assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS, '')((x, y), ''.join(map(py23_str, [x, y]))) == ((x[0],), (x, y))
+    assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS, '', '')((x, y), ''.join(map(py23_str, [x, y]))) == ((x[0],), (x, y))
 
 
 def test_final_data_transform_factory_returns_first_element_in_first_tuple_element_caseswapped_with_DUMB_and_LOWERCASEFIRST_example():
-    assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS | ns._DUMB | ns.LOWERCASEFIRST, '')(('this', 60), 'this60') == (('T',), ('this', 60))
+    assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS | ns._DUMB | ns.LOWERCASEFIRST, '', '')(('this', 60), 'this60') == (('T',), ('this', 60))
 
 
 @given(x=text().filter(bool), y=floats(allow_nan=False, allow_infinity=False) | integers())
 def test_final_data_transform_factory_returns_first_element_in_first_tuple_element_caseswapped_with_DUMB_and_LOWERCASEFIRST(x, y):
-    assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS | ns._DUMB | ns.LOWERCASEFIRST, '')((x, y), ''.join(map(py23_str, [x, y]))) == ((x[0].swapcase(),), (x, y))
+    assert _final_data_transform_factory(ns.LOCALE | ns.UNGROUPLETTERS | ns._DUMB | ns.LOWERCASEFIRST, '', '')((x, y), ''.join(map(py23_str, [x, y]))) == ((x[0].swapcase(),), (x, y))
diff --git a/test_natsort/test_natsort_key.py b/test_natsort/test_natsort_key.py
index a675cb2..9aabd11 100644
--- a/test_natsort/test_natsort_key.py
+++ b/test_natsort/test_natsort_key.py
@@ -34,10 +34,10 @@ if PY_VERSION >= 3:
 regex = _regex_chooser[ns.INT]
 pre = _input_string_transform_factory(ns.INT)
 post = _string_component_transform_factory(ns.INT)
-after = _final_data_transform_factory(ns.INT, '')
+after = _final_data_transform_factory(ns.INT, '', '')
 string_func = _parse_string_factory(ns.INT, '', regex.split, pre, post, after)
 bytes_func = _parse_bytes_factory(ns.INT)
-num_func = _parse_number_factory(ns.INT, '')
+num_func = _parse_number_factory(ns.INT, '', '')
 
 
 def test__natsort_key_with_numeric_input_and_PATH_returns_number_in_nested_tuple():
@@ -45,7 +45,7 @@ def test__natsort_key_with_numeric_input_and_PATH_returns_number_in_nested_tuple
     # so it will sort against the other as_path results.
     sfunc = _parse_path_factory(string_func)
     bytes_func = _parse_bytes_factory(ns.PATH)
-    num_func = _parse_number_factory(ns.PATH, '')
+    num_func = _parse_number_factory(ns.PATH, '', '')
     assert _natsort_key(10, None, sfunc, bytes_func, num_func) == (('', 10),)
 
 
@@ -55,7 +55,7 @@ def test__natsort_key_with_bytes_input_and_PATH_returns_number_in_nested_tuple()
     # so it will sort against the other as_path results.
     sfunc = _parse_path_factory(string_func)
     bytes_func = _parse_bytes_factory(ns.PATH)
-    num_func = _parse_number_factory(ns.PATH, '')
+    num_func = _parse_number_factory(ns.PATH, '', '')
     assert _natsort_key(b'/hello/world', None, sfunc, bytes_func, num_func) == ((b'/hello/world',),)
 
 
@@ -63,7 +63,7 @@ def test__natsort_key_with_tuple_of_paths_and_PATH_returns_triply_nested_tuple()
     # PATH also handles recursion well.
     sfunc = _parse_path_factory(string_func)
     bytes_func = _parse_bytes_factory(ns.PATH)
-    num_func = _parse_number_factory(ns.PATH, '')
+    num_func = _parse_number_factory(ns.PATH, '', '')
     assert _natsort_key(('/Folder', '/Folder (1)'), None, sfunc, bytes_func, num_func) == ((('/',), ('Folder',)), (('/',), ('Folder (', 1, ')')))
 
 
diff --git a/test_natsort/test_natsort_keygen.py b/test_natsort/test_natsort_keygen.py
index e94d3d3..9ea408a 100644
--- a/test_natsort/test_natsort_keygen.py
+++ b/test_natsort/test_natsort_keygen.py
@@ -16,7 +16,7 @@ from natsort import (
 )
 from natsort.compat.py23 import PY_VERSION
 from natsort.compat.locale import (
-    null_string,
+    null_string_locale,
     get_strxfrm,
 )
 from compat.mock import patch
@@ -79,9 +79,9 @@ def test_natsort_keygen_splits_input_with_locale():
     load_locale('en_US')
     strxfrm = get_strxfrm()
     with patch('natsort.compat.locale.dumb_sort', return_value=False):
-        assert natsort_keygen(alg=ns.L)(INPUT) == ((null_string, 6, strxfrm('A-'), 5, strxfrm('.'), 34, strxfrm('e+'), 1), (strxfrm('/Folder ('), 1, strxfrm(')/Foo')), (null_string, 56.7))
+        assert natsort_keygen(alg=ns.L)(INPUT) == ((null_string_locale, 6, strxfrm('A-'), 5, strxfrm('.'), 34, strxfrm('e+'), 1), (strxfrm('/Folder ('), 1, strxfrm(')/Foo')), (null_string_locale, 56.7))
     with patch('natsort.compat.locale.dumb_sort', return_value=True):
-        assert natsort_keygen(alg=ns.L)(INPUT) == ((null_string, 6, strxfrm('aa--'), 5, strxfrm('..'), 34, strxfrm('eE++'), 1), (strxfrm('//ffoOlLdDeErR  (('), 1, strxfrm('))//ffoOoO')), (null_string, 56.7))
+        assert natsort_keygen(alg=ns.L)(INPUT) == ((null_string_locale, 6, strxfrm('aa--'), 5, strxfrm('..'), 34, strxfrm('eE++'), 1), (strxfrm('//ffoOlLdDeErR  (('), 1, strxfrm('))//ffoOoO')), (null_string_locale, 56.7))
     if PY_VERSION >= 3: assert natsort_keygen(alg=ns.LA)(b'6A-5.034e+1') == (b'6A-5.034e+1',)
     locale.setlocale(locale.LC_ALL, str(''))
 
@@ -90,7 +90,7 @@ def test_natsort_keygen_splits_input_with_locale_and_capitalfirst():
     load_locale('en_US')
     strxfrm = get_strxfrm()
     with patch('natsort.compat.locale.dumb_sort', return_value=False):
-        assert natsort_keygen(alg=ns.LA | ns.C)(INPUT) == ((('',), (null_string, 6, strxfrm('A-'), 5, strxfrm('.'), 34, strxfrm('e+'), 1)), (('/',), (strxfrm('/Folder ('), 1, strxfrm(')/Foo'))), (('',), (null_string, 56.7)))
+        assert natsort_keygen(alg=ns.LA | ns.C)(INPUT) == ((('',), (null_string_locale, 6, strxfrm('A-'), 5, strxfrm('.'), 34, strxfrm('e+'), 1)), (('/',), (strxfrm('/Folder ('), 1, strxfrm(')/Foo'))), (('',), (null_string_locale, 56.7)))
     if PY_VERSION >= 3: assert natsort_keygen(alg=ns.LA | ns.C)(b'6A-5.034e+1') == (b'6A-5.034e+1',)
     locale.setlocale(locale.LC_ALL, str(''))
 
diff --git a/test_natsort/test_natsorted.py b/test_natsort/test_natsorted.py
index fcbf75b..71ffbf6 100644
--- a/test_natsort/test_natsorted.py
+++ b/test_natsort/test_natsorted.py
@@ -82,8 +82,10 @@ def test_natsorted_returns_sorted_list_with_mixed_type_input_and_does_not_raise_
 def test_natsorted_with_mixed_input_returns_sorted_results_without_error():
     a = ['0', 'Á', '2', 'Z']
     assert natsorted(a) == ['0', '2', 'Á', 'Z']
+    assert natsorted(a, alg=ns.NUMAFTER) == ['Á', 'Z', '0', '2']
     a = ['2', 'ä', 'b', 1.5, 3]
     assert natsorted(a) == [1.5, '2', 3, 'ä', 'b']
+    assert natsorted(a, alg=ns.NUMAFTER) == ['ä', 'b', 1.5, '2', 3]
 
 
 def test_natsorted_with_nan_input_returns_sorted_results_with_nan_last_with_NANLAST():
@@ -243,8 +245,10 @@ def test_natsorted_with_LOCALE_and_mixed_input_returns_sorted_results_without_er
     load_locale('en_US')
     a = ['0', 'Á', '2', 'Z']
     assert natsorted(a, alg=ns.LOCALE) == ['0', '2', 'Á', 'Z']
+    assert natsorted(a, alg=ns.LOCALE | ns.NUMAFTER) == ['Á', 'Z', '0', '2']
     a = ['2', 'ä', 'b', 1.5, 3]
     assert natsorted(a, alg=ns.LOCALE) == [1.5, '2', 3, 'ä', 'b']
+    assert natsorted(a, alg=ns.LOCALE | ns.NUMAFTER) == ['ä', 'b', 1.5, '2', 3]
     locale.setlocale(locale.LC_ALL, str(''))
 
 
@@ -252,8 +256,10 @@ def test_natsorted_with_LOCALE_and_UNGROUPLETTERS_and_mixed_input_returns_sorted
     load_locale('en_US')
     a = ['0', 'Á', '2', 'Z']
     assert natsorted(a, alg=ns.LOCALE | ns.UNGROUPLETTERS) == ['0', '2', 'Á', 'Z']
+    assert natsorted(a, alg=ns.LOCALE | ns.UNGROUPLETTERS | ns.NUMAFTER) == ['Á', 'Z', '0', '2']
     a = ['2', 'ä', 'b', 1.5, 3]
     assert natsorted(a, alg=ns.LOCALE | ns.UNGROUPLETTERS) == [1.5, '2', 3, 'ä', 'b']
+    assert natsorted(a, alg=ns.LOCALE | ns.UNGROUPLETTERS | ns.NUMAFTER) == ['ä', 'b', 1.5, '2', 3]
     locale.setlocale(locale.LC_ALL, str(''))
 
 
@@ -261,6 +267,16 @@ def test_natsorted_with_PATH_and_LOCALE_and_UNGROUPLETTERS_and_mixed_input_retur
     load_locale('en_US')
     a = ['0', 'Á', '2', 'Z']
     assert natsorted(a, alg=ns.PATH | ns.LOCALE | ns.UNGROUPLETTERS) == ['0', '2', 'Á', 'Z']
+    assert natsorted(a, alg=ns.PATH | ns.LOCALE | ns.UNGROUPLETTERS | ns.NUMAFTER) == ['Á', 'Z', '0', '2']
     a = ['2', 'ä', 'b', 1.5, 3]
     assert natsorted(a, alg=ns.PATH | ns.LOCALE | ns.UNGROUPLETTERS) == [1.5, '2', 3, 'ä', 'b']
+    assert natsorted(a, alg=ns.PATH | ns.LOCALE | ns.UNGROUPLETTERS | ns.NUMAFTER) == ['ä', 'b', 1.5, '2', 3]
     locale.setlocale(locale.LC_ALL, str(''))
+
+
+def test_natsorted_sorts_an_odd_collection_of_string():
+    a = ['Corn', 'apple', 'Banana', '73', 'Apple', '5039', 'corn', '~~~~~~', 'banana']
+    assert natsorted(a) == ['73', '5039', 'Apple', 'Banana', 'Corn',
+                            'apple', 'banana', 'corn', '~~~~~~']
+    assert natsorted(a, alg=ns.NUMAFTER) == ['Apple', 'Banana', 'Corn',
+                                             'apple', 'banana', 'corn', '~~~~~~', '73', '5039']
diff --git a/test_natsort/test_parse_number_function.py b/test_natsort/test_parse_number_function.py
index 163d066..2e7a9fe 100644
--- a/test_natsort/test_parse_number_function.py
+++ b/test_natsort/test_parse_number_function.py
@@ -18,38 +18,38 @@ from hypothesis.strategies import (
 
 
 def test_parse_number_factory_makes_function_that_returns_tuple_example():
-    assert _parse_number_factory(0, '')(57) == ('', 57)
-    assert _parse_number_factory(0, '')(float('nan')) == ('', float('-inf'))
-    assert _parse_number_factory(ns.NANLAST, '')(float('nan')) == ('', float('+inf'))
+    assert _parse_number_factory(0, '', '')(57) == ('', 57)
+    assert _parse_number_factory(0, '', '')(float('nan')) == ('', float('-inf'))
+    assert _parse_number_factory(ns.NANLAST, '', '')(float('nan')) == ('', float('+inf'))
 
 
 @given(floats(allow_nan=False) | integers())
 def test_parse_number_factory_makes_function_that_returns_tuple(x):
-    assert _parse_number_factory(0, '')(x) == ('', x)
+    assert _parse_number_factory(0, '', '')(x) == ('', x)
 
 
 def test_parse_number_factory_with_PATH_makes_function_that_returns_nested_tuple_example():
-    assert _parse_number_factory(ns.PATH, '')(57) == (('', 57),)
+    assert _parse_number_factory(ns.PATH, '', '')(57) == (('', 57),)
 
 
 @given(floats(allow_nan=False) | integers())
 def test_parse_number_factory_with_PATH_makes_function_that_returns_nested_tuple(x):
-    assert _parse_number_factory(ns.PATH, '')(x) == (('', x),)
+    assert _parse_number_factory(ns.PATH, '', '')(x) == (('', x),)
 
 
 def test_parse_number_factory_with_UNGROUPLETTERS_LOCALE_makes_function_that_returns_nested_tuple_example():
-    assert _parse_number_factory(ns.UNGROUPLETTERS | ns.LOCALE, '')(57) == (('',), ('', 57))
+    assert _parse_number_factory(ns.UNGROUPLETTERS | ns.LOCALE, '', 'xx')(57) == (('xx',), ('', 57))
 
 
 @given(floats(allow_nan=False) | integers())
 def test_parse_number_factory_with_UNGROUPLETTERS_LOCALE_makes_function_that_returns_nested_tuple(x):
-    assert _parse_number_factory(ns.UNGROUPLETTERS | ns.LOCALE, '')(x) == (('',), ('', x))
+    assert _parse_number_factory(ns.UNGROUPLETTERS | ns.LOCALE, '', 'xx')(x) == (('xx',), ('', x))
 
 
 def test_parse_number_factory_with_PATH_UNGROUPLETTERS_LOCALE_makes_function_that_returns_nested_tuple_example():
-    assert _parse_number_factory(ns.PATH | ns.UNGROUPLETTERS | ns.LOCALE, '')(57) == ((('',), ('', 57)),)
+    assert _parse_number_factory(ns.PATH | ns.UNGROUPLETTERS | ns.LOCALE, '', 'xx')(57) == ((('xx',), ('', 57)),)
 
 
 @given(floats(allow_nan=False) | integers())
 def test_parse_number_factory_with_PATH_UNGROUPLETTERS_LOCALE_makes_function_that_returns_nested_tuple(x):
-    assert _parse_number_factory(ns.PATH | ns.UNGROUPLETTERS | ns.LOCALE, '')(x) == ((('',), ('', x)),)
+    assert _parse_number_factory(ns.PATH | ns.UNGROUPLETTERS | ns.LOCALE, '', 'xx')(x) == ((('xx',), ('', x)),)
diff --git a/test_natsort/test_utils.py b/test_natsort/test_utils.py
index cfa06b6..10ad0fa 100644
--- a/test_natsort/test_utils.py
+++ b/test_natsort/test_utils.py
@@ -24,7 +24,7 @@ from natsort.utils import (
     chain_functions,
 )
 from natsort.compat.py23 import py23_str, py23_cmp
-from natsort.compat.locale import null_string
+from natsort.compat.locale import null_string_locale
 from slow_splitters import (
     sep_inserter,
     add_leading_space_if_first_is_num,
@@ -149,6 +149,7 @@ def test_ns_enum_values_have_are_as_expected():
     assert ns.UNGROUPLETTERS == ns.CAPITALFIRST
     assert ns.NANLAST == ns.NL
     assert ns.COMPATIBILITYNORMALIZE == ns.CN
+    assert ns.NUMAFTER == ns.NA
 
     # Convenience
     assert ns.LOCALE == ns.LOCALEALPHA | ns.LOCALENUM
@@ -195,7 +196,7 @@ def test_sep_inserter_does_nothing_if_only_one_number_example():
 
 def test_sep_inserter_inserts_separator_string_between_two_numbers_example():
     assert list(_sep_inserter(iter([5, 9]), '')) == ['', 5, '', 9]
-    assert list(_sep_inserter(iter([5, 9]), null_string)) == [null_string, 5, null_string, 9]
+    assert list(_sep_inserter(iter([5, 9]), null_string_locale)) == [null_string_locale, 5, null_string_locale, 9]
 
 
 @given(lists(elements=text().filter(bool) | integers()))