Simple yet flexible natural sorting in Python that enables developers to sort strings containing numbers in a natural, human-expected order rather than lexicographical order.
—
The ns enum system provides fine-grained control over natural sorting behavior. Options can be combined using bitwise OR operations to create customized sorting algorithms that handle different data types and cultural conventions.
The main configuration enum that controls natsort algorithm behavior.
class ns(enum.IntEnum):
"""
Enum to control the natsort algorithm.
Options can be combined using bitwise OR (|) operations.
Each option has a shortened 1- or 2-letter form.
"""
# Number interpretation (mutually exclusive groups)
DEFAULT = 0 # Default behavior
INT = I = 0 # Parse numbers as integers (default)
UNSIGNED = U = 0 # Ignore signs like +/- (default)
FLOAT = F = 1 # Parse numbers as floats
SIGNED = S = 2 # Consider +/- signs in numbers
REAL = R = 3 # Shortcut for FLOAT | SIGNED
NOEXP = N = 4 # Don't parse scientific notation (e.g., 1e4)
# String and character handling
IGNORECASE = IC = 64 # Case-insensitive sorting
LOWERCASEFIRST = LF = 128 # Lowercase letters before uppercase
GROUPLETTERS = G = 256 # Group upper/lowercase together
CAPITALFIRST = C = 512 # Capitalized words first (alias: UNGROUPLETTERS)
UNGROUPLETTERS = UG = 512 # Alias for CAPITALFIRST
# Locale and internationalization
LOCALEALPHA = LA = 16 # Locale-aware alphabetical sorting only
LOCALENUM = LN = 32 # Locale-aware numeric formatting only
LOCALE = L = 48 # Locale-aware sorting (LOCALEALPHA | LOCALENUM)
# Special handling
PATH = P = 8 # Treat strings as filesystem paths
NANLAST = NL = 1024 # Place NaN values last instead of first
COMPATIBILITYNORMALIZE = CN = 2048 # Use NFKD unicode normalization
NUMAFTER = NA = 4096 # Sort numbers after non-numbers
PRESORT = PS = 8192 # Pre-sort as strings before natural sortingType definition for algorithm specifications.
NSType = Union[ns, int]
"""
Type alias for algorithm specification.
Accepts either ns enum values or integer combinations.
"""from natsort import natsorted, ns
# Default integer handling
data = ['item1', 'item10', 'item2']
print(natsorted(data)) # ['item1', 'item2', 'item10']
# Float number handling
float_data = ['value1.5', 'value1.10', 'value1.05']
print(natsorted(float_data, alg=ns.FLOAT))
# Output: ['value1.05', 'value1.5', 'value1.10']
# Signed number handling (positive and negative)
signed_data = ['temp-5', 'temp10', 'temp-12', 'temp2']
print(natsorted(signed_data, alg=ns.SIGNED))
# Output: ['temp-12', 'temp-5', 'temp2', 'temp10']
# Real numbers (float + signed)
real_data = ['val-1.5', 'val2.3', 'val-0.8', 'val10.1']
print(natsorted(real_data, alg=ns.REAL))
# Output: ['val-1.5', 'val-0.8', 'val2.3', 'val10.1']from natsort import natsorted, ns
mixed_case = ['Item1', 'item10', 'Item2', 'item20']
# Default (case-sensitive)
print("Default:", natsorted(mixed_case))
# Output: ['Item1', 'Item2', 'item10', 'item20']
# Case-insensitive
print("Ignore case:", natsorted(mixed_case, alg=ns.IGNORECASE))
# Output: ['Item1', 'Item2', 'item10', 'item20']
# Lowercase first
print("Lowercase first:", natsorted(mixed_case, alg=ns.LOWERCASEFIRST))
# Output: ['item10', 'item20', 'Item1', 'Item2']
# Group letters (case-insensitive grouping)
print("Group letters:", natsorted(mixed_case, alg=ns.GROUPLETTERS))
# Output: ['Item1', 'item10', 'Item2', 'item20']from natsort import natsorted, ns
# File paths with different extensions and directories
paths = [
'folder/file10.txt',
'folder/file2.txt',
'folder/file1.log',
'folder/file10.log',
'folder/file2.py'
]
# Default sorting
print("Default sorting:")
for path in natsorted(paths):
print(f" {path}")
# Path-aware sorting (splits on separators and extensions)
print("\nPath-aware sorting:")
for path in natsorted(paths, alg=ns.PATH):
print(f" {path}")from natsort import natsorted, ns
# International characters and accents
international = ['café', 'naïve', 'résumé', 'Åpfel', 'zebra', 'Zürich']
# Default sorting (ASCII order)
print("Default:", natsorted(international))
# Locale-aware sorting (requires proper locale setup)
print("Locale-aware:", natsorted(international, alg=ns.LOCALE))
# Locale-aware with case insensitive
print("Locale + ignore case:",
natsorted(international, alg=ns.LOCALE | ns.IGNORECASE))
# Note: Results depend on system locale configuration
# Install PyICU for best locale supportfrom natsort import natsorted, ns
# Scientific data with complex requirements
scientific_data = [
'Sample-1.5E+3_Trial2',
'sample-2.1e-4_trial10',
'SAMPLE-0.8E+2_trial1',
'Sample-3.2_Trial20'
]
# Combine multiple options:
# - REAL: handle signed floats and scientific notation
# - IGNORECASE: case-insensitive comparison
# - LOWERCASEFIRST: lowercase variants come first
combined_alg = ns.REAL | ns.IGNORECASE | ns.LOWERCASEFIRST
sorted_scientific = natsorted(scientific_data, alg=combined_alg)
print("Scientific data with combined algorithm:")
for item in sorted_scientific:
print(f" {item}")from natsort import natsorted, ns
import math
# Data with special values
data_with_nan = ['value1', 'value10', None, 'value2', float('nan')]
# Default NaN handling (NaN comes first)
print("Default NaN handling:", natsorted(data_with_nan))
# NaN last
print("NaN last:", natsorted(data_with_nan, alg=ns.NANLAST))
# Numbers after non-numbers (reverse typical order)
mixed_types = ['abc', '10', 'def', '2', 'ghi']
print("Default:", natsorted(mixed_types))
print("Numbers after:", natsorted(mixed_types, alg=ns.NUMAFTER))from natsort import natsorted, ns
# Data where string representation affects order
inconsistent_data = ['a01', 'a1', 'a10', 'a010']
# Default behavior (may be inconsistent due to string representation)
print("Default:", natsorted(inconsistent_data))
# Pre-sort to ensure consistent ordering
print("With presort:", natsorted(inconsistent_data, alg=ns.PRESORT))from natsort import natsorted, ns
# Unicode characters that may have different representations
unicode_data = ['café', 'cafe\u0301'] # Second has combining accent
# Default normalization (NFD)
print("Default normalization:", natsorted(unicode_data))
# Compatibility normalization (NFKD) - converts more characters
print("Compatibility normalization:",
natsorted(unicode_data, alg=ns.COMPATIBILITYNORMALIZE))from natsort import natsorted, ns
def create_algorithm(**options):
"""Helper function to build algorithm from named options."""
alg = ns.DEFAULT
if options.get('float_numbers'):
alg |= ns.FLOAT
if options.get('signed_numbers'):
alg |= ns.SIGNED
if options.get('ignore_case'):
alg |= ns.IGNORECASE
if options.get('locale_aware'):
alg |= ns.LOCALE
if options.get('path_sorting'):
alg |= ns.PATH
if options.get('numbers_last'):
alg |= ns.NUMAFTER
return alg
# Example usage of algorithm builder
data = ['File-1.5', 'file10.2', 'FILE2.0']
# Build custom algorithm
custom_alg = create_algorithm(
float_numbers=True,
signed_numbers=True,
ignore_case=True
)
result = natsorted(data, alg=custom_alg)
print(f"Custom algorithm result: {result}")
# Equivalent to:
# result = natsorted(data, alg=ns.REAL | ns.IGNORECASE)from natsort import natsorted, ns
import sys
# Algorithm selection based on platform/locale
def get_platform_algorithm():
"""Select appropriate algorithm based on platform."""
base_alg = ns.REAL | ns.IGNORECASE
if sys.platform.startswith('win'):
# Windows-specific adjustments
return base_alg | ns.LOWERCASEFIRST
else:
# Unix-like systems
return base_alg | ns.LOCALE
platform_alg = get_platform_algorithm()
data = ['File1', 'file10', 'File2']
result = natsorted(data, alg=platform_alg)
print(f"Platform-optimized result: {result}")Install with Tessl CLI
npx tessl i tessl/pypi-natsort