Hash consing for flyweight instance management.
Project description
A simple implementation of flyweight instance management using hash consing.
Install
You can install the latest release from PyPI:
$ pip install -U hashcons
Usage
The InstanceStore class can be used to create instance stores, i.e. flyweight factories. A common pattern is to use an instance store for each flyweight class, stored as a protected/private class attribute:
from hashcons import InstanceStore
from typing import ClassVar
from typing_extensions import Self
class MyFrac:
_store: ClassVar[InstanceStore] = InstanceStore()
... # <- class body here
In the constructor for the class, the store is queried by calling the instance method with:
the specific subclass cls, because instances of different subclasses are stored separately;
an instance_key, derived from the constructor arguments and uniquely identifying a flyweight instance.
The store returns the instance of the class for given instance key, or None if none exists.
class MyFrac:
_store: ClassVar[InstanceStore] = InstanceStore()
def __new__(cls, num: int, den: int) -> Self:
instance_key = (num, den) # instance key derived from constructor args
with MyFrac._store.instance(cls, instance_key) as self:
if self is None:
... # <- instance construction logic here
return self
... # <- class body here
If no instance exists, one should be created, usually via a call to the super constructor. When a new instance is created, its attributes should be validated and set. The instance is then registered by passing it to the register method of the store.
class MyFrac:
_store: ClassVar[InstanceStore] = InstanceStore()
__num: int
__den: int
def __new__(cls, num: int, den: int) -> Self:
instance_key = (num, den)
with MyFrac._store.instance(cls, instance_key) as self:
if self is None: # if no instance with given key exists
# 1. Validate constructor arguments:
if den == 0:
raise ZeroDivisionError()
# 2. Create the new instance:
self = super().__new__(cls)
# 3. Set instance attributes:
self.__num = num
self.__den = den
# 4. Register the instance in the store:
MyFrac._store.register(self)
return self
... # <- class body here
Note that it is safe to raise exceptions as part of the instance construction process, as the instance context manager will take care of performing the necessary cleanup. The code snippet below exemplifies validation, new instance creation, and instance reuse.
try:
inf = MyFrac(1, 0) # does not pass constructor validation
except ZeroDivisionError:
pass
x = MyFrac(10, 3) # new instance with key=(10, 3) created
x1 = MyFrac(10, 3) # instance with key=(10, 3) exists
assert x is x1 # a unique instance exists for each (cls, instance_key) pair
Because subclasses are stored separately, flyweight classes support inheritance. Subclasses should use the instance context manager for the flyweight superclass’s store, which will return an instance of the subclass for the given instance key, if one exists. If a new instance of the subclass must be created, the subclass can do so by making a call to the superclass constructor:
1. The instance context is entered in the superclass constructor: it recognises that it is entered within another instance context for the same store, it presumes that this is because the superclass constructor was called by a subclass, and it returns None to signal to the superclass constructor that a new instance is needed. 2. The superclass constructor creates a new instance, sets its attributes, registers it, and returns it to the subclass constructor. 3. The subclass constructor takes the instance from the superclass constructor, sets its attribtues, and returns it.
Note that the subclass’s constructor should not call register when creating a new instance: by the time the superclass constructor returns, the new instance has already been registered. The code snippet below exemplifies subclass usage.
class MyNamedFrac(MyFrac):
__name: str
def __new__(cls, num: int, den: int, name: str) -> Self:
key = (num, den, name)
with MyFrac._store.instance(cls, key) as self:
if self is None:
self = super().__new__(cls, num, den)
self.__name = name
return self
y = MyNamedFrac(10, 3, "y") # new instance with key=(10, 3, 'y') created
y1 = MyNamedFrac(10, 3, "y") # instance with key=(10, 3, 'y') returned
z = MyNamedFrac(10, 3, "z") # new instance with key=(10, 3, 'z') created
assert y is not x
assert y is y1
assert y is not z
Subclasses can perform their validation both before and after the superclass constructor call. The ability to perform validation after is important in cases where subclass validation depends on superclass validation, e.g. because it uses properties/methods of the partially initialised instance. There is no issue with errors being raised after the superclass constructor has returned: the new instance as been registered by the superclass constructor, but it will be unregistered by the subclass instance context if it is exited in error.
API
The full API documentation is available at https://hashcons.readthedocs.io/
License
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
File details
Details for the file hashcons-0.1.0.tar.gz
.
File metadata
- Download URL: hashcons-0.1.0.tar.gz
- Upload date:
- Size: 171.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.12.3
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 098ad772634942cf226acf667ba1bf235e73489d722ebce3b914ef5f681e86c4 |
|
MD5 | 29fa8d5309e5690e2cb9098e3821a782 |
|
BLAKE2b-256 | 6b27667e3855f605ba355f4ab60302eed89801f6921244b65901729af8d1c2d6 |
File details
Details for the file hashcons-0.1.0-py3-none-any.whl
.
File metadata
- Download URL: hashcons-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.12.3
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | b9a5248fefe8160960d661d6b9af256827a7e0f21e028fa334226014ec64b0e3 |
|
MD5 | c8bf9dbd39598833b31b10f16d3a1bed |
|
BLAKE2b-256 | 36ed74c3c53bb047a344794f509f8af6eb0826d1f127ab724c37b99d124f8b10 |