SubclassDict
A TypeDict that allows subclasses of type keys to be used as keys. When a key is not found, it automatically looks for the closest superclass key, enabling polymorphic behavior in dictionary lookups.
Features
- Polymorphic lookups: Subclasses automatically find values stored under their parent class keys
- Performance optimized: Cached subclass lookups for O(1) performance after first access
- Full dict compatibility: Implements all standard dictionary methods
- Type safe: Full type hints with generic support
- Comprehensive: Includes utility functions for class hierarchy traversal
Installation
pip install subclassdict
Quick Start
from subclassdict import SubclassDict
# Define a class hierarchy
class Animal:
pass
class Dog(Animal):
pass
class Puppy(Dog):
pass
# Create a SubclassDict
d = SubclassDict()
# Store values under parent classes
d[Animal] = "I'm an animal"
d[Dog] = "I'm a dog"
# Subclasses can access parent values
print(d[Puppy]) # Output: "I'm a dog" (finds Dog's value)
print(d[Dog]) # Output: "I'm a dog" (direct match)
print(d[Animal]) # Output: "I'm an animal" (direct match)
Real-World Examples
Plugin System
from subclassdict import SubclassDict
class Plugin:
def __init__(self, name: str):
self.name = name
class DatabasePlugin(Plugin): pass
class WebPlugin(Plugin): pass
class APIPlugin(WebPlugin): pass
# Register plugins
plugins = SubclassDict()
plugins[Plugin] = "base_plugin"
plugins[DatabasePlugin] = "database_plugin"
plugins[WebPlugin] = "web_plugin"
# Plugin resolution
print(plugins[APIPlugin]) # "web_plugin" (finds WebPlugin)
print(plugins[DatabasePlugin]) # "database_plugin" (direct match)
Event Handling
from subclassdict import SubclassDict
class Event: pass
class UserEvent(Event): pass
class LoginEvent(UserEvent): pass
class LogoutEvent(UserEvent): pass
# Register event handlers
handlers = SubclassDict()
handlers[Event] = "generic_handler"
handlers[UserEvent] = "user_handler"
# Event handling
print(handlers[LoginEvent]) # "user_handler"
print(handlers[LogoutEvent]) # "user_handler"
print(handlers[Event]) # "generic_handler"
Serialization System
from subclassdict import SubclassDict
class Serializable: pass
class JSONSerializable(Serializable): pass
class XMLSerializable(Serializable): pass
class JSONAPISerializable(JSONSerializable): pass
# Register serializers
serializers = SubclassDict()
serializers[Serializable] = "generic_serializer"
serializers[JSONSerializable] = "json_serializer"
serializers[XMLSerializable] = "xml_serializer"
# Serialization
print(serializers[JSONAPISerializable]) # "json_serializer"
print(serializers[XMLSerializable]) # "xml_serializer"
API Reference
SubclassDict
The main class that provides polymorphic dictionary behavior.
Methods
__getitem__(key): Get value by key, with subclass lookup__setitem__(key, value): Set value for key__delitem__(key): Delete key__contains__(key): Check if key exists (with subclass lookup)get(key, default=None): Get value with defaultsetdefault(key, default=None): Set default if key not presentpop(key, default=None): Pop value with defaultpopitem(): Pop arbitrary itemcopy(): Create shallow copyclear(): Clear all items and cache
Performance Characteristics
- First lookup: O(n) where n is the number of keys (searches for superclass)
- Subsequent lookups: O(1) (cached)
- Memory: O(k) where k is the number of unique keys accessed
Utility Functions
subclasses(cls)
Get all subclasses of a class recursively.
from subclassdict import subclasses
class Animal: pass
class Dog(Animal): pass
class Cat(Animal): pass
class Puppy(Dog): pass
print(subclasses(Animal)) # [Dog, Cat, Puppy]
print(subclasses(Dog)) # [Puppy]
Advanced Usage
Custom Type Keys
SubclassDict works with any hashable type, not just classes:
d = SubclassDict()
d[str] = "string type"
d[int] = "integer type"
# Non-type keys work normally (no subclass lookup)
d["string"] = "string value"
Cache Management
The cache is automatically managed, but you can inspect it:
d = SubclassDict()
d[Animal] = "animal"
# Access to populate cache
d[Dog] # This populates the cache
# Inspect cache
print(d._subclass_cache) # {Dog: Animal}
Performance with Large Hierarchies
SubclassDict is optimized for large class hierarchies:
# Create a deep hierarchy
classes = []
prev_class = None
for i in range(100):
if prev_class is None:
new_class = type(f'Class{i}', (), {})
else:
new_class = type(f'Class{i}', (prev_class,), {})
classes.append(new_class)
prev_class = new_class
d = SubclassDict()
d[classes[0]] = "root"
# Even deep lookups are fast after caching
print(d[classes[-1]]) # Fast O(1) lookup
Comparison with Alternatives
| Feature | SubclassDict | Regular dict | Custom lookup |
|---|---|---|---|
| Polymorphic lookups | ✅ Automatic | ❌ Manual | ✅ Manual |
| Performance | ✅ O(1) cached | ✅ O(1) | ❌ O(n) |
| Type safety | ✅ Full typing | ✅ Full typing | ❌ Manual |
| Standard dict methods | ✅ All methods | ✅ All methods | ❌ Manual |
| Memory usage | ✅ Minimal | ✅ Minimal | ❌ Can be high |
Development
Setup
git clone https://github.com/eddiethedean/subclassdict.git
cd subclassdict
pip install -e .[dev]
Running Tests
pytest
Code Quality
# Linting
ruff check .
# Formatting
ruff format .
# Type checking
mypy src/
Pre-commit Hooks
pre-commit install
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Run the test suite
- Submit a pull request
See CONTRIBUTING.md for detailed guidelines.
License
MIT License - see LICENSE for details.
Changelog
See CHANGELOG.md for version history.
Troubleshooting
Common Issues
Q: Why doesn't my subclass find the parent's value? A: Make sure the parent class key exists in the dictionary. SubclassDict only looks up the hierarchy, not down.
Q: Performance seems slow with many keys? A: The first lookup for each key is O(n), but subsequent lookups are O(1) due to caching.
Q: Can I use non-class keys? A: Yes, but subclass lookup only works with type objects. Non-type keys work like a regular dictionary.
Performance Tips
- Pre-populate cache: Access keys you'll use frequently to populate the cache
- Use specific keys: Store values under the most specific class possible
- Avoid deep hierarchies: Very deep inheritance chains can impact first-lookup performance
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: GitHub Pages
Metadata
Release files for subclassdict 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| subclassdict-0.1.1.tar.gz | 15.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| subclassdict-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.7 kB
Release files / subclassdict-0.1.1.tar.gz
| Download URL | subclassdict-0.1.1.tar.gz |
|---|---|
| Size | 15.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d5b84edbcee800182cf8f1782f7e28b05b25020bf6fcb3c504686f12a94ccc45
|
|
BLAKE2b-256 checksum How to use checksums |
e08ef02e2255a12fbf36e971a0878a8d57cff9f7ba0212d6ac8de7fddc62e45c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|
Release files / subclassdict-0.1.1-py3-none-any.whl
| Download URL | subclassdict-0.1.1-py3-none-any.whl |
|---|---|
| Size | 8.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
89839c6cc1a6a5082bb60d3e42d0711ae6edd0ccd3153b39f7bbbbf5c303846a
|
|
BLAKE2b-256 checksum How to use checksums |
717d048b619d25f48891a17f3e053c5582cc6ba8528f47f9f16cd996d821623a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|