Skip to main content

Hash consing for flyweight instance management.

Project description

Python versions PyPI version PyPI status Checked with Mypy Documentation Status Python package status standard-readme compliant

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

LGPL © Hashberg Ltd.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hashcons-0.1.0.tar.gz (171.0 kB view details)

Uploaded Source

Built Distribution

hashcons-0.1.0-py3-none-any.whl (17.3 kB view details)

Uploaded Python 3

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

Hashes for hashcons-0.1.0.tar.gz
Algorithm Hash digest
SHA256 098ad772634942cf226acf667ba1bf235e73489d722ebce3b914ef5f681e86c4
MD5 29fa8d5309e5690e2cb9098e3821a782
BLAKE2b-256 6b27667e3855f605ba355f4ab60302eed89801f6921244b65901729af8d1c2d6

See more details on using hashes here.

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

Hashes for hashcons-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b9a5248fefe8160960d661d6b9af256827a7e0f21e028fa334226014ec64b0e3
MD5 c8bf9dbd39598833b31b10f16d3a1bed
BLAKE2b-256 36ed74c3c53bb047a344794f509f8af6eb0826d1f127ab724c37b99d124f8b10

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page