bidict v0.22.0 Release Notes

Release Date: 2022-03-23 // 3 months ago
    • ⬇️ Drop support for Python 3.6, which reached end of life on 2021-12-23 and is no longer supported by pip as of pip version 22. Take advantage of this to reduce bidict's maintenance costs.

    • Use mypy-appeasing explicit re-exports in __init__.py (e.g. import x as x) so that mypy no longer gives you an implicit re-export error if you run it with --no-implicit-reexport (or --strict) against code that imports from :mod:bidict.

    • ⚡️ Update the implementations and type annotations of :meth:bidict.BidictBase.keys and :meth:bidict.BidictBase.values to make use of the new :class:~bidict.BidictKeysView type, which works a bit better with type checkers.

    • Inverse bidict instances are now computed lazily the first time the :attr:~bidict.BidictBase.inverse attribute is accessed rather than being computed eagerly during initialization. (A bidict's backing, inverse, one-way mapping is still kept in sync eagerly as any mutations are made, to preserve key- and value-uniqueness.)

    • ⚡️ Optimize initializing a bidict with another bidict. In a microbenchmark on Python 3.10, this now performs over 2x faster.

    • ⚡️ Optimize updating an empty bidict with another bidict. In a microbenchmark on Python 3.10, this now performs 60-75% faster.

    • ⚡️ Optimize :meth:~bidict.BidictBase.copy. In a microbenchmark on Python 3.10, this now performs 10-20x faster.

    • ⚡️ Optimize rolling back :ref:failed updates to a bidict <basic-usage:Updates Fail Clean> in the case that the number of items passed to the update call can be determined to be larger than the bidict being updated. Previously this rollback was O(n) in the number of items passed. Now it is O(1), i.e. unboundedly faster.

    • Optimize :meth:bidict.BidictBase.__contains__ (the method called when you run key in mybidict). In a microbenchmark on Python 3.10, this now performs over 3-10x faster in the False case, and at least 50% faster in the True case.

    • Optimize :meth:bidict.BidictBase.__eq__ (the method called when you run mybidict == other). In a microbenchmark on Python 3.10, this now performs 15-25x faster for ordered bidicts, and 7-12x faster for unordered bidicts.

    • Optimize :meth:~bidict.BidictBase.equals_order_sensitive. In a microbenchmark on Python 3.10, this now performs 2x faster for ordered bidicts and 60-90% faster for unordered bidicts.

    • ⚡️ Optimize the :class:~collections.abc.MappingView objects returned by :meth:bidict.OrderedBidict.keys, :meth:bidict.OrderedBidict.values, and :meth:bidict.OrderedBidict.items to delegate to backing dict_keys and dict_items objects if available, which are much faster in CPython. For example, in a microbenchmark on Python 3.10, orderedbi.items() == d.items() now performs 30-50x faster.

    • 🛠 Fix a bug where :meth:bidict.BidictBase.__eq__ was always returning False rather than :obj:NotImplemented in the case that the argument was not a :class:~collections.abc.Mapping, defeating the argument's own __eq__() if implemented. As a notable example, bidicts now correctly compare equal to :obj:unittest.mock.ANY.

    • :class:bidict.BidictBase now adds a __reversed__ implementation to subclasses that don't have an overridden implementation depending on whether both their backing mappings are :class:~collections.abc.Reversible. Previously, a __reversed__ implementation was only added to :class:~bidict.BidictBase when BidictBase._fwdm_cls was :class:~collections.abc.Reversible. So if a :class:~bidict.BidictBase subclass set its _fwdm_cls to a non-reversible mutable mapping, it would also have to manually set its __reversed__ attribute to None to override the implementation inherited from :class:~bidict.BidictBase. This is no longer necessary thanks to bidict's new :meth:object.__init_subclass__ logic.

    • The :class:~collections.abc.MappingView objects returned by :meth:bidict.OrderedBidict.keys, :meth:bidict.OrderedBidict.values, and :meth:bidict.OrderedBidict.items are now :class:~collections.abc.Reversible. (This was already the case for unordered bidicts when running on Python 3.8+.)

    • ➕ Add support for Python 3.9-style dict merge operators (PEP 584 <https://www.python.org/dev/peps/pep-0584/>__).

    See the tests <https://github.com/jab/bidict/blob/main/tests/>__ for examples.

    • ⚡️ Update docstrings for :meth:bidict.BidictBase.keys, :meth:bidict.BidictBase.values, and :meth:bidict.BidictBase.items to include more details.

    • :func:~bidict.namedbidict now exposes the passed-in keyname and valname in the corresponding properties on the generated class.

    • :func:~bidict.namedbidict now requires base_type to be a subclass of :class:~bidict.BidictBase, but no longer requires base_type to provide an _isinv attribute, which :class:~bidict.BidictBase subclasses no longer provide.

    • When attempting to pickle a bidict's inverse whose class was :ref:dynamically generated <extending:Dynamic Inverse Class Generation>, and no reference to the dynamically-generated class has been stored anywhere in :data:sys.modules where :mod:pickle can find it, the pickle call is now more likely to succeed rather than failing with a :class:~pickle.PicklingError.

    • ✂ Remove the use of slots from (non-ABC) bidict types.

    This better matches the mapping implementations in Python's standard library, and significantly reduces code complexity and maintenance burden. The memory savings conferred by using slots are not noticeable unless you're creating millions of bidict instances anyway, which is an extremely unusual usage pattern.

    Of course, bidicts can still contain millions (or more) items (which is not an unusual usage pattern) without using any more memory than before these changes. Notably, slots are still used in the internal linked list nodes of ordered bidicts to save memory, since as many node instances are created as there are items inserted.


Previous changes from v0.21.4

  • 👍 Explicitly declare support for Python 3.10 as well as some minor internal improvements.