Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1968ad3

Browse files
committed
- Patch 1433928:
- The copy module now "copies" function objects (as atomic objects). - dict.__getitem__ now looks for a __missing__ hook before raising KeyError. - Added a new type, defaultdict, to the collections module. This uses the new __missing__ hook behavior added to dict (see above).
1 parent ab51f5f commit 1968ad3

12 files changed

Lines changed: 611 additions & 10 deletions

File tree

Doc/lib/libcollections.tex

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ \section{\module{collections} ---
88
\versionadded{2.4}
99

1010

11-
This module implements high-performance container datatypes. Currently, the
12-
only datatype is a deque. Future additions may include B-trees
13-
and Fibonacci heaps.
11+
This module implements high-performance container datatypes. Currently,
12+
there are two datatypes, deque and defaultdict.
13+
Future additions may include B-trees and Fibonacci heaps.
14+
\versionchanged[Added defaultdict]{2.5}
1415

1516
\begin{funcdesc}{deque}{\optional{iterable}}
1617
Returns a new deque objected initialized left-to-right (using
@@ -211,3 +212,46 @@ \subsection{Recipes \label{deque-recipes}}
211212
[[[['a', 'b'], ['c', 'd']], [['e', 'f'], ['g', 'h']]]]
212213
213214
\end{verbatim}
215+
216+
217+
218+
\begin{funcdesc}{defaultdict}{\optional{default_factory\optional{, ...}}}
219+
Returns a new dictionary-like object. \class{defaultdict} is a subclass
220+
of the builtin \class{dict} class. It overrides one method and adds one
221+
writable instance variable. The remaining functionality is the same as
222+
for the \class{dict} class and is not documented here.
223+
224+
The first argument provides the initial value for the
225+
\member{default_factory} attribute; it defaults to \code{None}.
226+
All remaining arguments are treated the same as if they were
227+
passed to the \class{dict} constructor, including keyword arguments.
228+
229+
\versionadded{2.5}
230+
\end{funcdesc}
231+
232+
\class{defaultdict} objects support the following method in addition to
233+
the standard \class{dict} operations:
234+
235+
\begin{methoddesc}{__missing__}{key}
236+
If the \member{default_factory} attribute is \code{None}, this raises
237+
an \exception{KeyError} exception with the \var{key} as argument.
238+
239+
If \member{default_factory} is not \code{None}, it is called without
240+
arguments to provide a default value for the given \var{key}, this
241+
value is inserted in the dictionary for the \var{key}, and returned.
242+
243+
If calling \member{default_factory} raises an exception this exception
244+
is propagated unchanged.
245+
246+
This method is called by the \method{__getitem__} method of the
247+
\class{dict} class when the requested key is not found; whatever it
248+
returns or raises is then returned or raised by \method{__getitem__}.
249+
\end{methoddesc}
250+
251+
\class{defaultdict} objects support the following instance variable:
252+
253+
\begin{datadesc}{default_factory}
254+
This attribute is used by the \method{__missing__} method; it is initialized
255+
from the first argument to the constructor, if present, or to \code{None},
256+
if absent.
257+
\end{datadesc}

Doc/lib/libcopy.tex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,12 @@ \section{\module{copy} ---
6767

6868
\end{itemize}
6969

70-
This version does not copy types like module, class, function, method,
70+
This module does not copy types like module, method,
7171
stack trace, stack frame, file, socket, window, array, or any similar
72-
types.
72+
types. It does ``copy'' functions and classes (shallow and deeply),
73+
by returning the original object unchanged; this is compatible with
74+
the way these are treated by the \module{pickle} module.
75+
\versionchanged[Added copying functions]{2.5}
7376

7477
Classes can use the same interfaces to control copying that they use
7578
to control pickling. See the description of module

Doc/lib/libstdtypes.tex

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1350,7 +1350,7 @@ \subsection{Mapping Types --- \class{dict} \label{typesmapping}}
13501350

13511351
\begin{tableiii}{c|l|c}{code}{Operation}{Result}{Notes}
13521352
\lineiii{len(\var{a})}{the number of items in \var{a}}{}
1353-
\lineiii{\var{a}[\var{k}]}{the item of \var{a} with key \var{k}}{(1)}
1353+
\lineiii{\var{a}[\var{k}]}{the item of \var{a} with key \var{k}}{(1), (10)}
13541354
\lineiii{\var{a}[\var{k}] = \var{v}}
13551355
{set \code{\var{a}[\var{k}]} to \var{v}}
13561356
{}
@@ -1454,6 +1454,17 @@ \subsection{Mapping Types --- \class{dict} \label{typesmapping}}
14541454
\versionchanged[Allowed the argument to be an iterable of key/value
14551455
pairs and allowed keyword arguments]{2.4}
14561456

1457+
\item[(10)] If a subclass of dict defines a method \method{__missing__},
1458+
if the key \var{k} is not present, the \var{a}[\var{k}] operation calls
1459+
that method with the key \var{k} as argument. The \var{a}[\var{k}]
1460+
operation then returns or raises whatever is returned or raised by the
1461+
\function{__missing__}(\var{k}) call if the key is not present.
1462+
No other operations or methods invoke \method{__missing__}().
1463+
If \method{__missing__} is not defined, \exception{KeyError} is raised.
1464+
\method{__missing__} must be a method; it cannot be an instance variable.
1465+
For an example, see \module{collections}.\class{defaultdict}.
1466+
\versionadded{2.5}
1467+
14571468
\end{description}
14581469

14591470
\subsection{File Objects

Lib/UserDict.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ def __cmp__(self, dict):
1414
else:
1515
return cmp(self.data, dict)
1616
def __len__(self): return len(self.data)
17-
def __getitem__(self, key): return self.data[key]
17+
def __getitem__(self, key):
18+
if key in self.data:
19+
return self.data[key]
20+
if hasattr(self.__class__, "__missing__"):
21+
return self.__class__.__missing__(self, key)
22+
raise KeyError(key)
1823
def __setitem__(self, key, item): self.data[key] = item
1924
def __delitem__(self, key): del self.data[key]
2025
def clear(self): self.data.clear()

Lib/copy.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ def _copy_immutable(x):
101101
return x
102102
for t in (type(None), int, long, float, bool, str, tuple,
103103
frozenset, type, xrange, types.ClassType,
104-
types.BuiltinFunctionType):
104+
types.BuiltinFunctionType,
105+
types.FunctionType):
105106
d[t] = _copy_immutable
106107
for name in ("ComplexType", "UnicodeType", "CodeType"):
107108
t = getattr(types, name, None)
@@ -217,6 +218,7 @@ def _deepcopy_atomic(x, memo):
217218
d[xrange] = _deepcopy_atomic
218219
d[types.ClassType] = _deepcopy_atomic
219220
d[types.BuiltinFunctionType] = _deepcopy_atomic
221+
d[types.FunctionType] = _deepcopy_atomic
220222

221223
def _deepcopy_list(x, memo):
222224
y = []

Lib/test/test_copy.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,22 @@ def __getstate__(self):
568568
raise ValueError, "ain't got no stickin' state"
569569
self.assertRaises(ValueError, copy.copy, EvilState())
570570

571+
def test_copy_function(self):
572+
self.assertEqual(copy.copy(global_foo), global_foo)
573+
def foo(x, y): return x+y
574+
self.assertEqual(copy.copy(foo), foo)
575+
bar = lambda: None
576+
self.assertEqual(copy.copy(bar), bar)
577+
578+
def test_deepcopy_function(self):
579+
self.assertEqual(copy.deepcopy(global_foo), global_foo)
580+
def foo(x, y): return x+y
581+
self.assertEqual(copy.deepcopy(foo), foo)
582+
bar = lambda: None
583+
self.assertEqual(copy.deepcopy(bar), bar)
584+
585+
def global_foo(x, y): return x+y
586+
571587
def test_main():
572588
test_support.run_unittest(TestCopy)
573589

Lib/test/test_defaultdict.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""Unit tests for collections.defaultdict."""
2+
3+
import os
4+
import copy
5+
import tempfile
6+
import unittest
7+
8+
from collections import defaultdict
9+
10+
def foobar():
11+
return list
12+
13+
class TestDefaultDict(unittest.TestCase):
14+
15+
def test_basic(self):
16+
d1 = defaultdict()
17+
self.assertEqual(d1.default_factory, None)
18+
d1.default_factory = list
19+
d1[12].append(42)
20+
self.assertEqual(d1, {12: [42]})
21+
d1[12].append(24)
22+
self.assertEqual(d1, {12: [42, 24]})
23+
d1[13]
24+
d1[14]
25+
self.assertEqual(d1, {12: [42, 24], 13: [], 14: []})
26+
self.assert_(d1[12] is not d1[13] is not d1[14])
27+
d2 = defaultdict(list, foo=1, bar=2)
28+
self.assertEqual(d2.default_factory, list)
29+
self.assertEqual(d2, {"foo": 1, "bar": 2})
30+
self.assertEqual(d2["foo"], 1)
31+
self.assertEqual(d2["bar"], 2)
32+
self.assertEqual(d2[42], [])
33+
self.assert_("foo" in d2)
34+
self.assert_("foo" in d2.keys())
35+
self.assert_("bar" in d2)
36+
self.assert_("bar" in d2.keys())
37+
self.assert_(42 in d2)
38+
self.assert_(42 in d2.keys())
39+
self.assert_(12 not in d2)
40+
self.assert_(12 not in d2.keys())
41+
d2.default_factory = None
42+
self.assertEqual(d2.default_factory, None)
43+
try:
44+
d2[15]
45+
except KeyError, err:
46+
self.assertEqual(err.args, (15,))
47+
else:
48+
self.fail("d2[15] didn't raise KeyError")
49+
50+
def test_missing(self):
51+
d1 = defaultdict()
52+
self.assertRaises(KeyError, d1.__missing__, 42)
53+
d1.default_factory = list
54+
self.assertEqual(d1.__missing__(42), [])
55+
56+
def test_repr(self):
57+
d1 = defaultdict()
58+
self.assertEqual(d1.default_factory, None)
59+
self.assertEqual(repr(d1), "defaultdict(None, {})")
60+
d1[11] = 41
61+
self.assertEqual(repr(d1), "defaultdict(None, {11: 41})")
62+
d2 = defaultdict(0)
63+
self.assertEqual(d2.default_factory, 0)
64+
d2[12] = 42
65+
self.assertEqual(repr(d2), "defaultdict(0, {12: 42})")
66+
def foo(): return 43
67+
d3 = defaultdict(foo)
68+
self.assert_(d3.default_factory is foo)
69+
d3[13]
70+
self.assertEqual(repr(d3), "defaultdict(%s, {13: 43})" % repr(foo))
71+
72+
def test_print(self):
73+
d1 = defaultdict()
74+
def foo(): return 42
75+
d2 = defaultdict(foo, {1: 2})
76+
# NOTE: We can't use tempfile.[Named]TemporaryFile since this
77+
# code must exercise the tp_print C code, which only gets
78+
# invoked for *real* files.
79+
tfn = tempfile.mktemp()
80+
try:
81+
f = open(tfn, "w+")
82+
try:
83+
print >>f, d1
84+
print >>f, d2
85+
f.seek(0)
86+
self.assertEqual(f.readline(), repr(d1) + "\n")
87+
self.assertEqual(f.readline(), repr(d2) + "\n")
88+
finally:
89+
f.close()
90+
finally:
91+
os.remove(tfn)
92+
93+
def test_copy(self):
94+
d1 = defaultdict()
95+
d2 = d1.copy()
96+
self.assertEqual(type(d2), defaultdict)
97+
self.assertEqual(d2.default_factory, None)
98+
self.assertEqual(d2, {})
99+
d1.default_factory = list
100+
d3 = d1.copy()
101+
self.assertEqual(type(d3), defaultdict)
102+
self.assertEqual(d3.default_factory, list)
103+
self.assertEqual(d3, {})
104+
d1[42]
105+
d4 = d1.copy()
106+
self.assertEqual(type(d4), defaultdict)
107+
self.assertEqual(d4.default_factory, list)
108+
self.assertEqual(d4, {42: []})
109+
d4[12]
110+
self.assertEqual(d4, {42: [], 12: []})
111+
112+
def test_shallow_copy(self):
113+
d1 = defaultdict(foobar, {1: 1})
114+
d2 = copy.copy(d1)
115+
self.assertEqual(d2.default_factory, foobar)
116+
self.assertEqual(d2, d1)
117+
d1.default_factory = list
118+
d2 = copy.copy(d1)
119+
self.assertEqual(d2.default_factory, list)
120+
self.assertEqual(d2, d1)
121+
122+
def test_deep_copy(self):
123+
d1 = defaultdict(foobar, {1: [1]})
124+
d2 = copy.deepcopy(d1)
125+
self.assertEqual(d2.default_factory, foobar)
126+
self.assertEqual(d2, d1)
127+
self.assert_(d1[1] is not d2[1])
128+
d1.default_factory = list
129+
d2 = copy.deepcopy(d1)
130+
self.assertEqual(d2.default_factory, list)
131+
self.assertEqual(d2, d1)
132+
133+
134+
if __name__ == "__main__":
135+
unittest.main()

Lib/test/test_dict.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,56 @@ def __eq__(self, other):
395395
else:
396396
self.fail("< didn't raise Exc")
397397

398+
def test_missing(self):
399+
# Make sure dict doesn't have a __missing__ method
400+
self.assertEqual(hasattr(dict, "__missing__"), False)
401+
self.assertEqual(hasattr({}, "__missing__"), False)
402+
# Test several cases:
403+
# (D) subclass defines __missing__ method returning a value
404+
# (E) subclass defines __missing__ method raising RuntimeError
405+
# (F) subclass sets __missing__ instance variable (no effect)
406+
# (G) subclass doesn't define __missing__ at a all
407+
class D(dict):
408+
def __missing__(self, key):
409+
return 42
410+
d = D({1: 2, 3: 4})
411+
self.assertEqual(d[1], 2)
412+
self.assertEqual(d[3], 4)
413+
self.assert_(2 not in d)
414+
self.assert_(2 not in d.keys())
415+
self.assertEqual(d[2], 42)
416+
class E(dict):
417+
def __missing__(self, key):
418+
raise RuntimeError(key)
419+
e = E()
420+
try:
421+
e[42]
422+
except RuntimeError, err:
423+
self.assertEqual(err.args, (42,))
424+
else:
425+
self.fail_("e[42] didn't raise RuntimeError")
426+
class F(dict):
427+
def __init__(self):
428+
# An instance variable __missing__ should have no effect
429+
self.__missing__ = lambda key: None
430+
f = F()
431+
try:
432+
f[42]
433+
except KeyError, err:
434+
self.assertEqual(err.args, (42,))
435+
else:
436+
self.fail_("f[42] didn't raise KeyError")
437+
class G(dict):
438+
pass
439+
g = G()
440+
try:
441+
g[42]
442+
except KeyError, err:
443+
self.assertEqual(err.args, (42,))
444+
else:
445+
self.fail_("g[42] didn't raise KeyError")
446+
447+
398448
import mapping_tests
399449

400450
class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol):

0 commit comments

Comments
 (0)