mirror of https://github.com/jab/bidict.git
Move equals_order_sensitive into BidictBase.
This commit is contained in:
parent
3b2833cc3e
commit
89cb936edb
|
@ -27,6 +27,13 @@ to be notified when new versions of ``bidict`` are released.
|
|||
0.21.3 (not yet released)
|
||||
-------------------------
|
||||
|
||||
- All bidicts now provide the :meth:`~bidict.BidictBase.equals_order_sensitive` method,
|
||||
not just :class:`bidict.OrderedBidict`\s.
|
||||
|
||||
Since support for Python < 3.6 was dropped in v0.21.0,
|
||||
bidicts that are not :class:`bidict.OrderedBidict`\s preserve a deterministic ordering
|
||||
(just like dicts do in Python 3.6+), so all bidicts can now provide this method.
|
||||
|
||||
- Drop setuptools_scm as a setup_requires dependency.
|
||||
|
||||
- Remove ``bidict.__version_info__`` attribute.
|
||||
|
|
|
@ -195,6 +195,16 @@ class BidictBase(BidirectionalMapping[KT, VT]):
|
|||
selfget = self.get
|
||||
return all(selfget(k, _NONE) == v for (k, v) in other.items()) # type: ignore
|
||||
|
||||
def equals_order_sensitive(self, other: object) -> bool:
|
||||
"""Order-sensitive equality check.
|
||||
|
||||
*See also* :ref:`eq-order-insensitive`
|
||||
"""
|
||||
# Same short-circuit as in __eq__ above. Factoring out not worth function call overhead.
|
||||
if not isinstance(other, _t.Mapping) or len(self) != len(other):
|
||||
return False
|
||||
return all(i == j for (i, j) in zip(self.items(), other.items()))
|
||||
|
||||
# The following methods are mutating and so are not public. But they are implemented in this
|
||||
# non-mutable base class (rather than the mutable `bidict` subclass) because they are used here
|
||||
# during initialization (starting with the `_update` method). (Why is this? Because `__init__`
|
||||
|
|
|
@ -297,16 +297,6 @@ class OrderedBidictBase(BidictBase[KT, VT]):
|
|||
"""Iterator over the contained keys in reverse insertion order."""
|
||||
yield from self._iter(reverse=True)
|
||||
|
||||
def equals_order_sensitive(self, other: object) -> bool:
|
||||
"""Order-sensitive equality check.
|
||||
|
||||
*See also* :ref:`eq-order-insensitive`
|
||||
"""
|
||||
# Same short-circuit as BidictBase.__eq__. Factoring out not worth function call overhead.
|
||||
if not isinstance(other, _t.Mapping) or len(self) != len(other):
|
||||
return False
|
||||
return all(i == j for (i, j) in zip(self.items(), other.items()))
|
||||
|
||||
|
||||
# * Code review nav *
|
||||
#==============================================================================
|
||||
|
|
|
@ -192,8 +192,9 @@ Python surprises, gotchas, regrets
|
|||
but it's too late now to fix.
|
||||
|
||||
Fortunately, it wasn't too late for bidict to learn from this.
|
||||
Hence :ref:`eq-order-insensitive` for ordered bidicts,
|
||||
and their separate :meth:`~bidict.FrozenOrderedBidict.equals_order_sensitive` method.
|
||||
Hence :ref:`eq-order-insensitive` even for ordered bidicts.
|
||||
For an order-sensitive equality check, bidict provides the separate
|
||||
:meth:`~bidict.BidictBase.equals_order_sensitive` method.
|
||||
|
||||
- If you define a custom :meth:`~object.__eq__` on a class,
|
||||
it will *not* be used for ``!=`` comparisons on Python 2 automatically;
|
||||
|
|
|
@ -178,20 +178,17 @@ are always order-insensitive:
|
|||
True
|
||||
|
||||
For order-sensitive equality tests, use
|
||||
:meth:`~bidict.FrozenOrderedBidict.equals_order_sensitive`:
|
||||
:meth:`~bidict.BidictBase.equals_order_sensitive`:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> o1.equals_order_sensitive(o2)
|
||||
False
|
||||
>>> from collections import OrderedDict
|
||||
>>> od = OrderedDict(o2)
|
||||
>>> o1.equals_order_sensitive(od)
|
||||
False
|
||||
|
||||
Note that this differs from the behavior of
|
||||
:class:`collections.OrderedDict`\'s ``__eq__()``,
|
||||
by recommendation of Raymond Hettinger (the author) himself.
|
||||
by recommendation of Raymond Hettinger
|
||||
(the author of :class:`~collections.OrderedDict`) himself.
|
||||
He later said that making OrderedDict's ``__eq__()``
|
||||
intransitive was a mistake.
|
||||
|
||||
|
@ -386,28 +383,5 @@ whose interfaces are implemented by various bidict types.
|
|||
Have a look through the :mod:`collections.abc` documentation
|
||||
if you're interested.
|
||||
|
||||
One thing you might notice is that there is no
|
||||
``Ordered`` or ``OrderedMapping`` ABC.
|
||||
However, Python 3.6 introduced the :class:`collections.abc.Reversible` ABC.
|
||||
Since being reversible implies having an ordering,
|
||||
you could check for reversibility instead.
|
||||
For example:
|
||||
|
||||
.. doctest::
|
||||
:pyversion: >= 3.6
|
||||
|
||||
>>> from collections.abc import Reversible
|
||||
|
||||
>>> def is_reversible_mapping(cls):
|
||||
... return issubclass(cls, Reversible) and issubclass(cls, Mapping)
|
||||
...
|
||||
|
||||
>>> is_reversible_mapping(OrderedBidict)
|
||||
True
|
||||
|
||||
>>> is_reversible_mapping(OrderedDict)
|
||||
True
|
||||
|
||||
|
||||
For more you can do with :mod:`bidict`,
|
||||
check out :doc:`extending` next.
|
||||
|
|
|
@ -23,6 +23,7 @@ BIDICT_TYPES = st.sampled_from(t.BIDICT_TYPES)
|
|||
MUTABLE_BIDICT_TYPES = st.sampled_from(t.MUTABLE_BIDICT_TYPES)
|
||||
FROZEN_BIDICT_TYPES = st.sampled_from(t.FROZEN_BIDICT_TYPES)
|
||||
ORDERED_BIDICT_TYPES = st.sampled_from(t.ORDERED_BIDICT_TYPES)
|
||||
REVERSIBLE_BIDICT_TYPES = st.sampled_from(t.REVERSIBLE_BIDICT_TYPES)
|
||||
MAPPING_TYPES = st.sampled_from(t.MAPPING_TYPES)
|
||||
NON_BIDICT_MAPPING_TYPES = st.sampled_from(t.NON_BIDICT_MAPPING_TYPES)
|
||||
ORDERED_MAPPING_TYPES = st.sampled_from(t.ORDERED_MAPPING_TYPES)
|
||||
|
@ -70,12 +71,11 @@ MUTABLE_BIDICTS = _bidict_strat(MUTABLE_BIDICT_TYPES)
|
|||
ORDERED_BIDICTS = _bidict_strat(ORDERED_BIDICT_TYPES)
|
||||
|
||||
|
||||
_ALPHABET = [chr(i) for i in range(0x10ffff) if chr(i).isidentifier()]
|
||||
_ALPHABET = tuple(chr(i) for i in range(0x10ffff) if chr(i).isidentifier())
|
||||
_NAMEDBI_VALID_NAMES = st.text(_ALPHABET, min_size=1)
|
||||
IS_VALID_NAME = str.isidentifier
|
||||
NAMEDBIDICT_NAMES_ALL_VALID = st.lists(_NAMEDBI_VALID_NAMES, min_size=3, max_size=3, unique=True)
|
||||
NAMEDBIDICT_NAMES_SOME_INVALID = st.lists(st.text(min_size=1), min_size=3, max_size=3).filter(
|
||||
lambda i: not all(IS_VALID_NAME(name) for name in i)
|
||||
lambda i: not all(str.isidentifier(name) for name in i)
|
||||
)
|
||||
NAMEDBIDICT_TYPES = st.tuples(NAMEDBIDICT_NAMES_ALL_VALID, BIDICT_TYPES).map(
|
||||
lambda i: namedbidict(*i[0], base_type=i[1])
|
||||
|
@ -83,16 +83,17 @@ NAMEDBIDICT_TYPES = st.tuples(NAMEDBIDICT_NAMES_ALL_VALID, BIDICT_TYPES).map(
|
|||
NAMEDBIDICTS = _bidict_strat(NAMEDBIDICT_TYPES)
|
||||
|
||||
|
||||
def _bi_and_map(bi_types, map_types, init_items=L_PAIRS_NODUP):
|
||||
return st.tuples(bi_types, map_types, init_items).map(
|
||||
def _bi_and_map(bi_types, builtin_map_types=MAPPING_TYPES, init_items=L_PAIRS_NODUP):
|
||||
"""Given bidict types and builtin mapping types, return a pair of each type created from init_items."""
|
||||
return st.tuples(bi_types, builtin_map_types, init_items).map(
|
||||
lambda i: (i[0](i[2]), i[1](i[2]))
|
||||
)
|
||||
|
||||
|
||||
BI_AND_MAP_FROM_SAME_ITEMS = _bi_and_map(BIDICT_TYPES, MAPPING_TYPES)
|
||||
OBI_AND_OD_FROM_SAME_ITEMS = _bi_and_map(ORDERED_BIDICT_TYPES, st.just(OrderedDict))
|
||||
OBI_AND_OMAP_FROM_SAME_ITEMS = _bi_and_map(ORDERED_BIDICT_TYPES, ORDERED_MAPPING_TYPES)
|
||||
HBI_AND_HMAP_FROM_SAME_ITEMS = _bi_and_map(FROZEN_BIDICT_TYPES, HASHABLE_MAPPING_TYPES)
|
||||
BI_AND_MAP_FROM_SAME_ND_ITEMS = _bi_and_map(BIDICT_TYPES)
|
||||
# Update the following when we drop support for Python < 3.8. On 3.8+, all mappings are reversible.
|
||||
RBI_AND_RMAP_FROM_SAME_ND_ITEMS = _bi_and_map(REVERSIBLE_BIDICT_TYPES, st.just(OrderedDict))
|
||||
HBI_AND_HMAP_FROM_SAME_ND_ITEMS = _bi_and_map(FROZEN_BIDICT_TYPES, HASHABLE_MAPPING_TYPES)
|
||||
|
||||
_unpack = lambda i: (i[0](i[2][0]), i[1](i[2][1])) # noqa: E731
|
||||
BI_AND_MAP_FROM_DIFF_ITEMS = st.tuples(BIDICT_TYPES, MAPPING_TYPES, DIFF_ITEMS).map(_unpack)
|
||||
|
|
|
@ -20,6 +20,7 @@ MUTABLE_BIDICT_TYPES = (bidict, OrderedBidict, MyNamedBidict)
|
|||
FROZEN_BIDICT_TYPES = (frozenbidict, FrozenOrderedBidict, MyNamedFrozenBidict)
|
||||
ORDERED_BIDICT_TYPES = (OrderedBidict, FrozenOrderedBidict, MyNamedOrderedBidict)
|
||||
BIDICT_TYPES = tuple(set(MUTABLE_BIDICT_TYPES + FROZEN_BIDICT_TYPES + ORDERED_BIDICT_TYPES))
|
||||
REVERSIBLE_BIDICT_TYPES = ORDERED_BIDICT_TYPES
|
||||
|
||||
|
||||
class _FrozenDict(KeysView, Mapping):
|
||||
|
|
|
@ -55,7 +55,7 @@ def test_unequal_to_mapping_with_different_items(bi_and_map_from_diff_items):
|
|||
assert not bi == mapping
|
||||
|
||||
|
||||
@given(st.BI_AND_MAP_FROM_SAME_ITEMS)
|
||||
@given(st.BI_AND_MAP_FROM_SAME_ND_ITEMS)
|
||||
def test_equal_to_mapping_with_same_items(bi_and_map_from_same_items):
|
||||
"""Bidicts should be equal to mappings created from the same non-duplicating items.
|
||||
|
||||
|
@ -69,7 +69,7 @@ def test_equal_to_mapping_with_same_items(bi_and_map_from_same_items):
|
|||
assert not bi.inv != mapping_inv
|
||||
|
||||
|
||||
@given(st.HBI_AND_HMAP_FROM_SAME_ITEMS)
|
||||
@given(st.HBI_AND_HMAP_FROM_SAME_ND_ITEMS)
|
||||
def test_equal_hashables_have_same_hash(hashable_bidict_and_mapping):
|
||||
"""Hashable bidicts and hashable mappings that are equal should hash to the same value."""
|
||||
bi, mapping = hashable_bidict_and_mapping
|
||||
|
@ -77,16 +77,16 @@ def test_equal_hashables_have_same_hash(hashable_bidict_and_mapping):
|
|||
assert hash(bi) == hash(mapping)
|
||||
|
||||
|
||||
@given(st.OBI_AND_OMAP_FROM_SAME_ITEMS)
|
||||
def test_equals_order_sensitive(ob_and_om):
|
||||
@given(st.BI_AND_MAP_FROM_SAME_ND_ITEMS)
|
||||
def test_equals_order_sensitive(bi_and_map_from_same_items):
|
||||
"""Ordered bidicts should be order-sensitive-equal to ordered mappings with same nondup items.
|
||||
|
||||
The bidict's inverse and the ordered mapping's inverse should also be order-sensitive-equal.
|
||||
"""
|
||||
ob, om = ob_and_om
|
||||
assert ob.equals_order_sensitive(om)
|
||||
om_inv = OrderedDict((v, k) for (k, v) in om.items())
|
||||
assert ob.inv.equals_order_sensitive(om_inv)
|
||||
bi, mapping = bi_and_map_from_same_items
|
||||
assert bi.equals_order_sensitive(mapping)
|
||||
mapping_inv = {v: k for (k, v) in mapping.items()}
|
||||
assert bi.inv.equals_order_sensitive(mapping_inv)
|
||||
|
||||
|
||||
@given(st.OBI_AND_OMAP_FROM_SAME_ITEMS_DIFF_ORDER)
|
||||
|
@ -236,27 +236,18 @@ def test_putall_same_as_put_for_each_item(bi, items, on_dup):
|
|||
assert check.inv == expect.inv
|
||||
|
||||
|
||||
@given(st.BI_AND_CMPDICT_FROM_SAME_ITEMS)
|
||||
def test_iter(bi_and_cmp_dict):
|
||||
""":meth:`bidict.BidictBase.__iter__` should yield all the keys in a bidict."""
|
||||
bi, cmp_dict = bi_and_cmp_dict
|
||||
assert set(bi) == cmp_dict.keys()
|
||||
@given(st.BI_AND_MAP_FROM_SAME_ND_ITEMS)
|
||||
def test_bidict_iter(bi_and_mapping):
|
||||
"""iter(bi) should yield the keys in a bidict in insertion order."""
|
||||
bi, mapping = bi_and_mapping
|
||||
assert all(i == j for (i, j) in zip(bi, mapping))
|
||||
|
||||
|
||||
@given(st.OBI_AND_OD_FROM_SAME_ITEMS)
|
||||
def test_orderedbidict_iter(ob_and_od):
|
||||
"""Ordered bidict __iter__ should yield all the keys in an ordered bidict in the right order."""
|
||||
ob, od = ob_and_od
|
||||
assert all(i == j for (i, j) in zip(ob, od))
|
||||
|
||||
|
||||
@given(st.OBI_AND_OD_FROM_SAME_ITEMS)
|
||||
def test_orderedbidict_reversed(ob_and_od):
|
||||
""":meth:`bidict.OrderedBidictBase.__reversed__` should yield all the keys
|
||||
in an ordered bidict in the reverse-order they were inserted.
|
||||
"""
|
||||
ob, od = ob_and_od
|
||||
assert all(i == j for (i, j) in zip(reversed(ob), reversed(od)))
|
||||
@given(st.RBI_AND_RMAP_FROM_SAME_ND_ITEMS)
|
||||
def test_bidict_reversed(rb_and_rd):
|
||||
"""reversed(bi) should yield the keys in a bidict in reverse insertion order."""
|
||||
rb, rd = rb_and_rd
|
||||
assert all(i == j for (i, j) in zip(reversed(rb), reversed(rd)))
|
||||
|
||||
|
||||
@given(st.FROZEN_BIDICTS)
|
||||
|
@ -427,19 +418,10 @@ def test_inverted_pairs(pairs):
|
|||
assert list(inverted(inverted(pairs))) == pairs
|
||||
|
||||
|
||||
@given(st.BI_AND_CMPDICT_FROM_SAME_ITEMS)
|
||||
def test_inverted_bidict(bi_and_cmp_dict):
|
||||
""":func:`bidict.inverted` should yield the inverse items of a bidict."""
|
||||
bi, cmp_dict = bi_and_cmp_dict
|
||||
cmp_dict_inv = OrderedDict((v, k) for (k, v) in cmp_dict.items())
|
||||
assert set(inverted(bi)) == cmp_dict_inv.items() == bi.inv.items()
|
||||
assert set(inverted(inverted(bi))) == cmp_dict.items() == bi.inv.inv.items()
|
||||
|
||||
|
||||
@given(st.OBI_AND_OD_FROM_SAME_ITEMS)
|
||||
def test_inverted_orderedbidict(ob_and_od):
|
||||
@given(st.BI_AND_MAP_FROM_SAME_ND_ITEMS)
|
||||
def test_inverted_bidict(bi_and_mapping):
|
||||
""":func:`bidict.inverted` should yield the inverse items of an ordered bidict."""
|
||||
ob, od = ob_and_od
|
||||
od_inv = OrderedDict((v, k) for (k, v) in od.items())
|
||||
assert all(i == j for (i, j) in zip(inverted(ob), od_inv.items()))
|
||||
assert all(i == j for (i, j) in zip(inverted(inverted(ob)), od.items()))
|
||||
bi, mapping = bi_and_mapping
|
||||
mapping_inv = {v: k for (k, v) in mapping.items()}
|
||||
assert all(i == j for (i, j) in zip(inverted(bi), mapping_inv.items()))
|
||||
assert all(i == j for (i, j) in zip(inverted(inverted(bi)), mapping.items()))
|
||||
|
|
Loading…
Reference in New Issue