Oven logo

Oven

Published

A simple immutable dictionary

pip install frozendict

Package Downloads

Weekly DownloadsMonthly Downloads

Requires Python

>=3.6

Dependencies

    frozendict

    Table of Contents

    Introduction

    Welcome, fellow programmer, to the house of frozendict and deepfreeze!

    frozendict is a simple immutable dictionary. It's fast as dict, and sometimes faster!

    Unlike other similar implementations, immutability is guaranteed: you can't change the internal variables of the class, and they are all immutable objects. Reinvoking __init__ does not alter the object.

    The API is the same as dict, without methods that can change the immutability. So it supports also fromkeys, unlike other implementations. Furthermore, it can be pickled, unpickled and have a hash, if all values are hashable.

    You can also add any dict to a frozendict using the | operator. The result is a new frozendict.

    Install

    You can install frozendict by simply typing in a command line:

    pip install frozendict
    

    The C Extension is optional by default from version 2.3.5. You can make it mandatory using:

    CIBUILDWHEEL=1 pip install frozendict
    

    On the contrary, if you want the pure py implementation:

    FROZENDICT_PURE_PY=1 pip install frozendict
    

    API

    frozendict API

    The API is the same of dict of Python 3.10, without the methods and operands which alter the map. Additionally, frozendict supports these methods:

    __hash__()

    If all the values of the frozendict are hashable, returns a hash, otherwise raises a TypeError.

    set(key, value)

    It returns a new frozendict. If key is already in the original frozendict, the new one will have it with the new value associated. Otherwise, the new frozendict will contain the new (key, value) item.

    delete(key)

    It returns a new frozendict without the item corresponding to the key. If the key is not present, a KeyError is raised.

    setdefault(key[, default])

    If key is already in frozendict, the object itself is returned unchanged. Otherwise, the new frozendict will contain the new (key, default) item. The parameter default defaults to None.

    key([index])

    It returns the key at the specified index (determined by the insertion order). If index is not passed, it defaults to 0. If the index is negative, the position will be the size of the frozendict + index

    value([index])

    Same as key(index), but it returns the value at the given index.

    item([index])

    Same as key(index), but it returns a tuple with (key, value) at the given index.

    deepfreeze API

    The frozendict module has also these static methods:

    frozendict.deepfreeze(o, custom_converters = None, custom_inverse_converters = None)

    Converts the object and all the objects nested in it, into their immutable counterparts.

    The conversion map is in getFreezeConversionMap().

    You can register a new conversion using register() You can also pass a map of custom converters with custom_converters and a map of custom inverse converters with custom_inverse_converters, without using register().

    By default, if the type is not registered and has a __dict__ attribute, it's converted to the frozendict of that __dict__.

    This function assumes that hashable == immutable (that is not always true).

    This function uses recursion, with all the limits of recursions in Python.

    Where is a good old tail call when you need it?

    frozendict.register(to_convert, converter, *, inverse = False)

    Adds a converter for a type to_convert. converter must be callable. The new converter will be used by deepfreeze() and has precedence over any previous converter.

    If to_covert has already a converter, a FreezeWarning is raised.

    If inverse is True, the conversion is considered from an immutable type to a mutable one. This make it possible to convert mutable objects nested in the registered immutable one.

    frozendict.unregister(type, inverse = False)

    Unregister a type from custom conversion. If inverse is True, the unregistered conversion is an inverse conversion (see register()).

    Examples

    frozendict examples

    from frozendict import frozendict
    
    fd = frozendict(Guzzanti = "Corrado", Hicks = "Bill")
    
    print(fd)
    # frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill'})
    
    frozendict({"Guzzanti": "Corrado", "Hicks": "Bill"})
    # frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill'})
    
    hash(fd)
    # 5833699487320513741
    
    fd_unhashable = frozendict({1: []})
    hash(fd_unhashable)
    # TypeError: Not all values are hashable.
    
    frozendict({frozendict(nested = 4, key = 2): 42})
    # frozendict({frozendict({'nested': 4, 'key': 2}): 42})
    
    fd | {1: 2}
    # frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill', 1: 2})
    
    fd.set(1, 2)
    # frozendict.frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill', 1: 2})
    
    fd.set("Guzzanti", "Sabina")
    # frozendict.frozendict({'Guzzanti': 'Sabina', 'Hicks': 'Bill'})
    
    fd.delete("Guzzanti")
    # frozendict.frozendict({'Hicks': 'Bill'})
    
    fd.setdefault("Guzzanti", "Sabina")
    # frozendict.frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill'})
    
    fd.setdefault(1, 2)
    # frozendict.frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill', 1: 2})
    
    fd.key()
    # 'Guzzanti'
    
    fd.value(1)
    # 'Bill'
    
    fd.item(-1)
    # (1, 2)
    
    print(fd["Guzzanti"])
    # Corrado
    
    fd["Brignano"]
    # KeyError: 'Brignano'
    
    len(fd)
    # 2
    
    "Guzzanti" in fd
    # True
    
    "Guzzanti" not in fd
    # False
    
    "Brignano" in fd
    # False
    
    fd5 = frozendict(fd)
    id_fd5 = id(fd5)
    fd5 |= {1: 2}
    fd5
    # frozendict.frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill', 1: 2})
    id(fd5) != id_fd5
    # True
    
    fd2 = fd.copy()
    fd2 == fd
    # True
    
    fd3 = frozendict(fd)
    fd3 == fd
    # True
    
    fd4 = frozendict({"Hicks": "Bill", "Guzzanti": "Corrado"})
    
    print(fd4)
    # frozendict({'Hicks': 'Bill', 'Guzzanti': 'Corrado'})
    
    fd4 == fd
    # True
    
    import pickle
    fd_unpickled = pickle.loads(pickle.dumps(fd))
    print(fd_unpickled)
    # frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill'})
    fd_unpickled == fd
    # True
    
    frozendict(Guzzanti="Corrado", Hicks="Bill")
    # frozendict({'Guzzanti': 'Corrado', 'Hicks': 'Bill'}
    
    fd.get("Guzzanti")
    # 'Corrado'
    
    print(fd.get("Brignano"))
    # None
    
    tuple(fd.keys())
    # ('Guzzanti', 'Hicks')
    
    tuple(fd.values())
    # ('Corrado', 'Bill')
    
    tuple(fd.items())
    # (('Guzzanti', 'Corrado'), ('Hicks', 'Bill'))
    
    frozendict.fromkeys(["Corrado", "Sabina"], "Guzzanti")
    # frozendict({'Corrado': 'Guzzanti', 'Sabina': 'Guzzanti'})
    
    iter(fd)
    # <dict_keyiterator object at 0x7feb75c49188>
    
    fd["Guzzanti"] = "Caterina"
    # TypeError: 'frozendict' object doesn't support item assignment
    

    deepfreeze examples

    import frozendict as cool
    
    from frozendict import frozendict
    from array import array
    from collections import OrderedDict
    from types import MappingProxyType
    
    class A:
        def __init__(self, x):
            self.x = x
    
    a = A(3)
            
    o = {"x": [
        5, 
        frozendict(y = {5, "b", memoryview(b"b")}), 
        array("B", (0, 1, 2)), 
        OrderedDict(a=bytearray(b"a")),
        MappingProxyType({2: []}),
        a
    ]}
    
    cool.deepfreeze(o)
    # frozendict(x = (
    #     5, 
    #     frozendict(y = frozenset({5, "b", memoryview(b"b")})), 
    #     (0, 1, 2), 
    #     frozendict(a = b'a'),
    #     MappingProxyType({2: ()}),
    #     frozendict(x = 3),
    # ))
    
    

    Building

    You can build frozendict directly from the code, using

    python3 setup.py bdist_wheel
    

    The C Extension is optional by default from version 2.3.5. You can make it mandatory by passing the environment variable CIBUILDWHEEL with value 1

    On the contrary, if you want the pure py implementation, you can pass the env var FROZENDICT_PURE_PY with value 1

    Benchmarks

    Some benchmarks between dict and frozendict[1]:

    ################################################################################
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(d)`;         Size:    5; Keys: str; Type:       dict; Time: 8.02e-08; Sigma: 4e-09
    Name: `constructor(d)`;         Size:    5; Keys: str; Type: frozendict; Time: 8.81e-08; Sigma: 3e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(d)`;         Size:    5; Keys: int; Type:       dict; Time: 7.96e-08; Sigma: 5e-09
    Name: `constructor(d)`;         Size:    5; Keys: int; Type: frozendict; Time: 8.97e-08; Sigma: 2e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(d)`;         Size: 1000; Keys: str; Type:       dict; Time: 6.38e-06; Sigma: 9e-08
    Name: `constructor(d)`;         Size: 1000; Keys: str; Type: frozendict; Time: 6.21e-06; Sigma: 2e-07
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(d)`;         Size: 1000; Keys: int; Type:       dict; Time: 3.49e-06; Sigma: 3e-07
    Name: `constructor(d)`;         Size: 1000; Keys: int; Type: frozendict; Time: 3.48e-06; Sigma: 2e-07
    ################################################################################
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(kwargs)`;    Size:    5; Keys: str; Type:       dict; Time: 2.40e-07; Sigma: 1e-09
    Name: `constructor(kwargs)`;    Size:    5; Keys: str; Type: frozendict; Time: 2.48e-07; Sigma: 2e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(kwargs)`;    Size: 1000; Keys: str; Type:       dict; Time: 4.80e-05; Sigma: 1e-06
    Name: `constructor(kwargs)`;    Size: 1000; Keys: str; Type: frozendict; Time: 2.90e-05; Sigma: 7e-07
    ################################################################################
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(seq2)`;      Size:    5; Keys: str; Type:       dict; Time: 2.01e-07; Sigma: 9e-10
    Name: `constructor(seq2)`;      Size:    5; Keys: str; Type: frozendict; Time: 2.50e-07; Sigma: 1e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(seq2)`;      Size:    5; Keys: int; Type:       dict; Time: 2.18e-07; Sigma: 2e-09
    Name: `constructor(seq2)`;      Size:    5; Keys: int; Type: frozendict; Time: 2.73e-07; Sigma: 1e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(seq2)`;      Size: 1000; Keys: str; Type:       dict; Time: 4.29e-05; Sigma: 6e-07
    Name: `constructor(seq2)`;      Size: 1000; Keys: str; Type: frozendict; Time: 4.33e-05; Sigma: 6e-07
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(seq2)`;      Size: 1000; Keys: int; Type:       dict; Time: 3.04e-05; Sigma: 4e-07
    Name: `constructor(seq2)`;      Size: 1000; Keys: int; Type: frozendict; Time: 3.45e-05; Sigma: 4e-07
    ################################################################################
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(o)`;         Size:    5; Keys: str; Type:       dict; Time: 7.93e-08; Sigma: 3e-09
    Name: `constructor(o)`;         Size:    5; Keys: str; Type: frozendict; Time: 2.41e-08; Sigma: 6e-10
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(o)`;         Size:    5; Keys: int; Type:       dict; Time: 7.94e-08; Sigma: 5e-09
    Name: `constructor(o)`;         Size:    5; Keys: int; Type: frozendict; Time: 2.41e-08; Sigma: 6e-10
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(o)`;         Size: 1000; Keys: str; Type:       dict; Time: 6.18e-06; Sigma: 3e-07
    Name: `constructor(o)`;         Size: 1000; Keys: str; Type: frozendict; Time: 2.41e-08; Sigma: 6e-10
    ////////////////////////////////////////////////////////////////////////////////
    Name: `constructor(o)`;         Size: 1000; Keys: int; Type:       dict; Time: 3.47e-06; Sigma: 2e-07
    Name: `constructor(o)`;         Size: 1000; Keys: int; Type: frozendict; Time: 2.41e-08; Sigma: 6e-10
    ################################################################################
    ////////////////////////////////////////////////////////////////////////////////
    Name: `o.copy()`;               Size:    5; Keys: str; Type:       dict; Time: 7.28e-08; Sigma: 2e-09
    Name: `o.copy()`;               Size:    5; Keys: str; Type: frozendict; Time: 3.18e-08; Sigma: 2e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `o.copy()`;               Size:    5; Keys: int; Type:       dict; Time: 7.21e-08; Sigma: 4e-09
    Name: `o.copy()`;               Size:    5; Keys: int; Type: frozendict; Time: 3.32e-08; Sigma: 2e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `o.copy()`;               Size: 1000; Keys: str; Type:       dict; Time: 6.16e-06; Sigma: 3e-07
    Name: `o.copy()`;               Size: 1000; Keys: str; Type: frozendict; Time: 3.18e-08; Sigma: 2e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `o.copy()`;               Size: 1000; Keys: int; Type:       dict; Time: 3.46e-06; Sigma: 1e-07
    Name: `o.copy()`;               Size: 1000; Keys: int; Type: frozendict; Time: 3.18e-08; Sigma: 2e-09
    ################################################################################
    ////////////////////////////////////////////////////////////////////////////////
    Name: `o == o`;                 Size:    5; Keys: str; Type:       dict; Time: 7.23e-08; Sigma: 8e-10
    Name: `o == o`;                 Size:    5; Keys: str; Type: frozendict; Time: 2.44e-08; Sigma: 2e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `o == o`;                 Size:    5; Keys: int; Type:       dict; Time: 7.30e-08; Sigma: 1e-09
    Name: `o == o`;                 Size:    5; Keys: int; Type: frozendict; Time: 2.44e-08; Sigma: 2e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `o == o`;                 Size: 1000; Keys: str; Type:       dict; Time: 1.38e-05; Sigma: 1e-07
    Name: `o == o`;                 Size: 1000; Keys: str; Type: frozendict; Time: 2.44e-08; Sigma: 2e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `o == o`;                 Size: 1000; Keys: int; Type:       dict; Time: 1.05e-05; Sigma: 7e-08
    Name: `o == o`;                 Size: 1000; Keys: int; Type: frozendict; Time: 2.44e-08; Sigma: 2e-09
    ################################################################################
    ////////////////////////////////////////////////////////////////////////////////
    Name: `for x in o`;             Size:    5; Keys: str; Type:       dict; Time: 7.33e-08; Sigma: 2e-09
    Name: `for x in o`;             Size:    5; Keys: str; Type: frozendict; Time: 6.70e-08; Sigma: 1e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `for x in o`;             Size:    5; Keys: int; Type:       dict; Time: 7.33e-08; Sigma: 2e-09
    Name: `for x in o`;             Size:    5; Keys: int; Type: frozendict; Time: 6.70e-08; Sigma: 1e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `for x in o`;             Size: 1000; Keys: str; Type:       dict; Time: 8.84e-06; Sigma: 5e-08
    Name: `for x in o`;             Size: 1000; Keys: str; Type: frozendict; Time: 7.06e-06; Sigma: 6e-08
    ////////////////////////////////////////////////////////////////////////////////
    Name: `for x in o`;             Size: 1000; Keys: int; Type:       dict; Time: 8.67e-06; Sigma: 7e-08
    Name: `for x in o`;             Size: 1000; Keys: int; Type: frozendict; Time: 6.94e-06; Sigma: 3e-08
    ################################################################################
    ////////////////////////////////////////////////////////////////////////////////
    Name: `for x in o.values()`;    Size:    5; Keys: str; Type:       dict; Time: 7.28e-08; Sigma: 9e-10
    Name: `for x in o.values()`;    Size:    5; Keys: str; Type: frozendict; Time: 6.48e-08; Sigma: 8e-10
    ////////////////////////////////////////////////////////////////////////////////
    Name: `for x in o.values()`;    Size:    5; Keys: int; Type:       dict; Time: 7.25e-08; Sigma: 1e-09
    Name: `for x in o.values()`;    Size:    5; Keys: int; Type: frozendict; Time: 6.45e-08; Sigma: 1e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `for x in o.values()`;    Size: 1000; Keys: str; Type:       dict; Time: 9.06e-06; Sigma: 5e-07
    Name: `for x in o.values()`;    Size: 1000; Keys: str; Type: frozendict; Time: 7.04e-06; Sigma: 4e-08
    ////////////////////////////////////////////////////////////////////////////////
    Name: `for x in o.values()`;    Size: 1000; Keys: int; Type:       dict; Time: 9.53e-06; Sigma: 3e-08
    Name: `for x in o.values()`;    Size: 1000; Keys: int; Type: frozendict; Time: 6.97e-06; Sigma: 3e-08
    ################################################################################
    ////////////////////////////////////////////////////////////////////////////////
    Name: `for x in o.items()`;     Size:    5; Keys: str; Type:       dict; Time: 1.13e-07; Sigma: 3e-09
    Name: `for x in o.items()`;     Size:    5; Keys: str; Type: frozendict; Time: 1.16e-07; Sigma: 2e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `for x in o.items()`;     Size:    5; Keys: int; Type:       dict; Time: 1.14e-07; Sigma: 3e-09
    Name: `for x in o.items()`;     Size:    5; Keys: int; Type: frozendict; Time: 1.17e-07; Sigma: 2e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `for x in o.items()`;     Size: 1000; Keys: str; Type:       dict; Time: 1.53e-05; Sigma: 3e-07
    Name: `for x in o.items()`;     Size: 1000; Keys: str; Type: frozendict; Time: 1.53e-05; Sigma: 4e-07
    ////////////////////////////////////////////////////////////////////////////////
    Name: `for x in o.items()`;     Size: 1000; Keys: int; Type:       dict; Time: 1.53e-05; Sigma: 3e-07
    Name: `for x in o.items()`;     Size: 1000; Keys: int; Type: frozendict; Time: 1.55e-05; Sigma: 4e-07
    ################################################################################
    ////////////////////////////////////////////////////////////////////////////////
    Name: `pickle.dumps(o)`;        Size:    5; Keys: str; Type:       dict; Time: 6.82e-07; Sigma: 2e-08
    Name: `pickle.dumps(o)`;        Size:    5; Keys: str; Type: frozendict; Time: 2.86e-06; Sigma: 1e-07
    ////////////////////////////////////////////////////////////////////////////////
    Name: `pickle.dumps(o)`;        Size:    5; Keys: int; Type:       dict; Time: 4.77e-07; Sigma: 2e-08
    Name: `pickle.dumps(o)`;        Size:    5; Keys: int; Type: frozendict; Time: 2.72e-06; Sigma: 8e-08
    ////////////////////////////////////////////////////////////////////////////////
    Name: `pickle.dumps(o)`;        Size: 1000; Keys: str; Type:       dict; Time: 1.24e-04; Sigma: 4e-06
    Name: `pickle.dumps(o)`;        Size: 1000; Keys: str; Type: frozendict; Time: 1.92e-04; Sigma: 5e-06
    ////////////////////////////////////////////////////////////////////////////////
    Name: `pickle.dumps(o)`;        Size: 1000; Keys: int; Type:       dict; Time: 2.81e-05; Sigma: 6e-07
    Name: `pickle.dumps(o)`;        Size: 1000; Keys: int; Type: frozendict; Time: 7.37e-05; Sigma: 1e-06
    ################################################################################
    ////////////////////////////////////////////////////////////////////////////////
    Name: `pickle.loads(dump)`;     Size:    5; Keys: str; Type:       dict; Time: 9.08e-07; Sigma: 6e-09
    Name: `pickle.loads(dump)`;     Size:    5; Keys: str; Type: frozendict; Time: 1.79e-06; Sigma: 9e-08
    ////////////////////////////////////////////////////////////////////////////////
    Name: `pickle.loads(dump)`;     Size:    5; Keys: int; Type:       dict; Time: 4.46e-07; Sigma: 6e-09
    Name: `pickle.loads(dump)`;     Size:    5; Keys: int; Type: frozendict; Time: 1.32e-06; Sigma: 7e-08
    ////////////////////////////////////////////////////////////////////////////////
    Name: `pickle.loads(dump)`;     Size: 1000; Keys: str; Type:       dict; Time: 1.57e-04; Sigma: 8e-06
    Name: `pickle.loads(dump)`;     Size: 1000; Keys: str; Type: frozendict; Time: 1.69e-04; Sigma: 7e-06
    ////////////////////////////////////////////////////////////////////////////////
    Name: `pickle.loads(dump)`;     Size: 1000; Keys: int; Type:       dict; Time: 5.97e-05; Sigma: 5e-06
    Name: `pickle.loads(dump)`;     Size: 1000; Keys: int; Type: frozendict; Time: 6.68e-05; Sigma: 2e-06
    ################################################################################
    ////////////////////////////////////////////////////////////////////////////////
    Name: `class.fromkeys()`;       Size:    5; Keys: str; Type:       dict; Time: 1.88e-07; Sigma: 1e-09
    Name: `class.fromkeys()`;       Size:    5; Keys: str; Type: frozendict; Time: 2.22e-07; Sigma: 7e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `class.fromkeys()`;       Size:    5; Keys: int; Type:       dict; Time: 2.08e-07; Sigma: 6e-09
    Name: `class.fromkeys()`;       Size:    5; Keys: int; Type: frozendict; Time: 2.44e-07; Sigma: 2e-09
    ////////////////////////////////////////////////////////////////////////////////
    Name: `class.fromkeys()`;       Size: 1000; Keys: str; Type:       dict; Time: 4.05e-05; Sigma: 4e-06
    Name: `class.fromkeys()`;       Size: 1000; Keys: str; Type: frozendict; Time: 3.84e-05; Sigma: 5e-07
    ////////////////////////////////////////////////////////////////////////////////
    Name: `class.fromkeys()`;       Size: 1000; Keys: int; Type:       dict; Time: 2.93e-05; Sigma: 7e-07
    Name: `class.fromkeys()`;       Size: 1000; Keys: int; Type: frozendict; Time: 3.08e-05; Sigma: 2e-06
    ################################################################################
    

    [1] Benchmarks done under Linux 64 bit, Python 3.10.2, using the C Extension.