DictAnyKey: Python Dictionary That Can Use Any Key
DictAnyKey is a modern Python package that provides dictionary-like objects capable of using unhashable keys (such as lists and dictionaries) while maintaining excellent performance for hashable keys.
✨ Key Features
- 🔑 Any Key Type: Use lists, dictionaries, sets, or any unhashable object as keys
- ⚡ Optimized Performance: Hashable keys perform at native dict speed
- 📊 Maintains Order: Preserves insertion order like Python 3.7+ dictionaries
- 🧊 Immutable Variant:
FrozenDictAnyKeyfor hashable, immutable dictionaries - 🎯 Default Values:
DefaultDictAnyKeywith customizable default factories - 📈 Value Counting: Built-in
value_counts()function for frequency analysis - 🔒 Type Safe: Full type hints and mypy compliance
- 🧪 Well Tested: Comprehensive test suite with 271+ tests
🚀 Quick Start
Installation
pip install dictanykey
Basic Usage
from dictanykey import DictAnyKey, FrozenDictAnyKey, DefaultDictAnyKey, value_counts
# Create a dictionary with mixed key types
d = DictAnyKey()
# Hashable keys (fast lookup)
d[1] = "one"
d["hello"] = "world"
# Unhashable keys (slower but supported)
d[[1, 2, 3]] = "list key"
d[{"nested": "dict"}] = "dict key"
print(d) # {1: 'one', 'hello': 'world', [1, 2, 3]: 'list key', {'nested': 'dict'}: 'dict key'}
# All standard dictionary operations work
print(len(d)) # 4
print(1 in d) # True
print([1, 2, 3] in d) # True
print(d.get("missing", "default")) # default
# Iteration preserves insertion order
for key, value in d.items():
print(f"{key}: {value}")
# Output:
# 1: one
# hello: world
# [1, 2, 3]: list key
# {'nested': 'dict'}: dict key
Advanced Features
FrozenDictAnyKey (Immutable)
# Create immutable dictionary
frozen = FrozenDictAnyKey({1: "one", (1, 2): "tuple"})
# Read operations work normally
print(frozen[1]) # one
# Mutation operations raise errors
try:
frozen[2] = "two" # Raises TypeError
except TypeError:
print("Cannot modify frozen dictionary") # Cannot modify frozen dictionary
# Can be used as dictionary keys (if all keys are hashable)
if all(isinstance(k, (int, str, tuple)) for k in frozen.keys()):
other_dict = {frozen: "value"}
print(other_dict) # {FrozenDictAnyKey([(1, 'one'), ((1, 2), 'tuple')]): 'value'}
DefaultDictAnyKey
# Create with default factory
dd = DefaultDictAnyKey(list) # Default to empty list
# Missing keys automatically get default value
dd["new_key"].append("item")
print(dd["new_key"]) # ['item']
# Works with any callable
dd_int = DefaultDictAnyKey(lambda: 0)
dd_int["count"] += 1
print(dd_int["count"]) # 1
Value Counting
# Count frequency of values
data = [1, 2, 2, 3, 3, 3, "a", "a", "b"]
counts = value_counts(data)
print(counts) # {1: 1, 'b': 1, 2: 2, 'a': 2, 3: 3}
# Sort options
counts_asc = value_counts(data, ascending=True) # Lowest to highest
counts_desc = value_counts(data, ascending=False) # Highest to lowest
counts_unsorted = value_counts(data, sort=False) # Preserve original order
print(counts_asc) # {1: 1, 'b': 1, 2: 2, 'a': 2, 3: 3}
print(counts_desc) # {3: 3, 2: 2, 'a': 2, 1: 1, 'b': 1}
print(counts_unsorted) # {1: 1, 2: 2, 3: 3, 'a': 2, 'b': 1}
# Works with unhashable values too!
unhashable_data = [[1, 2], [1, 2], [3, 4], [1, 2]]
unhashable_counts = value_counts(unhashable_data)
print(unhashable_counts) # {[3, 4]: 1, [1, 2]: 3}
📋 Requirements
- Python: 3.9+ (supports 3.9, 3.10, 3.11, 3.12, 3.13)
- Dependencies: None (pure Python)
🔧 API Reference
DictAnyKey
The main dictionary class supporting any key type.
class DictAnyKey(MutableMapping[Any, Any]):
def __init__(self, data: Optional[Union[Iterable, Mapping]] = None) -> None
def __getitem__(self, key: Any) -> Any
def __setitem__(self, key: Any, value: Any) -> None
def __delitem__(self, key: Any) -> None
def __len__(self) -> int
def __iter__(self) -> Iterator[Any]
def __contains__(self, key: Any) -> bool
def __eq__(self, other: object) -> bool
# Standard dictionary methods
def get(self, key: Any, default: Optional[Any] = None) -> Any
def pop(self, key: Any, default: Optional[Any] = None) -> Any
def popitem(self) -> tuple[Any, Any]
def setdefault(self, key: Any, default: Optional[Any] = None) -> Any
def update(self, data: Optional[Union[Iterable, Mapping]] = None) -> None
def clear(self) -> None
def copy(self) -> DictAnyKey
def fromkeys(cls, keys: Iterable[Any], value: Optional[Any] = None) -> DictAnyKey
# View objects
def keys(self) -> DictKeys
def values(self) -> DictValues
def items(self) -> DictItems
FrozenDictAnyKey
Immutable version of DictAnyKey.
class FrozenDictAnyKey(DictAnyKey):
def __hash__(self) -> int # Raises TypeError if keys are unhashable
# All mutation methods raise AttributeError
DefaultDictAnyKey
Dictionary with default value factory.
class DefaultDictAnyKey(DictAnyKey):
def __init__(self, default_factory: Optional[Callable[[], Any]],
data: Optional[Union[Iterable, Mapping]] = None) -> None
# Inherits all DictAnyKey methods
# Missing keys automatically get default_factory() value
Utility Functions
def value_counts(values: Iterable[Any],
sort: bool = True,
ascending: bool = True) -> DictAnyKey
🎯 Use Cases
Data Processing
# Group data by complex keys
groups = DictAnyKey()
for item in data:
key = (item.category, item.subcategory, item.tags)
if key not in groups:
groups[key] = []
groups[key].append(item)
Configuration Management
# Use nested structures as keys
config = DictAnyKey()
config[("database", "host")] = "localhost"
config[("database", "port")] = 5432
config[("cache", "redis", "host")] = "redis-server"
Scientific Computing
# Use arrays as keys for matrix operations
matrix_cache = DictAnyKey()
matrix_cache[tuple([1, 2, 3])] = compute_expensive_result([1, 2, 3])
⚡ Performance Characteristics
- Hashable Keys: O(1) lookup, same performance as built-in
dict - Unhashable Keys: O(n) lookup, where n is the number of unhashable keys
- Memory: Slightly higher memory usage due to dual storage (hashmap + list)
- Insertion Order: Always preserved, regardless of key type
🧪 Testing
The package includes comprehensive tests covering:
- All dictionary operations with various key types
- Edge cases (empty dictionaries, mutation during iteration)
- Performance characteristics
- Type safety and error handling
- Immutable and default dictionary variants
Run tests with:
pytest tests/
🔍 Development
Code Quality Tools
The project uses modern Python tooling:
- Ruff: Fast linting and formatting
- MyPy: Static type checking
- Pytest: Testing framework
- Black: Code formatting (via ruff)
# Format code
ruff format
# Lint code
ruff check
# Type check
mypy dictanykey/
# Run tests
pytest tests/
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Run the test suite
- Submit a pull request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🆕 Changelog
Latest Version
- ✅ Fixed critical bugs in
setdefault(),pop(), andpopitem()methods - ✅ Added comprehensive test coverage (271+ tests)
- ✅ Modernized to Python 3.9+ with full type hints
- ✅ Added
FrozenDictAnyKeyhash support - ✅ Optimized performance for key lookups
- ✅ Migrated to
pyproject.tomlconfiguration - ✅ Added
value_counts()utility function - ✅ Improved documentation and examples
See CHANGELOG.md for detailed version history.
🤝 Support
- Issues: GitHub Issues
- Documentation: This README and inline docstrings
- Source: GitHub Repository
DictAnyKey - Because sometimes you need a dictionary that accepts anything as a key! 🗝️
Metadata
Release files for dictanykey 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dictanykey-0.1.3.tar.gz | 20.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dictanykey-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.2 kB
Release files / dictanykey-0.1.3.tar.gz
| Download URL | dictanykey-0.1.3.tar.gz |
|---|---|
| Size | 20.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
41199435ee377af6fca7be9cadf379b3fc62b1ba277a82153d248476bc46f307
|
|
BLAKE2b-256 checksum How to use checksums |
ee7a2a201510b609e6805e371acda49ddc332c72ae6e6388fade4416f1dbb9ae
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|
Release files / dictanykey-0.1.3-py3-none-any.whl
| Download URL | dictanykey-0.1.3-py3-none-any.whl |
|---|---|
| Size | 13.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bf00fbb2e38876f07b622b5091c523043f8a26e7adf1e0061b4731b3062710e6
|
|
BLAKE2b-256 checksum How to use checksums |
22d3f758e396511c538aa1fc25598dfdecb396e7796246750c7e0277ccf97dce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|