Commit 46d230c1 authored by Tim Peters's avatar Tim Peters

Merge the alienoid-btrees_setdefault branch.

This adds .setdefault() methods to BTrees and Buckets.
parent e0a2513c
...@@ -183,6 +183,13 @@ FileStorage ...@@ -183,6 +183,13 @@ FileStorage
BTrees BTrees
------ ------
- (3.5a9) BTrees and Buckets now implement the ``setdefault()`` method.
This is exactly list 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.5a5) Collector 1843. When a non-integer was passed to a method like - (3.5a5) Collector 1843. When a non-integer was passed to a method like
``keys()`` of a Bucket or Set with integer keys, an internal error code ``keys()`` of a Bucket or Set with integer keys, an internal error code
was overlooked, leading to everything from "delayed errors" to segfaults. was overlooked, leading to everything from "delayed errors" to segfaults.
......
...@@ -1700,6 +1700,36 @@ BTree_has_key(BTree *self, PyObject *key) ...@@ -1700,6 +1700,36 @@ BTree_has_key(BTree *self, PyObject *key)
return _BTree_get(self, key, 1); return _BTree_get(self, key, 1);
} }
static PyObject *
BTree_setdefault(BTree *self, PyObject *args)
{
PyObject *key;
PyObject *failobj; /* default */
PyObject *value; /* return value */
if (! PyArg_UnpackTuple(args, "setdefault", 2, 2, &key, &failobj))
return NULL;
value = _BTree_get(self, key, 0);
if (value != 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;
PyErr_Clear();
/* Associate `key` with `failobj` in the tree, and return `failobj`. */
value = failobj;
if (_BTree_set(self, key, failobj, 0, 0) < 0)
value = NULL;
Py_XINCREF(value);
return value;
}
/* Search BTree self for key. This is the sq_contains slot of the /* Search BTree self for key. This is the sq_contains slot of the
* PySequenceMethods. * PySequenceMethods.
* *
...@@ -1838,6 +1868,11 @@ static struct PyMethodDef BTree_methods[] = { ...@@ -1838,6 +1868,11 @@ static struct PyMethodDef BTree_methods[] = {
"get(key[, default=None]) -> Value for key or default\n\n" "get(key[, default=None]) -> Value for key or default\n\n"
"Return the value or the default if the key is not found."}, "Return the value or the default if the key is not found."},
{"setdefault", (PyCFunction) BTree_setdefault, METH_VARARGS,
"D.setdefault(k, d) -> D.get(k, d), also set D[k]=d if k not in D.\n\n"
"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."},
{"maxKey", (PyCFunction) BTree_maxKey, METH_VARARGS, {"maxKey", (PyCFunction) BTree_maxKey, METH_VARARGS,
"maxKey([max]) -> key\n\n" "maxKey([max]) -> key\n\n"
"Return the largest key in the BTree. If max is specified, return\n" "Return the largest key in the BTree. If max is specified, return\n"
......
...@@ -1257,6 +1257,35 @@ bucket_has_key(Bucket *self, PyObject *key) ...@@ -1257,6 +1257,35 @@ bucket_has_key(Bucket *self, PyObject *key)
return _bucket_get(self, key, 1); return _bucket_get(self, key, 1);
} }
static PyObject *
bucket_setdefault(Bucket *self, PyObject *args)
{
PyObject *key;
PyObject *failobj; /* default */
PyObject *value; /* return value */
int dummy_changed; /* in order to call _bucket_set */
if (! PyArg_UnpackTuple(args, "setdefault", 2, 2, &key, &failobj))
return NULL;
value = _bucket_get(self, key, 0);
if (value != 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;
PyErr_Clear();
/* Associate `key` with `failobj` in the bucket, and return `failobj`. */
value = failobj;
if (_bucket_set(self, key, failobj, 0, 0, &dummy_changed) < 0)
value = NULL;
Py_XINCREF(value);
return value;
}
/* Search bucket self for key. This is the sq_contains slot of the /* Search bucket self for key. This is the sq_contains slot of the
* PySequenceMethods. * PySequenceMethods.
...@@ -1481,6 +1510,11 @@ static struct PyMethodDef Bucket_methods[] = { ...@@ -1481,6 +1510,11 @@ static struct PyMethodDef Bucket_methods[] = {
"get(key[,default]) -- Look up a value\n\n" "get(key[,default]) -- Look up a value\n\n"
"Return the default (or None) if the key is not found."}, "Return the default (or None) if the key is not found."},
{"setdefault", (PyCFunction) bucket_setdefault, METH_VARARGS,
"D.setdefault(k, d) -> D.get(k, d), also set D[k]=d if k not in D.\n\n"
"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."},
{"iterkeys", (PyCFunction) Bucket_iterkeys, METH_KEYWORDS, {"iterkeys", (PyCFunction) Bucket_iterkeys, METH_KEYWORDS,
"B.iterkeys([min[,max]]) -> an iterator over the keys of B"}, "B.iterkeys([min[,max]]) -> an iterator over the keys of B"},
......
...@@ -214,6 +214,18 @@ class IDictionaryIsh(IMinimalDictionary): ...@@ -214,6 +214,18 @@ class IDictionaryIsh(IMinimalDictionary):
integer values, the normalization is division. integer values, the normalization is division.
""" """
def setdefault(key, d):
"""D.setdefault(k, d) -> D.get(k, d), also set D[k]=d if k not in D.
Return the value like get() except that if key is missing, d is both
returned and inserted into the dictionary as the value of k.
Note that, unlike as for Python's dict.setdefault(), d is not
optional. Python defaults d to None, but that doesn't make sense
for mappings that can't have None as a value (for example, an
IIBTree can have only integers as values).
"""
class IBTree(IDictionaryIsh): class IBTree(IDictionaryIsh):
def insert(key, value): def insert(key, value):
......
...@@ -611,6 +611,23 @@ class MappingBase(Base): ...@@ -611,6 +611,23 @@ class MappingBase(Base):
excludemax=True)), excludemax=True)),
f([1])) f([1]))
def testSetdefault(self):
t = self.t
self.assertEqual(t.setdefault(1, 2), 2)
# That should also have associated 1 with 2 in the tree.
self.assert_(1 in t)
self.assertEqual(t[1], 2)
# And trying to change it again should have no effect.
self.assertEqual(t.setdefault(1, 666), 2)
self.assertEqual(t[1], 2)
# Not enough arguments.
self.assertRaises(TypeError, t.setdefault)
self.assertRaises(TypeError, t.setdefault, 1)
# Too many arguments.
self.assertRaises(TypeError, t.setdefault, 1, 2, 3)
class NormalSetTests(Base): class NormalSetTests(Base):
""" Test common to all set types """ """ 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