mirror of https://github.com/jab/bidict.git
improve __inverted__ logic, docs
- Classes no longer have to provide an ``__inverted__`` attribute to be considered virtual subclasses of :class:`~bidict.BidirectionalMapping`. - If :func:`bidict.inverted` is passed an object with an ``__inverted__`` attribute, it now ensures it is :func:`callable` before returning the result of calling it.
This commit is contained in:
parent
d1711fc71f
commit
1ecc50a32c
|
@ -16,6 +16,15 @@ Changelog
|
|||
See the new :ref:`inv-avoids-reference-cycles` documentation.
|
||||
Fixes `#24 <https://github.com/jab/bidict/issues/20>`_.
|
||||
|
||||
- Classes no longer have to provide an ``__inverted__``
|
||||
attribute to be considered virtual subclasses of
|
||||
:class:`~bidict.BidirectionalMapping`.
|
||||
|
||||
- If :func:`bidict.inverted` is passed
|
||||
an object with an ``__inverted__`` attribute,
|
||||
it now ensures it is :func:`callable`
|
||||
before returning the result of calling it.
|
||||
|
||||
Breaking API Changes
|
||||
++++++++++++++++++++
|
||||
|
||||
|
|
|
@ -15,6 +15,25 @@ See https://bidict.readthedocs.io for comprehensive documentation.
|
|||
|
||||
"""
|
||||
|
||||
# Welcome to the bidict source code.
|
||||
#
|
||||
# Beginning a code review? Excellent!
|
||||
#
|
||||
# This __init__.py just collects functionality implemented in the other files
|
||||
# and exports it under the `bidict` module namespace.
|
||||
#
|
||||
# If you're looking for an interesting place to head next, check out _abc.py.
|
||||
# There the BidirectionalMapping abstract base class (ABC) is defined, which
|
||||
# all the bidirectional mapping types that bidict provides are subclasses of.
|
||||
#
|
||||
# ONE MORE THING! If you are not reading this on https://github.com/jab/bidict
|
||||
# right now, you may not be viewing the latest version of the code. Please head
|
||||
# to https://github.com/jab/bidict to review the latest version, which contains
|
||||
# important improvements over older versions.
|
||||
#
|
||||
# Thank you for reading =)
|
||||
# —jab ʕ•●̫•ʔ
|
||||
|
||||
from ._abc import BidirectionalMapping
|
||||
from ._bidict import bidict
|
||||
from ._dup import DuplicationPolicy, IGNORE, OVERWRITE, RAISE
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
"""Provides bidict ABCs."""
|
||||
"""Provides the :class:`BidirectionalMapping` abstract base class (ABC)."""
|
||||
|
||||
from collections import Mapping
|
||||
|
||||
|
@ -14,7 +14,11 @@ from .compat import iteritems
|
|||
|
||||
class BidirectionalMapping(Mapping): # pylint: disable=abstract-method
|
||||
"""Abstract base class for bidirectional mappings.
|
||||
Extends :class:`collections.abc.Mapping`.
|
||||
|
||||
Extends :class:`collections.abc.Mapping` primarily by adding the :attr:`inv`
|
||||
attribute, which holds a reference to the inverse mapping.
|
||||
|
||||
All the bidirectional mapping types that bidict provides subclass this.
|
||||
|
||||
.. py:attribute:: inv
|
||||
|
||||
|
@ -31,11 +35,23 @@ class BidirectionalMapping(Mapping): # pylint: disable=abstract-method
|
|||
inv = NotImplemented
|
||||
|
||||
def __inverted__(self):
|
||||
"""Get an iterator over the items in :attr:`inv`."""
|
||||
"""Get an iterator over the items in :attr:`inv`.
|
||||
|
||||
This is functionally equivalent to iterating over each item in the
|
||||
forward mapping and inverting each one on the fly, but this is more
|
||||
efficient. Since we already have the inverted items stored in
|
||||
:attr:`inv`, we can just iterate over them directly.
|
||||
|
||||
Providing this default implementation enables external functions,
|
||||
particularly :func:`~bidict.inverted`, to use this optimized
|
||||
implementation when available, instead of having to invert on the fly.
|
||||
|
||||
.. seealso:: :func:`bidict.inverted`
|
||||
"""
|
||||
return iteritems(self.inv)
|
||||
|
||||
_subclsattrs = frozenset({
|
||||
'inv', '__inverted__',
|
||||
'inv', # __inverted__ is just an optimization, not a requirement of the interface
|
||||
# see "Mapping" in the table at
|
||||
# https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes
|
||||
'__getitem__', '__iter__', '__len__', # abstract methods
|
||||
|
|
|
@ -31,6 +31,12 @@ def _proxied(methodname, attrname='fwdm', doc=None):
|
|||
return proxy
|
||||
|
||||
|
||||
# Since BidirectionalMapping implements __subclasshook__, and frozenbidict
|
||||
# provides all the required attributes that the __subclasshook__ checks for,
|
||||
# frozenbidict would be a (virtual) subclass of BidirectionalMapping even if
|
||||
# it didn't subclass it explicitly. But subclassing it explicitly allows
|
||||
# frozenbidict to inherit the optimized __inverted__ implementation that
|
||||
# BidirectionalMapping also provides.
|
||||
# pylint: disable=invalid-name,too-many-instance-attributes
|
||||
class frozenbidict(BidirectionalMapping): # noqa: N801
|
||||
u"""
|
||||
|
|
|
@ -19,7 +19,7 @@ def pairs(*args, **kw):
|
|||
|
||||
If a positional argument is provided,
|
||||
its pairs are yielded before those of any keyword arguments.
|
||||
The positional argument may be a mapping or sequence or pairs.
|
||||
The positional argument may be a mapping or an iterable of pairs.
|
||||
|
||||
>>> list(pairs({'a': 1}, b=2))
|
||||
[('a', 1), ('b', 2)]
|
||||
|
@ -46,18 +46,24 @@ def _arg0(args):
|
|||
return args[0]
|
||||
|
||||
|
||||
def inverted(data):
|
||||
def inverted(obj):
|
||||
"""
|
||||
Yield the inverse items of the provided mapping or iterable.
|
||||
Yield the inverse items of the provided object.
|
||||
|
||||
Works with any object that can be iterated over as a mapping or in pairs,
|
||||
or that implements its own *__inverted__* method.
|
||||
If `obj` has a :func:`callable` ``__inverted__`` attribute
|
||||
(such as :attr:`bidict.BidirectionalMapping.__inverted__`),
|
||||
just return the result of calling the ``__inverted__`` attribute.
|
||||
|
||||
Otherwise, return an iterator that iterates over the items in `obj`,
|
||||
inverting each item on the fly.
|
||||
|
||||
.. seealso:: :attr:`bidict.BidirectionalMapping.__inverted__`
|
||||
"""
|
||||
inv = getattr(data, '__inverted__', None)
|
||||
return inv() if inv else _inverted(data)
|
||||
inv = getattr(obj, '__inverted__', None)
|
||||
return inv() if callable(inv) else _inverted_on_the_fly(obj)
|
||||
|
||||
|
||||
def _inverted(data):
|
||||
# This is faster than `return imap(tuple, imap(reversed, pairs(data)))`:
|
||||
for (key, val) in pairs(data):
|
||||
def _inverted_on_the_fly(iterable):
|
||||
# This is faster than `return imap(tuple, imap(reversed, pairs(iterable)))`:
|
||||
for (key, val) in pairs(iterable):
|
||||
yield (val, key)
|
||||
|
|
Loading…
Reference in New Issue