A dict that can use unhashable keys
Project description
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}")
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")
# 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"}
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) # DictAnyKey({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) # DictAnyKey({[1, 2]: 3, [3, 4]: 1})
📋 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! 🗝️
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dictanykey-0.1.1.tar.gz.
File metadata
- Download URL: dictanykey-0.1.1.tar.gz
- Upload date:
- Size: 19.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2cd21f361a6d32d9f6003b764c328c604662080a25e3617d3f1d575300054c67
|
|
| MD5 |
00b083f5603cbae9537c05c968c43564
|
|
| BLAKE2b-256 |
fe21978f60c14b82955f9064c69b637f460111a1e3e3220c16af59d2c336d480
|
File details
Details for the file dictanykey-0.1.1-py3-none-any.whl.
File metadata
- Download URL: dictanykey-0.1.1-py3-none-any.whl
- Upload date:
- Size: 13.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
796a8206fa102c88960ecacb8621b64f00126eb8a78302c5716f9311ebba2818
|
|
| MD5 |
91edd582db89d76845c35dbde110fab1
|
|
| BLAKE2b-256 |
c1a8d6580781175c3f7e2ec31b99a82393eb807d500adc805e5a896f2f709dbb
|