Commit 2f39f372 authored by Tim Peters's avatar Tim Peters

Merge the alienoid-btrees_pop branch.

This adds .pop() methods to BTrees and Buckets.

Alas, zpkg changed since the branch was made, and it
wasn't (obviously) possible to build the branch anymore,
so much of this code didn't actually appear on the
branch.  The C code was changed to simplify and purge
non-ANSI gcc extensions, and the new test cases beefed
up a little.
parent 22ed95e2
......@@ -31,12 +31,12 @@ PersistentMapping
BTrees
------
- (3.6a1) BTrees and Buckets now implement the ``setdefault()`` method.
This is exactly like Python's ``setdefault()`` method for dictionaries,
except that both arguments are required (and Python is likely to change
to require both arguments too -- defaulting the ``default`` argument to
``None`` has no viable use cases). Thanks to Ruslan Spivak for
contributing code, tests, and documentation.
- (3.6a1) BTrees and Buckets now implement the ``setdefault()`` and ``pop()``
methods. These are exactly like Python's dictionary methods of the same
names, except that ``setdefault()`` requires both arguments (and Python is
likely to change to require both arguments too -- defaulting the
``default`` argument to ``None`` has no viable use cases). Thanks to
Ruslan Spivak for contributing code, tests, and documentation.
- (3.6a1) Collector 1873. It wasn't possible to construct a BTree or Bucket
from, or apply their update() methods to, a PersistentMapping or
......
......@@ -1729,6 +1729,50 @@ BTree_setdefault(BTree *self, PyObject *args)
return value;
}
/* forward declaration */
static int
BTree_length_or_nonzero(BTree *self, int nonzero);
static PyObject *
BTree_pop(BTree *self, PyObject *args)
{
PyObject *key;
PyObject *failobj = NULL; /* default */
PyObject *value; /* return value */
if (! PyArg_UnpackTuple(args, "pop", 1, 2, &key, &failobj))
return NULL;
value = _BTree_get(self, key, 0);
if (value != NULL) {
/* Delete key and associated value. */
if (_BTree_set(self, key, NULL, 0, 0) < 0) {
Py_DECREF(value);
return NULL;;
}
return value;
}
/* The key isn't in the tree. If that's not due to a KeyError exception,
* pass back the unexpected exception.
*/
if (! PyErr_ExceptionMatches(PyExc_KeyError))
return NULL;
if (failobj != NULL) {
/* Clear the KeyError and return the explicit default. */
PyErr_Clear();
Py_INCREF(failobj);
return failobj;
}
/* No default given. The only difference in this case is the error
* message, which depends on whether the tree is empty.
*/
if (BTree_length_or_nonzero(self, 1) == 0) /* tree is empty */
PyErr_SetString(PyExc_KeyError, "pop(): BTree is empty");
return NULL;
}
/* Search BTree self for key. This is the sq_contains slot of the
* PySequenceMethods.
......@@ -1873,6 +1917,11 @@ static struct PyMethodDef BTree_methods[] = {
"Return the value like get() except that if key is missing, d is both\n"
"returned and inserted into the BTree as the value of k."},
{"pop", (PyCFunction) BTree_pop, METH_VARARGS,
"D.pop(k[, d]) -> v, remove key and return the corresponding value.\n\n"
"If key is not found, d is returned if given, otherwise KeyError\n"
"is raised."},
{"maxKey", (PyCFunction) BTree_maxKey, METH_VARARGS,
"maxKey([max]) -> key\n\n"
"Return the largest key in the BTree. If max is specified, return\n"
......
......@@ -1287,6 +1287,53 @@ bucket_setdefault(Bucket *self, PyObject *args)
return value;
}
/* forward declaration */
static int
Bucket_length(Bucket *self);
static PyObject *
bucket_pop(Bucket *self, PyObject *args)
{
PyObject *key;
PyObject *failobj = NULL; /* default */
PyObject *value; /* return value */
int dummy_changed; /* in order to call _bucket_set */
if (! PyArg_UnpackTuple(args, "pop", 1, 2, &key, &failobj))
return NULL;
value = _bucket_get(self, key, 0);
if (value != NULL) {
/* Delete key and associated value. */
if (_bucket_set(self, key, NULL, 0, 0, &dummy_changed) < 0) {
Py_DECREF(value);
return NULL;;
}
return value;
}
/* The key isn't in the bucket. If that's not due to a KeyError exception,
* pass back the unexpected exception.
*/
if (! PyErr_ExceptionMatches(PyExc_KeyError))
return NULL;
if (failobj != NULL) {
/* Clear the KeyError and return the explicit default. */
PyErr_Clear();
Py_INCREF(failobj);
return failobj;
}
/* No default given. The only difference in this case is the error
* message, which depends on whether the bucket is empty.
*/
if (Bucket_length(self) == 0)
PyErr_SetString(PyExc_KeyError, "pop(): Bucket is empty");
return NULL;
}
/* Search bucket self for key. This is the sq_contains slot of the
* PySequenceMethods.
*
......@@ -1515,6 +1562,11 @@ static struct PyMethodDef Bucket_methods[] = {
"Return the value like get() except that if key is missing, d is both\n"
"returned and inserted into the bucket as the value of k."},
{"pop", (PyCFunction) bucket_pop, METH_VARARGS,
"D.pop(k[, d]) -> v, remove key and return the corresponding value.\n\n"
"If key is not found, d is returned if given, otherwise KeyError\n"
"is raised."},
{"iterkeys", (PyCFunction) Bucket_iterkeys, METH_KEYWORDS,
"B.iterkeys([min[,max]]) -> an iterator over the keys of B"},
......
......@@ -226,6 +226,13 @@ class IDictionaryIsh(IMinimalDictionary):
IIBTree can have only integers as values).
"""
def pop(key, d):
"""D.pop(k[, d]) -> v, remove key and return the corresponding value.
If key is not found, d is returned if given, otherwise KeyError is
raised.
"""
class IBTree(IDictionaryIsh):
def insert(key, value):
......
......@@ -628,6 +628,44 @@ class MappingBase(Base):
# Too many arguments.
self.assertRaises(TypeError, t.setdefault, 1, 2, 3)
def testPop(self):
t = self.t
# Empty container.
# If no default given, raises KeyError.
self.assertRaises(KeyError, t.pop, 1)
# But if default given, returns that instead.
self.assertEqual(t.pop(1, 42), 42)
t[1] = 3
# KeyError when key is not in container and default is not passed.
self.assertRaises(KeyError, t.pop, 5)
self.assertEqual(list(t.items()), [(1, 3)])
# If key is in container, returns the value and deletes the key.
self.assertEqual(t.pop(1), 3)
self.assertEqual(len(t), 0)
# If key is present, return value bypassing default.
t[1] = 3
self.assertEqual(t.pop(1, 7), 3)
self.assertEqual(len(t), 0)
# Pop only one item.
t[1] = 3
t[2] = 4
self.assertEqual(len(t), 2)
self.assertEqual(t.pop(1), 3)
self.assertEqual(len(t), 1)
self.assertEqual(t[2], 4)
self.assertEqual(t.pop(1, 3), 3)
# Too few arguments.
self.assertRaises(TypeError, t.pop)
# Too many arguments.
self.assertRaises(TypeError, t.pop, 1, 2, 3)
class NormalSetTests(Base):
""" Test common to all set types """
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment