Skip to content

Type or key restricted and resettable Python dictionary

License

Notifications You must be signed in to change notification settings

kihiyuki/python-rsdict

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rsdict

Actions Badge - Python package PyPI version Downloads

rsdict is a restricted and resetable dictionary, a subclass of dict (inherits from built-in dictionary).

>>> from rsdict import rsdict

>>> d = {"foo": 0, "bar": "baz"}
>>> rd = rsdict(d)
>>> rd
rsdict({'foo': 0, 'bar': 'baz'}, frozen=False, fixkey=True, fixtype=True, cast=False)

# fixkey=True: key restriction
>>> rd["newkey"] = 1
AttributeError
# fixtype=True: type restriction
>>> rd["foo"] = "str.value"
TypeError

>>> rd["foo"] = 999
>>> rd == d
False
# reset values to initial
>>> rd.reset()
>>> rd == d
True

Installation

pip install rsdict

Features

  • Type-restrict(able): If activated, every type of value is fixed to its initial type.
  • Key-restrict(able): If activated, cannot add or delete keys.
  • Resettable: to initial value(s).

Arguments

rsdict(items, frozen=False, fixkey=True, fixtype=True, cast=False)

  • items (dict): Initial items (data). Built-in dictionary only. kwargs are not supported.
  • frozen (bool, optional): If True, the instance will be frozen (immutable).
  • fixkey (bool, optional): If True, cannot add or delete keys.
  • fixtype (bool, optional): If True, cannot change type of keys.
  • cast (bool, optional): If False, cast to initial type (if possible). If True, allow only the same type of initial value.

Subclasses

# rsdict(frozen=True) as default
from rsdict import rsdict_frozen as rsdict

# rsdict(fixkey=False, fixtype=False) as default
from rsdict import rsdict_unfix as rsdict

# rsdict(fixkey=True, fixtype=False) as default
from rsdict import rsdict_fixkey as rsdict

# rsdict(fixkey=False, fixtype=True) as default
from rsdict import rsdict_fixtype as rsdict

Additional methods

  • set(key, value): Alias of __setitem__.
  • to_dict() -> dict: Convert to dict instance.
  • reset(key: Optional[Any]) -> None: Reset value to the initial value. If key is None, reset all values.
  • is_changed(key: Optional[Any]) -> bool: If True, the values are changed from initial. If key is not None, check the key only.
  • get_initial(key: Optional[Any]) -> dict | Any: Return initial value(s). If key is None, Return dict of all initial values.

Examples

Create (Initialize)

>>> from rsdict import rsdict

>>> d = dict(
...     name = "John",
...     enable = True,
...     count = 0,
... )
>>> rd = rsdict(d)
>>> rd
rsdict({'name': 'John', 'enable': True, 'count': 0},
        frozen=False, fixkey=True, fixtype=False)

>>> type(rd) is dict
False
>>> isinstance(rd, dict)
True
>>> rd.frozen
False

Get

Same as dict.

>>> rd["count"] == d["count"]
True
>>> rd["xyz"]
KeyError

>>> rd.get("count") == d.get("count")
True
>>> rd.get("xyz")
None

Set

>>> rd["enable"] = False
>>> rd.set("enable", False)
# If frozen, always raise an exception.
>>> rd_frozen = rsdict(d, frozen=True)
>>> rd_frozen["count"] = 2
AttributeError
# If fixtype and not cast, different-type value raise an exception.
>>> rd["count"] = "2"
TypeError

# If fixtype and cast, cast value to initial type.
>>> rd_cast = rsdict(d, cast=True)
>>> rd_cast["count"] = "2"
>>> rd_cast["count"]
2
>>> rd_cast["count"] = "abc"
ValueError

# If not fixtype, anything can be set.
>>> rd_typefree = rsdict(d, fixtype=False)
>>> rd_typefree["count"] = "2"
>>> rd_typefree["count"]
'2'
# If fixkey, setting with a new key raises an exception.
>>> rd["location"] = 9
AttributeError

# If not fixkey, a new key can be set.
>>> rd_keyfree = rsdict(d, fixkey=False)
>>> rd_keyfree["location"] = 9
>>> rd_keyfree["location"]
9

Delete

# If frozen or fixkey, deleting key raises an exception.
>>> del rd["count"]
AttributeError

# Else, delete both current and initial values.
>>> rd_keyfree = rsdict(dict(a=1, b=2, c=3), fixkey=False)
>>> del rd_keyfree["b"]
>>> rd_keyfree.keys()
dict_keys(['a', 'c'])
>>> rd_keyfree.get_initial().keys()
dict_keys(['a', 'c'])

Reset

# Check whether the values are changed from initial.
>>> rd.is_changed()
False
# (Change some values.)
>>> rd["enable"] = False
>>> rd["count"] = 5
>>> rd.is_changed()
True

# Reset with a key.
>>> rd.reset("count")
>>> rd["count"]
0
>>> rd.is_changed()
True

# Reset all values.
>>> rd.reset()
>>> rd.is_changed()
False

Copy

# Create a new rsdict with different optional arguments.
# If reset, copy initial values only.
>>> rd["name"] = "Mike"
>>> rd2 = rd.copy(reset=True)
>>> rd2 == rd.get_initial()
True

# If frozen and not reset, copy current values as new initial values.
>>> rd3 = rd.copy(frozen=True)
>>> rd3
rsdict({'name': 'Mike', 'enable': True, 'count': 0},
    frozen=True, fixkey=True, fixtype=False, cast=False)
>>> rd3 == rd
True
>>> rd3.get_initial() == rd.get_initial()
False

Compare

>>> rd1 = rsdict({"key1": 10, "key2": "abc"})
>>> rd2 = rsdict({"key1": 20, "key2": "abc"})
# Change current value.
>>> rd2["key1"] = 10

# Current values are equal.
>>> rd1 == rd2
True

# Initial values are not equal.
>>> rd1.get_initial() == rd2.get_initial()
False

# If compare with dict, use current values.
>>> d2 = rd2.to_dict()
>>> rd2 == d2

Union

(Python3.9 or later)

>>> rd = rsdict({"key1": 10, "key2": "abc"}, fixkey=False)
>>> d = {"key2": 20, "key3": False}

# Return: dict
>>> rd | d
{'key1': 10, 'key2': 20, 'key3': False}
>>> d | rd
{'key2': 'abc', 'key3': False, 'key1': 10}

>>> rd |= d
>>> rd
rsdict({'key1': 10, 'key2': 20, 'key3': False},
    frozen=False, fixkey=False, fixtype=True, cast=False)
# Add initial values of new keys only.
>>> rd.get_initial()
{'key1': 10, 'key2': 'abc', 'key3': False}

Note

>>> d = dict(a=[1])
>>> rd = rsdict(d)
>>> rd["a"].append(2)
>>> rd
rsdict({'a': [1, 2]}, frozen=False, fixkey=True, fixtype=True, cast=False)
>>> d
{'a': [1, 2]}
>>> rd.get_initial()
{'a': [1]}

Performance

rsdict is slower than dict due to its additional checking.

Image: https://github.com/kihiyuki/python-rsdict/blob/main/docs/img/speed.png

Changelog

-> CHANGELOG.md