diff options
-rw-r--r-- | Doc/lib/libcollections.tex | 50 | ||||
-rw-r--r-- | Doc/lib/libcopy.tex | 7 | ||||
-rw-r--r-- | Doc/lib/libstdtypes.tex | 13 | ||||
-rw-r--r-- | Lib/UserDict.py | 7 | ||||
-rw-r--r-- | Lib/copy.py | 4 | ||||
-rw-r--r-- | Lib/test/test_copy.py | 16 | ||||
-rw-r--r-- | Lib/test/test_defaultdict.py | 135 | ||||
-rw-r--r-- | Lib/test/test_dict.py | 50 | ||||
-rw-r--r-- | Lib/test/test_userdict.py | 49 | ||||
-rw-r--r-- | Misc/NEWS | 8 | ||||
-rw-r--r-- | Modules/collectionsmodule.c | 266 | ||||
-rw-r--r-- | Objects/dictobject.c | 16 |
12 files changed, 611 insertions, 10 deletions
diff --git a/Doc/lib/libcollections.tex b/Doc/lib/libcollections.tex index 51603aa..542ef6b 100644 --- a/Doc/lib/libcollections.tex +++ b/Doc/lib/libcollections.tex @@ -8,9 +8,10 @@ \versionadded{2.4} -This module implements high-performance container datatypes. Currently, the -only datatype is a deque. Future additions may include B-trees -and Fibonacci heaps. +This module implements high-performance container datatypes. Currently, +there are two datatypes, deque and defaultdict. +Future additions may include B-trees and Fibonacci heaps. +\versionchanged[Added defaultdict]{2.5} \begin{funcdesc}{deque}{\optional{iterable}} Returns a new deque objected initialized left-to-right (using @@ -211,3 +212,46 @@ def maketree(iterable): [[[['a', 'b'], ['c', 'd']], [['e', 'f'], ['g', 'h']]]] \end{verbatim} + + + +\begin{funcdesc}{defaultdict}{\optional{default_factory\optional{, ...}}} + Returns a new dictionary-like object. \class{defaultdict} is a subclass + of the builtin \class{dict} class. It overrides one method and adds one + writable instance variable. The remaining functionality is the same as + for the \class{dict} class and is not documented here. + + The first argument provides the initial value for the + \member{default_factory} attribute; it defaults to \code{None}. + All remaining arguments are treated the same as if they were + passed to the \class{dict} constructor, including keyword arguments. + + \versionadded{2.5} +\end{funcdesc} + +\class{defaultdict} objects support the following method in addition to +the standard \class{dict} operations: + +\begin{methoddesc}{__missing__}{key} + If the \member{default_factory} attribute is \code{None}, this raises + an \exception{KeyError} exception with the \var{key} as argument. + + If \member{default_factory} is not \code{None}, it is called without + arguments to provide a default value for the given \var{key}, this + value is inserted in the dictionary for the \var{key}, and returned. + + If calling \member{default_factory} raises an exception this exception + is propagated unchanged. + + This method is called by the \method{__getitem__} method of the + \class{dict} class when the requested key is not found; whatever it + returns or raises is then returned or raised by \method{__getitem__}. +\end{methoddesc} + +\class{defaultdict} objects support the following instance variable: + +\begin{datadesc}{default_factory} + This attribute is used by the \method{__missing__} method; it is initialized + from the first argument to the constructor, if present, or to \code{None}, + if absent. +\end{datadesc} diff --git a/Doc/lib/libcopy.tex b/Doc/lib/libcopy.tex index d73d6fd..5964187 100644 --- a/Doc/lib/libcopy.tex +++ b/Doc/lib/libcopy.tex @@ -67,9 +67,12 @@ set of components copied. \end{itemize} -This version does not copy types like module, class, function, method, +This module does not copy types like module, method, stack trace, stack frame, file, socket, window, array, or any similar -types. +types. It does ``copy'' functions and classes (shallow and deeply), +by returning the original object unchanged; this is compatible with +the way these are treated by the \module{pickle} module. +\versionchanged[Added copying functions]{2.5} Classes can use the same interfaces to control copying that they use to control pickling. See the description of module diff --git a/Doc/lib/libstdtypes.tex b/Doc/lib/libstdtypes.tex index 5df39db..5d15375 100644 --- a/Doc/lib/libstdtypes.tex +++ b/Doc/lib/libstdtypes.tex @@ -1350,7 +1350,7 @@ arbitrary objects): \begin{tableiii}{c|l|c}{code}{Operation}{Result}{Notes} \lineiii{len(\var{a})}{the number of items in \var{a}}{} - \lineiii{\var{a}[\var{k}]}{the item of \var{a} with key \var{k}}{(1)} + \lineiii{\var{a}[\var{k}]}{the item of \var{a} with key \var{k}}{(1), (10)} \lineiii{\var{a}[\var{k}] = \var{v}} {set \code{\var{a}[\var{k}]} to \var{v}} {} @@ -1454,6 +1454,17 @@ then is updated with those key/value pairs: \versionchanged[Allowed the argument to be an iterable of key/value pairs and allowed keyword arguments]{2.4} +\item[(10)] If a subclass of dict defines a method \method{__missing__}, +if the key \var{k} is not present, the \var{a}[\var{k}] operation calls +that method with the key \var{k} as argument. The \var{a}[\var{k}] +operation then returns or raises whatever is returned or raised by the +\function{__missing__}(\var{k}) call if the key is not present. +No other operations or methods invoke \method{__missing__}(). +If \method{__missing__} is not defined, \exception{KeyError} is raised. +\method{__missing__} must be a method; it cannot be an instance variable. +For an example, see \module{collections}.\class{defaultdict}. +\versionadded{2.5} + \end{description} \subsection{File Objects diff --git a/Lib/UserDict.py b/Lib/UserDict.py index 7168703..5e97817 100644 --- a/Lib/UserDict.py +++ b/Lib/UserDict.py @@ -14,7 +14,12 @@ class UserDict: else: return cmp(self.data, dict) def __len__(self): return len(self.data) - def __getitem__(self, key): return self.data[key] + def __getitem__(self, key): + if key in self.data: + return self.data[key] + if hasattr(self.__class__, "__missing__"): + return self.__class__.__missing__(self, key) + raise KeyError(key) def __setitem__(self, key, item): self.data[key] = item def __delitem__(self, key): del self.data[key] def clear(self): self.data.clear() diff --git a/Lib/copy.py b/Lib/copy.py index b3419ca..9e60144 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -101,7 +101,8 @@ def _copy_immutable(x): return x for t in (type(None), int, long, float, bool, str, tuple, frozenset, type, xrange, types.ClassType, - types.BuiltinFunctionType): + types.BuiltinFunctionType, + types.FunctionType): d[t] = _copy_immutable for name in ("ComplexType", "UnicodeType", "CodeType"): t = getattr(types, name, None) @@ -217,6 +218,7 @@ d[type] = _deepcopy_atomic d[xrange] = _deepcopy_atomic d[types.ClassType] = _deepcopy_atomic d[types.BuiltinFunctionType] = _deepcopy_atomic +d[types.FunctionType] = _deepcopy_atomic def _deepcopy_list(x, memo): y = [] diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index bd5a3e1..ff4c987 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -568,6 +568,22 @@ class TestCopy(unittest.TestCase): raise ValueError, "ain't got no stickin' state" self.assertRaises(ValueError, copy.copy, EvilState()) + def test_copy_function(self): + self.assertEqual(copy.copy(global_foo), global_foo) + def foo(x, y): return x+y + self.assertEqual(copy.copy(foo), foo) + bar = lambda: None + self.assertEqual(copy.copy(bar), bar) + + def test_deepcopy_function(self): + self.assertEqual(copy.deepcopy(global_foo), global_foo) + def foo(x, y): return x+y + self.assertEqual(copy.deepcopy(foo), foo) + bar = lambda: None + self.assertEqual(copy.deepcopy(bar), bar) + +def global_foo(x, y): return x+y + def test_main(): test_support.run_unittest(TestCopy) diff --git a/Lib/test/test_defaultdict.py b/Lib/test/test_defaultdict.py new file mode 100644 index 0000000..b5a6628 --- /dev/null +++ b/Lib/test/test_defaultdict.py @@ -0,0 +1,135 @@ +"""Unit tests for collections.defaultdict.""" + +import os +import copy +import tempfile +import unittest + +from collections import defaultdict + +def foobar(): + return list + +class TestDefaultDict(unittest.TestCase): + + def test_basic(self): + d1 = defaultdict() + self.assertEqual(d1.default_factory, None) + d1.default_factory = list + d1[12].append(42) + self.assertEqual(d1, {12: [42]}) + d1[12].append(24) + self.assertEqual(d1, {12: [42, 24]}) + d1[13] + d1[14] + self.assertEqual(d1, {12: [42, 24], 13: [], 14: []}) + self.assert_(d1[12] is not d1[13] is not d1[14]) + d2 = defaultdict(list, foo=1, bar=2) + self.assertEqual(d2.default_factory, list) + self.assertEqual(d2, {"foo": 1, "bar": 2}) + self.assertEqual(d2["foo"], 1) + self.assertEqual(d2["bar"], 2) + self.assertEqual(d2[42], []) + self.assert_("foo" in d2) + self.assert_("foo" in d2.keys()) + self.assert_("bar" in d2) + self.assert_("bar" in d2.keys()) + self.assert_(42 in d2) + self.assert_(42 in d2.keys()) + self.assert_(12 not in d2) + self.assert_(12 not in d2.keys()) + d2.default_factory = None + self.assertEqual(d2.default_factory, None) + try: + d2[15] + except KeyError, err: + self.assertEqual(err.args, (15,)) + else: + self.fail("d2[15] didn't raise KeyError") + + def test_missing(self): + d1 = defaultdict() + self.assertRaises(KeyError, d1.__missing__, 42) + d1.default_factory = list + self.assertEqual(d1.__missing__(42), []) + + def test_repr(self): + d1 = defaultdict() + self.assertEqual(d1.default_factory, None) + self.assertEqual(repr(d1), "defaultdict(None, {})") + d1[11] = 41 + self.assertEqual(repr(d1), "defaultdict(None, {11: 41})") + d2 = defaultdict(0) + self.assertEqual(d2.default_factory, 0) + d2[12] = 42 + self.assertEqual(repr(d2), "defaultdict(0, {12: 42})") + def foo(): return 43 + d3 = defaultdict(foo) + self.assert_(d3.default_factory is foo) + d3[13] + self.assertEqual(repr(d3), "defaultdict(%s, {13: 43})" % repr(foo)) + + def test_print(self): + d1 = defaultdict() + def foo(): return 42 + d2 = defaultdict(foo, {1: 2}) + # NOTE: We can't use tempfile.[Named]TemporaryFile since this + # code must exercise the tp_print C code, which only gets + # invoked for *real* files. + tfn = tempfile.mktemp() + try: + f = open(tfn, "w+") + try: + print >>f, d1 + print >>f, d2 + f.seek(0) + self.assertEqual(f.readline(), repr(d1) + "\n") + self.assertEqual(f.readline(), repr(d2) + "\n") + finally: + f.close() + finally: + os.remove(tfn) + + def test_copy(self): + d1 = defaultdict() + d2 = d1.copy() + self.assertEqual(type(d2), defaultdict) + self.assertEqual(d2.default_factory, None) + self.assertEqual(d2, {}) + d1.default_factory = list + d3 = d1.copy() + self.assertEqual(type(d3), defaultdict) + self.assertEqual(d3.default_factory, list) + self.assertEqual(d3, {}) + d1[42] + d4 = d1.copy() + self.assertEqual(type(d4), defaultdict) + self.assertEqual(d4.default_factory, list) + self.assertEqual(d4, {42: []}) + d4[12] + self.assertEqual(d4, {42: [], 12: []}) + + def test_shallow_copy(self): + d1 = defaultdict(foobar, {1: 1}) + d2 = copy.copy(d1) + self.assertEqual(d2.default_factory, foobar) + self.assertEqual(d2, d1) + d1.default_factory = list + d2 = copy.copy(d1) + self.assertEqual(d2.default_factory, list) + self.assertEqual(d2, d1) + + def test_deep_copy(self): + d1 = defaultdict(foobar, {1: [1]}) + d2 = copy.deepcopy(d1) + self.assertEqual(d2.default_factory, foobar) + self.assertEqual(d2, d1) + self.assert_(d1[1] is not d2[1]) + d1.default_factory = list + d2 = copy.deepcopy(d1) + self.assertEqual(d2.default_factory, list) + self.assertEqual(d2, d1) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index e13829c..f3f78e7 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -395,6 +395,56 @@ class DictTest(unittest.TestCase): else: self.fail("< didn't raise Exc") + def test_missing(self): + # Make sure dict doesn't have a __missing__ method + self.assertEqual(hasattr(dict, "__missing__"), False) + self.assertEqual(hasattr({}, "__missing__"), False) + # Test several cases: + # (D) subclass defines __missing__ method returning a value + # (E) subclass defines __missing__ method raising RuntimeError + # (F) subclass sets __missing__ instance variable (no effect) + # (G) subclass doesn't define __missing__ at a all + class D(dict): + def __missing__(self, key): + return 42 + d = D({1: 2, 3: 4}) + self.assertEqual(d[1], 2) + self.assertEqual(d[3], 4) + self.assert_(2 not in d) + self.assert_(2 not in d.keys()) + self.assertEqual(d[2], 42) + class E(dict): + def __missing__(self, key): + raise RuntimeError(key) + e = E() + try: + e[42] + except RuntimeError, err: + self.assertEqual(err.args, (42,)) + else: + self.fail_("e[42] didn't raise RuntimeError") + class F(dict): + def __init__(self): + # An instance variable __missing__ should have no effect + self.__missing__ = lambda key: None + f = F() + try: + f[42] + except KeyError, err: + self.assertEqual(err.args, (42,)) + else: + self.fail_("f[42] didn't raise KeyError") + class G(dict): + pass + g = G() + try: + g[42] + except KeyError, err: + self.assertEqual(err.args, (42,)) + else: + self.fail_("g[42] didn't raise KeyError") + + import mapping_tests class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol): diff --git a/Lib/test/test_userdict.py b/Lib/test/test_userdict.py index 2d5fa03..a4b7de4 100644 --- a/Lib/test/test_userdict.py +++ b/Lib/test/test_userdict.py @@ -148,6 +148,55 @@ class UserDictTest(mapping_tests.TestHashMappingProtocol): self.assertEqual(t.popitem(), ("x", 42)) self.assertRaises(KeyError, t.popitem) + def test_missing(self): + # Make sure UserDict doesn't have a __missing__ method + self.assertEqual(hasattr(UserDict, "__missing__"), False) + # Test several cases: + # (D) subclass defines __missing__ method returning a value + # (E) subclass defines __missing__ method raising RuntimeError + # (F) subclass sets __missing__ instance variable (no effect) + # (G) subclass doesn't define __missing__ at a all + class D(UserDict.UserDict): + def __missing__(self, key): + return 42 + d = D({1: 2, 3: 4}) + self.assertEqual(d[1], 2) + self.assertEqual(d[3], 4) + self.assert_(2 not in d) + self.assert_(2 not in d.keys()) + self.assertEqual(d[2], 42) + class E(UserDict.UserDict): + def __missing__(self, key): + raise RuntimeError(key) + e = E() + try: + e[42] + except RuntimeError, err: + self.assertEqual(err.args, (42,)) + else: + self.fail_("e[42] didn't raise RuntimeError") + class F(UserDict.UserDict): + def __init__(self): + # An instance variable __missing__ should have no effect + self.__missing__ = lambda key: None + UserDict.UserDict.__init__(self) + f = F() + try: + f[42] + except KeyError, err: + self.assertEqual(err.args, (42,)) + else: + self.fail_("f[42] didn't raise KeyError") + class G(UserDict.UserDict): + pass + g = G() + try: + g[42] + except KeyError, err: + self.assertEqual(err.args, (42,)) + else: + self.fail_("g[42] didn't raise KeyError") + ########################## # Test Dict Mixin @@ -12,6 +12,11 @@ What's New in Python 2.5 alpha 1? Core and builtins ----------------- +- Patch 1433928: + - The copy module now "copies" function objects (as atomic objects). + - dict.__getitem__ now looks for a __missing__ hook before raising + KeyError. + - Fix the encodings package codec search function to only search inside its own package. Fixes problem reported in patch #1433198. @@ -224,6 +229,9 @@ Core and builtins Extension Modules ----------------- +- Patch 1433928: Added a new type, defaultdict, to the collections module. + This uses the new __missing__ hook behavior added to dict (see above). + - Bug #854823: socketmodule now builds on Sun platforms even when INET_ADDRSTRLEN is not defined. diff --git a/Modules/collectionsmodule.c b/Modules/collectionsmodule.c index 7368d80..b80ab07 100644 --- a/Modules/collectionsmodule.c +++ b/Modules/collectionsmodule.c @@ -1065,10 +1065,269 @@ PyTypeObject dequereviter_type = { 0, }; +/* defaultdict type *********************************************************/ + +typedef struct { + PyDictObject dict; + PyObject *default_factory; +} defdictobject; + +static PyTypeObject defdict_type; /* Forward */ + +PyDoc_STRVAR(defdict_missing_doc, +"__missing__(key) # Called by __getitem__ for missing key; pseudo-code:\n\ + if self.default_factory is None: raise KeyError(key)\n\ + self[key] = value = self.default_factory()\n\ + return value\n\ +"); + +static PyObject * +defdict_missing(defdictobject *dd, PyObject *key) +{ + PyObject *factory = dd->default_factory; + PyObject *value; + if (factory == NULL || factory == Py_None) { + /* XXX Call dict.__missing__(key) */ + PyErr_SetObject(PyExc_KeyError, key); + return NULL; + } + value = PyEval_CallObject(factory, NULL); + if (value == NULL) + return value; + if (PyObject_SetItem((PyObject *)dd, key, value) < 0) { + Py_DECREF(value); + return NULL; + } + return value; +} + +PyDoc_STRVAR(defdict_copy_doc, "D.copy() -> a shallow copy of D."); + +static PyObject * +defdict_copy(defdictobject *dd) +{ + /* This calls the object's class. That only works for subclasses + whose class constructor has the same signature. Subclasses that + define a different constructor signature must override copy(). + */ + return PyObject_CallFunctionObjArgs((PyObject *)dd->dict.ob_type, + dd->default_factory, dd, NULL); +} + +static PyObject * +defdict_reduce(defdictobject *dd) +{ + /* __reduce__ must returns a 5-tuple as follows: + + - factory function + - tuple of args for the factory function + - additional state (here None) + - sequence iterator (here None) + - dictionary iterator (yielding successive (key, value) pairs + + This API is used by pickle.py and copy.py. + + For this to be useful with pickle.py, the default_factory + must be picklable; e.g., None, a built-in, or a global + function in a module or package. + + Both shallow and deep copying are supported, but for deep + copying, the default_factory must be deep-copyable; e.g. None, + or a built-in (functions are not copyable at this time). + + This only works for subclasses as long as their constructor + signature is compatible; the first argument must be the + optional default_factory, defaulting to None. + */ + PyObject *args; + PyObject *items; + PyObject *result; + if (dd->default_factory == NULL || dd->default_factory == Py_None) + args = PyTuple_New(0); + else + args = PyTuple_Pack(1, dd->default_factory); + if (args == NULL) + return NULL; + items = PyObject_CallMethod((PyObject *)dd, "iteritems", "()"); + if (items == NULL) { + Py_DECREF(args); + return NULL; + } + result = PyTuple_Pack(5, dd->dict.ob_type, args, + Py_None, Py_None, items); + Py_DECREF(args); + return result; +} + +static PyMethodDef defdict_methods[] = { + {"__missing__", (PyCFunction)defdict_missing, METH_O, + defdict_missing_doc}, + {"copy", (PyCFunction)defdict_copy, METH_NOARGS, + defdict_copy_doc}, + {"__copy__", (PyCFunction)defdict_copy, METH_NOARGS, + defdict_copy_doc}, + {"__reduce__", (PyCFunction)defdict_reduce, METH_NOARGS, + reduce_doc}, + {NULL} +}; + +static PyMemberDef defdict_members[] = { + {"default_factory", T_OBJECT, + offsetof(defdictobject, default_factory), 0, + PyDoc_STR("Factory for default value called by __missing__().")}, + {NULL} +}; + +static void +defdict_dealloc(defdictobject *dd) +{ + Py_CLEAR(dd->default_factory); + PyDict_Type.tp_dealloc((PyObject *)dd); +} + +static int +defdict_print(defdictobject *dd, FILE *fp, int flags) +{ + int sts; + fprintf(fp, "defaultdict("); + if (dd->default_factory == NULL) + fprintf(fp, "None"); + else { + PyObject_Print(dd->default_factory, fp, 0); + } + fprintf(fp, ", "); + sts = PyDict_Type.tp_print((PyObject *)dd, fp, 0); + fprintf(fp, ")"); + return sts; +} + +static PyObject * +defdict_repr(defdictobject *dd) +{ + PyObject *defrepr; + PyObject *baserepr; + PyObject *result; + baserepr = PyDict_Type.tp_repr((PyObject *)dd); + if (baserepr == NULL) + return NULL; + if (dd->default_factory == NULL) + defrepr = PyString_FromString("None"); + else + defrepr = PyObject_Repr(dd->default_factory); + if (defrepr == NULL) { + Py_DECREF(baserepr); + return NULL; + } + result = PyString_FromFormat("defaultdict(%s, %s)", + PyString_AS_STRING(defrepr), + PyString_AS_STRING(baserepr)); + Py_DECREF(defrepr); + Py_DECREF(baserepr); + return result; +} + +static int +defdict_traverse(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(((defdictobject *)self)->default_factory); + return PyDict_Type.tp_traverse(self, visit, arg); +} + +static int +defdict_tp_clear(defdictobject *dd) +{ + if (dd->default_factory != NULL) { + Py_DECREF(dd->default_factory); + dd->default_factory = NULL; + } + return PyDict_Type.tp_clear((PyObject *)dd); +} + +static int +defdict_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + defdictobject *dd = (defdictobject *)self; + PyObject *olddefault = dd->default_factory; + PyObject *newdefault = NULL; + PyObject *newargs; + int result; + if (args == NULL || !PyTuple_Check(args)) + newargs = PyTuple_New(0); + else { + Py_ssize_t n = PyTuple_GET_SIZE(args); + if (n > 0) + newdefault = PyTuple_GET_ITEM(args, 0); + newargs = PySequence_GetSlice(args, 1, n); + } + if (newargs == NULL) + return -1; + Py_XINCREF(newdefault); + dd->default_factory = newdefault; + result = PyDict_Type.tp_init(self, newargs, kwds); + Py_DECREF(newargs); + Py_XDECREF(olddefault); + return result; +} + +PyDoc_STRVAR(defdict_doc, +"defaultdict(default_factory) --> dict with default factory\n\ +\n\ +The default factory is called without arguments to produce\n\ +a new value when a key is not present, in __getitem__ only.\n\ +A defaultdict compares equal to a dict with the same items.\n\ +"); + +static PyTypeObject defdict_type = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "collections.defaultdict", /* tp_name */ + sizeof(defdictobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)defdict_dealloc, /* tp_dealloc */ + (printfunc)defdict_print, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)defdict_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_HAVE_WEAKREFS, /* tp_flags */ + defdict_doc, /* tp_doc */ + (traverseproc)defdict_traverse, /* tp_traverse */ + (inquiry)defdict_tp_clear, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset*/ + 0, /* tp_iter */ + 0, /* tp_iternext */ + defdict_methods, /* tp_methods */ + defdict_members, /* tp_members */ + 0, /* tp_getset */ + &PyDict_Type, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)defdict_init, /* tp_init */ + PyType_GenericAlloc, /* tp_alloc */ + 0, /* tp_new */ + PyObject_GC_Del, /* tp_free */ +}; + /* module level code ********************************************************/ PyDoc_STRVAR(module_doc, -"High performance data structures\n\ +"High performance data structures.\n\ +- deque: ordered collection accessible from endpoints only\n\ +- defaultdict: dict subclass with a default value factory\n\ "); PyMODINIT_FUNC @@ -1085,6 +1344,11 @@ initcollections(void) Py_INCREF(&deque_type); PyModule_AddObject(m, "deque", (PyObject *)&deque_type); + if (PyType_Ready(&defdict_type) < 0) + return; + Py_INCREF(&defdict_type); + PyModule_AddObject(m, "defaultdict", (PyObject *)&defdict_type); + if (PyType_Ready(&dequeiter_type) < 0) return; diff --git a/Objects/dictobject.c b/Objects/dictobject.c index f5e5320..2254762 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -882,8 +882,22 @@ dict_subscript(dictobject *mp, register PyObject *key) return NULL; } v = (mp->ma_lookup)(mp, key, hash) -> me_value; - if (v == NULL) + if (v == NULL) { + if (!PyDict_CheckExact(mp)) { + /* Look up __missing__ method if we're a subclass. */ + static PyObject *missing_str = NULL; + if (missing_str == NULL) + missing_str = + PyString_InternFromString("__missing__"); + PyObject *missing = _PyType_Lookup(mp->ob_type, + missing_str); + if (missing != NULL) + return PyObject_CallFunctionObjArgs(missing, + (PyObject *)mp, key, NULL); + } PyErr_SetObject(PyExc_KeyError, key); + return NULL; + } else Py_INCREF(v); return v; |