_abc.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2018 Joshua Bronson. All Rights Reserved.
  3. #
  4. # This Source Code Form is subject to the terms of the Mozilla Public
  5. # License, v. 2.0. If a copy of the MPL was not distributed with this
  6. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  7. #==============================================================================
  8. # * Welcome to the bidict source code *
  9. #==============================================================================
  10. # Doing a code review? You'll find a "Code review nav" comment like the one
  11. # below at the top and bottom of the most important source files. This provides
  12. # a suggested initial path through the source when reviewing.
  13. #
  14. # Note: If you aren't reading this on https://github.com/jab/bidict, you may be
  15. # viewing an outdated version of the code. Please head to GitHub to review the
  16. # latest version, which contains important improvements over older versions.
  17. #
  18. # Thank you for reading and for any feedback you provide.
  19. # * Code review nav *
  20. #==============================================================================
  21. # ← Prev: __init__.py Current: _abc.py Next: _base.py →
  22. #==============================================================================
  23. """Provides the :class:`BidirectionalMapping` abstract base class."""
  24. from .compat import Mapping, abstractproperty, iteritems
  25. class BidirectionalMapping(Mapping): # pylint: disable=abstract-method,no-init
  26. """Abstract base class (ABC) for bidirectional mapping types.
  27. Extends :class:`collections.abc.Mapping` primarily by adding the
  28. (abstract) :attr:`inv` property,
  29. which implementors of :class:`BidirectionalMapping`
  30. should override to return a reference to the inverse
  31. :class:`BidirectionalMapping` instance.
  32. Implements :attr:`__subclasshook__` such that any
  33. :class:`~collections.abc.Mapping` that also provides
  34. :attr:`~BidirectionalMapping.inv`
  35. will be considered a (virtual) subclass of this ABC.
  36. """
  37. __slots__ = ()
  38. @abstractproperty
  39. def inv(self):
  40. """The inverse of this bidirectional mapping instance.
  41. *See also* :attr:`bidict.BidictBase.inv`
  42. :raises NotImplementedError: Meant to be overridden in subclasses.
  43. """
  44. # The @abstractproperty decorator prevents BidirectionalMapping subclasses from being
  45. # instantiated unless they override this method. So users shouldn't be able to get to the
  46. # point where they can unintentionally call this implementation of .inv on something
  47. # anyway. Could leave the method body empty, but raise NotImplementedError so it's extra
  48. # clear there's no reason to call this implementation (e.g. via super() after overriding).
  49. raise NotImplementedError
  50. def __inverted__(self):
  51. """Get an iterator over the items in :attr:`inv`.
  52. This is functionally equivalent to iterating over the items in the
  53. forward mapping and inverting each one on the fly, but this provides a
  54. more efficient implementation: Assuming the already-inverted items
  55. are stored in :attr:`inv`, just return an iterator over them directly.
  56. Providing this default implementation enables external functions,
  57. particularly :func:`~bidict.inverted`, to use this optimized
  58. implementation when available, instead of having to invert on the fly.
  59. *See also* :func:`bidict.inverted`
  60. """
  61. return iteritems(self.inv)
  62. @classmethod
  63. def __subclasshook__(cls, C): # noqa: N803 (argument name should be lowercase)
  64. """Check if *C* is a :class:`~collections.abc.Mapping`
  65. that also provides an ``inv`` attribute,
  66. thus conforming to the :class:`BidirectionalMapping` interface,
  67. in which case it will be considered a (virtual) C
  68. even if it doesn't explicitly extend it.
  69. """
  70. if cls is not BidirectionalMapping: # lgtm [py/comparison-using-is]
  71. return NotImplemented
  72. if not Mapping.__subclasshook__(C):
  73. return NotImplemented
  74. mro = getattr(C, '__mro__', None)
  75. if mro is None: # Python 2 old-style class
  76. return NotImplemented
  77. if not any(B.__dict__.get('inv') for B in mro):
  78. return NotImplemented
  79. return True
  80. # * Code review nav *
  81. #==============================================================================
  82. # ← Prev: __init__.py Current: _abc.py Next: _base.py →
  83. #==============================================================================