Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shortcut operators for union, intersection and difference #144

Merged
merged 7 commits into from Jun 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 11 additions & 2 deletions BTrees/BTreeTemplate.c
Expand Up @@ -2436,7 +2436,7 @@ BTree_nonzero(BTree *self)

static PyNumberMethods BTree_as_number_for_nonzero = {
0, /* nb_add */
0, /* nb_subtract */
bucket_sub, /* nb_subtract */
0, /* nb_multiply */
#ifndef PY3K
0, /* nb_divide */
Expand All @@ -2447,7 +2447,13 @@ static PyNumberMethods BTree_as_number_for_nonzero = {
0, /* nb_negative */
0, /* nb_positive */
0, /* nb_absolute */
(inquiry)BTree_nonzero /* nb_nonzero */
(inquiry)BTree_nonzero, /* nb_nonzero */
(unaryfunc)0, /* nb_invert */
(binaryfunc)0, /* nb_lshift */
(binaryfunc)0, /* nb_rshift */
bucket_and, /* nb_and */
(binaryfunc)0, /* nb_xor */
bucket_or, /* nb_or */
};

static PyTypeObject BTreeType = {
Expand All @@ -2470,6 +2476,9 @@ static PyTypeObject BTreeType = {
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
#ifndef PY3K
Py_TPFLAGS_CHECKTYPES |
#endif
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE, /* tp_flags */
Expand Down
49 changes: 48 additions & 1 deletion BTrees/BucketTemplate.c
Expand Up @@ -12,6 +12,7 @@

****************************************************************************/

#include "SetOpTemplate.h"
#define BUCKETTEMPLATE_C "$Id$\n"

/* Use BUCKET_SEARCH to find the index at which a key belongs.
Expand Down Expand Up @@ -1367,6 +1368,26 @@ bucket_setstate(Bucket *self, PyObject *state)
return Py_None;
}

static PyObject *
bucket_sub(PyObject *self, PyObject *other)
{
PyObject *args = Py_BuildValue("OO", self, other);
return difference_m(NULL, args);
}

static PyObject *
bucket_or(PyObject *self, PyObject *other)
{
PyObject *args = Py_BuildValue("OO", self, other);
return union_m(NULL, args);
}

static PyObject *
bucket_and(PyObject *self, PyObject *other)
{
PyObject *args = Py_BuildValue("OO", self, other);
return intersection_m(NULL, args);
}

static PyObject *
bucket_setdefault(Bucket *self, PyObject *args)
Expand Down Expand Up @@ -1847,6 +1868,29 @@ static PySequenceMethods Bucket_as_sequence = {
0, /* sq_inplace_repeat */
};

static PyNumberMethods Bucket_as_number = {
(binaryfunc)0, /* nb_add */
bucket_sub, /* nb_subtract */
(binaryfunc)0, /* nb_multiply */
#ifndef PY3K
0, /* nb_divide */
#endif
(binaryfunc)0, /* nb_remainder */
(binaryfunc)0, /* nb_divmod */
(ternaryfunc)0, /* nb_power */
(unaryfunc)0, /* nb_negative */
(unaryfunc)0, /* nb_positive */
(unaryfunc)0, /* nb_absolute */
(inquiry)0, /* nb_bool */
(unaryfunc)0, /* nb_invert */
(binaryfunc)0, /* nb_lshift */
(binaryfunc)0, /* nb_rshift */
bucket_and, /* nb_and */
(binaryfunc)0, /* nb_xor */
bucket_or, /* nb_or */
};


static PyObject *
bucket_repr(Bucket *self)
{
Expand Down Expand Up @@ -1911,7 +1955,7 @@ static PyTypeObject BucketType = {
0, /* tp_setattr */
0, /* tp_compare */
(reprfunc)bucket_repr, /* tp_repr */
0, /* tp_as_number */
&Bucket_as_number, /* tp_as_number */
&Bucket_as_sequence, /* tp_as_sequence */
&Bucket_as_mapping, /* tp_as_mapping */
0, /* tp_hash */
Expand All @@ -1920,6 +1964,9 @@ static PyTypeObject BucketType = {
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
#ifndef PY3K
Py_TPFLAGS_CHECKTYPES |
#endif
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE, /* tp_flags */
Expand Down
32 changes: 30 additions & 2 deletions BTrees/Interfaces.py
Expand Up @@ -126,11 +126,26 @@ def __getitem__(index):


class ISet(IKeySequence, ISetMutable):
pass
def __and__(other):
"""Shortcut for :meth:`~BTrees.Interfaces.IMerge.intersection`"""

def __or__(other):
"""Shortcut for :meth:`~BTrees.Interfaces.IMerge.union`"""

def __sub__(other):
"""Shortcut for :meth:`~BTrees.Interfaces.IMerge.difference"""


class ITreeSet(ISetMutable):
pass
def __and__(other):
"""Shortcut for :meth:`~BTrees.Interfaces.IMerge.intersection`"""

def __or__(other):
"""Shortcut for :meth:`~BTrees.Interfaces.IMerge.union`"""

def __sub__(other):
"""Shortcut for :meth:`~BTrees.Interfaces.IMerge.difference"""


class IMinimalDictionary(ISized, IKeyed):

Expand Down Expand Up @@ -267,6 +282,15 @@ def insert(key, value):
key=generate_key()
"""

def __and__(other):
"""Shortcut for :meth:`~BTrees.Interfaces.IMerge.intersection`"""

def __or__(other):
"""Shortcut for :meth:`~BTrees.Interfaces.IMerge.union`"""

def __sub__(other):
"""Shortcut for :meth:`~BTrees.Interfaces.IMerge.difference`"""


class IMerge(Interface):
"""Object with methods for merging sets, buckets, and trees.
Expand All @@ -277,6 +301,10 @@ class IMerge(Interface):
:meth:`BTrees.IIBTree.IIBTree.union` can only be used with :class:`~BTrees.IIBTree.IIBTree`,
:class:`~BTrees.IIBTree.IIBucket`, :class:`~BTrees.IIBTree.IISet`, and :class:`~BTrees.IIBTree.IITreeSet`.

The number protocols methods ``__and__``, ``__or__`` and ``__sub__`` are provided
by all the data structures. They are shortcuts for :meth:`~BTrees.Interfaces.IMerge.intersection`,
:meth:`~BTrees.Interfaces.IMerge.union` and :meth:`~BTrees.Interfaces.IMerge.difference`.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just set this line of documentation because I was not sure on which interface to add the new operators. Is it ok like this? Or should I add the operators to the IBTree, ITreeSet and ISet interfaces?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the modules (e.g., BTrees.OOBTree) that implement IMerge, so I don't think the method definitions of the operators belong here. It doesn't hurt anything to mention in the main docstring here that there are operator synonyms for the functions defined by this interface, but I think that the operator method definitions should be moved to the appropriate datatype interfaces.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, just to be sure, IBTree, ITreeSet and ISet ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds right to me. (I don't think there's an IBucket.)

The implementing module has a value type. The :class:`~BTrees.IOBTree.IOBTree` and :class:`~BTrees.OOBTree.OOBTree`
modules have object value type. The :class:`~BTrees.IIBTree.IIBTree` and :class:`~BTrees.OIBTree.OIBTree` modules
have integer value types. Other modules may be defined in the
Expand Down
1 change: 1 addition & 0 deletions BTrees/SetOpTemplate.c
Expand Up @@ -16,6 +16,7 @@
Set operations
****************************************************************************/

#include "SetOpTemplate.h"
#define SETOPTEMPLATE_C "$Id$\n"

#ifdef KEY_CHECK
Expand Down
15 changes: 15 additions & 0 deletions BTrees/SetOpTemplate.h
@@ -0,0 +1,15 @@
# ifndef SETOPTEMPLATE_H
# define SETOPTEMPLATE_H

#include "Python.h"

static PyObject *
union_m(PyObject *ignored, PyObject *args);

static PyObject *
intersection_m(PyObject *ignored, PyObject *args);

static PyObject *
difference_m(PyObject *ignored, PyObject *args);

# endif
27 changes: 26 additions & 1 deletion BTrees/SetTemplate.c
Expand Up @@ -304,6 +304,28 @@ static PySequenceMethods set_as_sequence = {
0, /* sq_inplace_repeat */
};

static PyNumberMethods set_as_number = {
(binaryfunc)0, /* nb_add */
bucket_sub, /* nb_subtract */
(binaryfunc)0, /* nb_multiply */
#ifndef PY3K
0, /* nb_divide */
#endif
(binaryfunc)0, /* nb_remainder */
(binaryfunc)0, /* nb_divmod */
(ternaryfunc)0, /* nb_power */
(unaryfunc)0, /* nb_negative */
(unaryfunc)0, /* nb_positive */
(unaryfunc)0, /* nb_absolute */
(inquiry)0, /* nb_bool */
(unaryfunc)0, /* nb_invert */
(binaryfunc)0, /* nb_lshift */
(binaryfunc)0, /* nb_rshift */
bucket_and, /* nb_and */
(binaryfunc)0, /* nb_xor */
bucket_or, /* nb_or */
};

static PyTypeObject SetType = {
PyVarObject_HEAD_INIT(NULL, 0) /* PyPersist_Type */
MODULE_NAME MOD_NAME_PREFIX "Set", /* tp_name */
Expand All @@ -315,7 +337,7 @@ static PyTypeObject SetType = {
0, /* tp_setattr */
0, /* tp_compare */
(reprfunc)set_repr, /* tp_repr */
0, /* tp_as_number */
&set_as_number, /* tp_as_number */
&set_as_sequence, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
Expand All @@ -324,6 +346,9 @@ static PyTypeObject SetType = {
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
#ifndef PY3K
Py_TPFLAGS_CHECKTYPES |
#endif
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE, /* tp_flags */
Expand Down
3 changes: 3 additions & 0 deletions BTrees/TreeSetTemplate.c
Expand Up @@ -230,6 +230,9 @@ static PyTypeObject TreeSetType =
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
#ifndef PY3K
Py_TPFLAGS_CHECKTYPES |
#endif
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE, /* tp_flags */
Expand Down
20 changes: 20 additions & 0 deletions BTrees/_base.py
Expand Up @@ -218,6 +218,16 @@ def _repr_helper(self, items):
name = name[:-2] if name.endswith("Py") else name
return "%s.%s(%r)" % (mod, name, items)

def __sub__(self, other):
return difference(self.__class__, self, other)

def __or__(self, other):
return union(self.__class__, self, other)

def __and__(self, other):
return intersection(self.__class__, self, other)


class _SetIteration(object):

__slots__ = ('to_iterate',
Expand Down Expand Up @@ -1153,6 +1163,16 @@ def __repr__(self):
r = r.replace('Py', '')
return r

def __sub__(self, other):
return difference(self.__class__, self, other)

def __or__(self, other):
return union(self.__class__, self, other)

def __and__(self, other):
return intersection(self.__class__, self, other)


def _get_simple_btree_bucket_state(state):
if state is None:
return state
Expand Down
3 changes: 3 additions & 0 deletions BTrees/tests/common.py
Expand Up @@ -2354,6 +2354,7 @@ def testUnion(self):
C = self.union(A, B)
self.assertTrue(not hasattr(C, "values"))
self.assertEqual(list(C), self._union(A, B))
self.assertEqual(set(A) | set(B), set(A | B))

def testIntersection(self):
inputs = self.As + self.Bs
Expand All @@ -2362,6 +2363,7 @@ def testIntersection(self):
C = self.intersection(A, B)
self.assertTrue(not hasattr(C, "values"))
self.assertEqual(list(C), self._intersection(A, B))
self.assertEqual(set(A) & set(B), set(A & B))

def testDifference(self):
inputs = self.As + self.Bs
Expand All @@ -2375,6 +2377,7 @@ def testDifference(self):
self.assertEqual(list(C.items()), want)
else:
self.assertEqual(list(C), want)
self.assertEqual(set(A) - set(B), set(A - B))

def testLargerInputs(self): # pylint:disable=too-many-locals
from BTrees.IIBTree import IISet # pylint:disable=no-name-in-module
Expand Down
5 changes: 5 additions & 0 deletions CHANGES.rst
Expand Up @@ -8,6 +8,11 @@
- Fix ``Tree.__setstate__`` to no longer accept children besides
tree or bucket types to prevent crashes. See `PR 143
<https://github.com/zopefoundation/BTrees/pull/143>`_ for details.
- BTrees, TreeSet, Set and Buckets implements the ``__and__``,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

``__or__`` and ``__sub__`` as shortcuts for
``BTrees.Interfaces.IMerge.intersection``,
``BTrees.Interfaces.IMerge.union`` and
``BTrees.Interfaces.IMerge.difference``.


4.7.2 (2020-04-07)
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Expand Up @@ -12,7 +12,7 @@ extras =
commands =
# Temporary work around. Avoid zope.testrunner pending
# https://github.com/zopefoundation/zope.security/issues/71
python -m unittest discover -s BTrees -t .
python -m unittest discover -s BTrees -t . {posargs}
setenv =
PYTHONFAULTHANDLER=1
PYTHONDEVMODE=1
Expand Down