diff --git a/CHANGELOG.md b/CHANGELOG.md index 7035496a7516167c450093e4a21a69dd7a618613..b57f49c984fca97274e0dcfb12538b0635c0aedb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,4 @@ # v2.18.7 + +* added NotEqualToAnything +* added DictionaryEQAble diff --git a/docs/coding/structures.rst b/docs/coding/structures.rst index 79b679ff16e34446728494b52dd4d56575223efc..611a2d7c6ae8388c42cc04f0ce72d89535da850d 100644 --- a/docs/coding/structures.rst +++ b/docs/coding/structures.rst @@ -2,6 +2,17 @@ Structures ========== +.. autoclass:: satella.coding.structures.NotEqualToAnything + :members: + +You can also use the following singleton. + +.. code-block:: python + from satella.coding.structures import NOT_EQUAL_TO_ANYTHING + + assert NOT_EQUAL_TO_ANYTHING != NOT_EQUAL_TO_ANYTHING + + .. autoclass:: satella.coding.structures.Vector :members: diff --git a/satella/__init__.py b/satella/__init__.py index 7c95a3b1795598097e13082951d107d682b17048..fd0aaca7ce5edbcf0c595c0b17a847e252140f59 100644 --- a/satella/__init__.py +++ b/satella/__init__.py @@ -1 +1 @@ -__version__ = '2.18.7a1' +__version__ = '2.18.7' diff --git a/satella/coding/structures/__init__.py b/satella/coding/structures/__init__.py index 95932389fdb433567659ed35da2ee0af9388b8ac..142f5939fc9bde294dabb5f948ec58dad304e1f1 100644 --- a/satella/coding/structures/__init__.py +++ b/satella/coding/structures/__init__.py @@ -3,10 +3,10 @@ from .dictionaries import DictObject, apply_dict_object, DictionaryView, TwoWayD CacheDict, ExclusiveWritebackCache, CountingDict, LRUCacheDict, DefaultDict from .hashable_objects import HashableWrapper from .heaps import Heap, SetHeap, TimeBasedHeap, TimeBasedSetHeap -from .immutable import Immutable, frozendict +from .immutable import Immutable, frozendict, NotEqualToAnything, NOT_EQUAL_TO_ANYTHING from .mixins import OmniHashableMixin, ReprableMixin, StrEqHashableMixin, ComparableIntEnum, \ HashableIntEnum, ComparableAndHashableBy, ComparableAndHashableByInt, ComparableEnum, \ - HashableMixin, ComparableAndHashableByStr + HashableMixin, ComparableAndHashableByStr, DictionaryEQAble from .proxy import Proxy from .queues import Subqueue from .ranking import Ranking @@ -26,8 +26,9 @@ __all__ = [ 'DBStorage', 'SyncableDroppable', 'LRU', 'LRUCacheDict', + 'NotEqualToAnything', 'NOT_EQUAL_TO_ANYTHING', 'HashableMixin', - 'CountingDict', + 'CountingDict', 'DictionaryEQAble', 'Subqueue', 'ExclusiveWritebackCache', 'DefaultDict', diff --git a/satella/coding/structures/immutable.py b/satella/coding/structures/immutable.py index ec62ddf4dab6df03228d86114714ca45a4154c4b..12385531e01ea3f6e2d71dd04baa2a539eab1848 100644 --- a/satella/coding/structures/immutable.py +++ b/satella/coding/structures/immutable.py @@ -46,6 +46,24 @@ class Immutable(metaclass=ImmutableMetaType): super().__delattr__(attr) +class NotEqualToAnything: + """ + A class that defines __eq__ and __ne__ to be always False and True respectively + + When exploiting this class please make sure that it gets to be the first + operand in the comparison, so that it's __eq__ and __ne__ gets called! + """ + + def __eq__(self, other) -> bool: + return False + + def __next__(self, other) -> bool: + return True + + +NOT_EQUAL_TO_ANYTHING = NotEqualToAnything() + + class frozendict(dict): """ A hashable dict with express forbid to change it's values diff --git a/satella/coding/structures/mixins/__init__.py b/satella/coding/structures/mixins/__init__.py index 85e9f77bb2d947421f53b62a022f34c577c52fa0..948d863102e452b3f9ba3c5ea8cf807423e2cbfa 100644 --- a/satella/coding/structures/mixins/__init__.py +++ b/satella/coding/structures/mixins/__init__.py @@ -2,8 +2,9 @@ from .enums import ComparableEnum, ComparableIntEnum, HashableIntEnum from .hashable import ComparableAndHashableBy, ComparableAndHashableByInt, \ OmniHashableMixin, HashableMixin, ComparableAndHashableByStr from .strings import ReprableMixin, StrEqHashableMixin +from .eqable import DictionaryEQAble __all__ = ['ComparableIntEnum', 'ComparableEnum', 'ComparableAndHashableBy', 'HashableIntEnum', 'ComparableAndHashableByInt', 'OmniHashableMixin', 'ReprableMixin', 'StrEqHashableMixin', 'HashableMixin', - 'ComparableAndHashableByStr'] + 'ComparableAndHashableByStr', 'DictionaryEQAble'] diff --git a/satella/coding/structures/mixins/eqable.py b/satella/coding/structures/mixins/eqable.py new file mode 100644 index 0000000000000000000000000000000000000000..ca9a5eda01cd3f9264809244186804f667fbd247 --- /dev/null +++ b/satella/coding/structures/mixins/eqable.py @@ -0,0 +1,26 @@ +from ..immutable import NOT_EQUAL_TO_ANYTHING + + +class DictionaryEQAble: + """ + A class mix-in that defines __eq__ and __ne__ to be: + + - both the same exact type (so subclassing won't work) + - have the exact same __dict__ + """ + + def __eq__(self, other) -> bool: + if type(self) != type(other): + return False + for key in self.__dict__.keys(): + if getattr(other, key, NOT_EQUAL_TO_ANYTHING) != self.__dict__[key]: + return False + return True + + def __ne__(self, other) -> bool: + if type(self) != type(other): + return True + for key in self.__dict__.keys(): + if getattr(other, key, NOT_EQUAL_TO_ANYTHING) != self.__dict__[key]: + return True + return True diff --git a/tests/test_coding/test_structures.py b/tests/test_coding/test_structures.py index b0fd4a7f40f16f3c081f763afb71e1f4c65ccdea..4e6a6fe1bb4f0706988b1b87c2014d5f53feb085 100644 --- a/tests/test_coding/test_structures.py +++ b/tests/test_coding/test_structures.py @@ -15,10 +15,39 @@ from satella.coding.structures import TimeBasedHeap, Heap, typednamedtuple, \ CacheDict, StrEqHashableMixin, ComparableIntEnum, HashableIntEnum, ComparableAndHashableBy, \ ComparableAndHashableByInt, SparseMatrix, ExclusiveWritebackCache, Subqueue, \ CountingDict, ComparableEnum, LRU, LRUCacheDict, Vector, DefaultDict, PushIterable, \ - ComparableAndHashableByStr + ComparableAndHashableByStr, NotEqualToAnything, NOT_EQUAL_TO_ANYTHING, DictionaryEQAble -class TestMisc(unittest.TestCase): +class TestStructures(unittest.TestCase): + + def test_dictionary_eqable(self): + class Dupa(DictionaryEQAble): + def __init__(self, a): + self.a = a + + class Dupa2(DictionaryEQAble): + def __init__(self, a): + self.a = a + + a = Dupa(5) + b = Dupa(5) + c = Dupa(6) + d = Dupa2(5) + self.assertTrue(a == b) + self.assertFalse(a == c) + self.assertFalse(a == d) + + def test_not_equal_to_anything(self): + self.assertTrue(NOT_EQUAL_TO_ANYTHING != NOT_EQUAL_TO_ANYTHING) + self.assertFalse(NOT_EQUAL_TO_ANYTHING == NOT_EQUAL_TO_ANYTHING) + + class Dupa(NotEqualToAnything): + pass + + a = Dupa() + + self.assertTrue(a != a) + self.assertFalse(a == a) def test_push_iterable(self):