OILS / opy / _regtest / src / collections.py View on Github | oilshell.org

743 lines, 347 significant
1from __future__ import print_function # for OPy compiler
2'''This module implements specialized container datatypes providing
3alternatives to Python's general purpose built-in containers, dict,
4list, set, and tuple.
5
6* namedtuple factory function for creating tuple subclasses with named fields
7* deque list-like container with fast appends and pops on either end
8* Counter dict subclass for counting hashable objects
9* OrderedDict dict subclass that remembers the order entries were added
10* defaultdict dict subclass that calls a factory function to supply missing values
11
12'''
13
14__all__ = ['Counter', 'deque', 'defaultdict', 'namedtuple', 'OrderedDict']
15# For bootstrapping reasons, the collection ABCs are defined in _abcoll.py.
16# They should however be considered an integral part of collections.py.
17from _abcoll import *
18import _abcoll
19__all__ += _abcoll.__all__
20
21from _collections import deque, defaultdict
22from operator import itemgetter as _itemgetter, eq as _eq
23from keyword import iskeyword as _iskeyword
24import sys as _sys
25import heapq as _heapq
26from itertools import repeat as _repeat, chain as _chain, starmap as _starmap
27from itertools import imap as _imap
28
29try:
30 from thread import get_ident as _get_ident
31except ImportError:
32 from dummy_thread import get_ident as _get_ident
33
34
35################################################################################
36### OrderedDict
37################################################################################
38
39class OrderedDict(dict):
40 'Dictionary that remembers insertion order'
41 # An inherited dict maps keys to values.
42 # The inherited dict provides __getitem__, __len__, __contains__, and get.
43 # The remaining methods are order-aware.
44 # Big-O running times for all methods are the same as regular dictionaries.
45
46 # The internal self.__map dict maps keys to links in a doubly linked list.
47 # The circular doubly linked list starts and ends with a sentinel element.
48 # The sentinel element never gets deleted (this simplifies the algorithm).
49 # Each link is stored as a list of length three: [PREV, NEXT, KEY].
50
51 def __init__(*args, **kwds):
52 '''Initialize an ordered dictionary. The signature is the same as
53 regular dictionaries, but keyword arguments are not recommended because
54 their insertion order is arbitrary.
55
56 '''
57 if not args:
58 raise TypeError("descriptor '__init__' of 'OrderedDict' object "
59 "needs an argument")
60 self = args[0]
61 args = args[1:]
62 if len(args) > 1:
63 raise TypeError('expected at most 1 arguments, got %d' % len(args))
64 try:
65 self.__root
66 except AttributeError:
67 self.__root = root = [] # sentinel node
68 root[:] = [root, root, None]
69 self.__map = {}
70 self.__update(*args, **kwds)
71
72 def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
73 'od.__setitem__(i, y) <==> od[i]=y'
74 # Setting a new item creates a new link at the end of the linked list,
75 # and the inherited dictionary is updated with the new key/value pair.
76 if key not in self:
77 root = self.__root
78 last = root[0]
79 last[1] = root[0] = self.__map[key] = [last, root, key]
80 return dict_setitem(self, key, value)
81
82 def __delitem__(self, key, dict_delitem=dict.__delitem__):
83 'od.__delitem__(y) <==> del od[y]'
84 # Deleting an existing item uses self.__map to find the link which gets
85 # removed by updating the links in the predecessor and successor nodes.
86 dict_delitem(self, key)
87 link_prev, link_next, _ = self.__map.pop(key)
88 link_prev[1] = link_next # update link_prev[NEXT]
89 link_next[0] = link_prev # update link_next[PREV]
90
91 def __iter__(self):
92 'od.__iter__() <==> iter(od)'
93 # Traverse the linked list in order.
94 root = self.__root
95 curr = root[1] # start at the first node
96 while curr is not root:
97 yield curr[2] # yield the curr[KEY]
98 curr = curr[1] # move to next node
99
100 def __reversed__(self):
101 'od.__reversed__() <==> reversed(od)'
102 # Traverse the linked list in reverse order.
103 root = self.__root
104 curr = root[0] # start at the last node
105 while curr is not root:
106 yield curr[2] # yield the curr[KEY]
107 curr = curr[0] # move to previous node
108
109 def clear(self):
110 'od.clear() -> None. Remove all items from od.'
111 root = self.__root
112 root[:] = [root, root, None]
113 self.__map.clear()
114 dict.clear(self)
115
116 # -- the following methods do not depend on the internal structure --
117
118 def keys(self):
119 'od.keys() -> list of keys in od'
120 return list(self)
121
122 def values(self):
123 'od.values() -> list of values in od'
124 return [self[key] for key in self]
125
126 def items(self):
127 'od.items() -> list of (key, value) pairs in od'
128 return [(key, self[key]) for key in self]
129
130 def iterkeys(self):
131 'od.iterkeys() -> an iterator over the keys in od'
132 return iter(self)
133
134 def itervalues(self):
135 'od.itervalues -> an iterator over the values in od'
136 for k in self:
137 yield self[k]
138
139 def iteritems(self):
140 'od.iteritems -> an iterator over the (key, value) pairs in od'
141 for k in self:
142 yield (k, self[k])
143
144 update = MutableMapping.update
145
146 __update = update # let subclasses override update without breaking __init__
147
148 __marker = object()
149
150 def pop(self, key, default=__marker):
151 '''od.pop(k[,d]) -> v, remove specified key and return the corresponding
152 value. If key is not found, d is returned if given, otherwise KeyError
153 is raised.
154
155 '''
156 if key in self:
157 result = self[key]
158 del self[key]
159 return result
160 if default is self.__marker:
161 raise KeyError(key)
162 return default
163
164 def setdefault(self, key, default=None):
165 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
166 if key in self:
167 return self[key]
168 self[key] = default
169 return default
170
171 def popitem(self, last=True):
172 '''od.popitem() -> (k, v), return and remove a (key, value) pair.
173 Pairs are returned in LIFO order if last is true or FIFO order if false.
174
175 '''
176 if not self:
177 raise KeyError('dictionary is empty')
178 key = next(reversed(self) if last else iter(self))
179 value = self.pop(key)
180 return key, value
181
182 def __repr__(self, _repr_running={}):
183 'od.__repr__() <==> repr(od)'
184 call_key = id(self), _get_ident()
185 if call_key in _repr_running:
186 return '...'
187 _repr_running[call_key] = 1
188 try:
189 if not self:
190 return '%s()' % (self.__class__.__name__,)
191 return '%s(%r)' % (self.__class__.__name__, self.items())
192 finally:
193 del _repr_running[call_key]
194
195 def __reduce__(self):
196 'Return state information for pickling'
197 items = [[k, self[k]] for k in self]
198 inst_dict = vars(self).copy()
199 for k in vars(OrderedDict()):
200 inst_dict.pop(k, None)
201 if inst_dict:
202 return (self.__class__, (items,), inst_dict)
203 return self.__class__, (items,)
204
205 def copy(self):
206 'od.copy() -> a shallow copy of od'
207 return self.__class__(self)
208
209 @classmethod
210 def fromkeys(cls, iterable, value=None):
211 '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S.
212 If not specified, the value defaults to None.
213
214 '''
215 self = cls()
216 for key in iterable:
217 self[key] = value
218 return self
219
220 def __eq__(self, other):
221 '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
222 while comparison to a regular mapping is order-insensitive.
223
224 '''
225 if isinstance(other, OrderedDict):
226 return dict.__eq__(self, other) and all(_imap(_eq, self, other))
227 return dict.__eq__(self, other)
228
229 def __ne__(self, other):
230 'od.__ne__(y) <==> od!=y'
231 return not self == other
232
233 # -- the following methods support python 3.x style dictionary views --
234
235 def viewkeys(self):
236 "od.viewkeys() -> a set-like object providing a view on od's keys"
237 return KeysView(self)
238
239 def viewvalues(self):
240 "od.viewvalues() -> an object providing a view on od's values"
241 return ValuesView(self)
242
243 def viewitems(self):
244 "od.viewitems() -> a set-like object providing a view on od's items"
245 return ItemsView(self)
246
247
248################################################################################
249### namedtuple
250################################################################################
251
252_class_template = '''\
253class {typename}(tuple):
254 '{typename}({arg_list})'
255
256 __slots__ = ()
257
258 _fields = {field_names!r}
259
260 def __new__(_cls, {arg_list}):
261 'Create new instance of {typename}({arg_list})'
262 return _tuple.__new__(_cls, ({arg_list}))
263
264 @classmethod
265 def _make(cls, iterable, new=tuple.__new__, len=len):
266 'Make a new {typename} object from a sequence or iterable'
267 result = new(cls, iterable)
268 if len(result) != {num_fields:d}:
269 raise TypeError('Expected {num_fields:d} arguments, got %d' % len(result))
270 return result
271
272 def __repr__(self):
273 'Return a nicely formatted representation string'
274 return '{typename}({repr_fmt})' % self
275
276 def _asdict(self):
277 'Return a new OrderedDict which maps field names to their values'
278 return OrderedDict(zip(self._fields, self))
279
280 def _replace(_self, **kwds):
281 'Return a new {typename} object replacing specified fields with new values'
282 result = _self._make(map(kwds.pop, {field_names!r}, _self))
283 if kwds:
284 raise ValueError('Got unexpected field names: %r' % kwds.keys())
285 return result
286
287 def __getnewargs__(self):
288 'Return self as a plain tuple. Used by copy and pickle.'
289 return tuple(self)
290
291 __dict__ = _property(_asdict)
292
293 def __getstate__(self):
294 'Exclude the OrderedDict from pickling'
295 pass
296
297{field_defs}
298'''
299
300_repr_template = '{name}=%r'
301
302_field_template = '''\
303 {name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}')
304'''
305
306def namedtuple(typename, field_names, verbose=False, rename=False):
307 """Returns a new subclass of tuple with named fields.
308
309 >>> Point = namedtuple('Point', ['x', 'y'])
310 >>> Point.__doc__ # docstring for the new class
311 'Point(x, y)'
312 >>> p = Point(11, y=22) # instantiate with positional args or keywords
313 >>> p[0] + p[1] # indexable like a plain tuple
314 33
315 >>> x, y = p # unpack like a regular tuple
316 >>> x, y
317 (11, 22)
318 >>> p.x + p.y # fields also accessible by name
319 33
320 >>> d = p._asdict() # convert to a dictionary
321 >>> d['x']
322 11
323 >>> Point(**d) # convert from a dictionary
324 Point(x=11, y=22)
325 >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
326 Point(x=100, y=22)
327
328 """
329
330 # Validate the field names. At the user's option, either generate an error
331 # message or automatically replace the field name with a valid name.
332 if isinstance(field_names, basestring):
333 field_names = field_names.replace(',', ' ').split()
334 field_names = map(str, field_names)
335 typename = str(typename)
336 if rename:
337 seen = set()
338 for index, name in enumerate(field_names):
339 if (not all(c.isalnum() or c=='_' for c in name)
340 or _iskeyword(name)
341 or not name
342 or name[0].isdigit()
343 or name.startswith('_')
344 or name in seen):
345 field_names[index] = '_%d' % index
346 seen.add(name)
347 for name in [typename] + field_names:
348 if type(name) != str:
349 raise TypeError('Type names and field names must be strings')
350 if not all(c.isalnum() or c=='_' for c in name):
351 raise ValueError('Type names and field names can only contain '
352 'alphanumeric characters and underscores: %r' % name)
353 if _iskeyword(name):
354 raise ValueError('Type names and field names cannot be a '
355 'keyword: %r' % name)
356 if name[0].isdigit():
357 raise ValueError('Type names and field names cannot start with '
358 'a number: %r' % name)
359 seen = set()
360 for name in field_names:
361 if name.startswith('_') and not rename:
362 raise ValueError('Field names cannot start with an underscore: '
363 '%r' % name)
364 if name in seen:
365 raise ValueError('Encountered duplicate field name: %r' % name)
366 seen.add(name)
367
368 # Fill-in the class template
369 class_definition = _class_template.format(
370 typename = typename,
371 field_names = tuple(field_names),
372 num_fields = len(field_names),
373 arg_list = repr(tuple(field_names)).replace("'", "")[1:-1],
374 repr_fmt = ', '.join(_repr_template.format(name=name)
375 for name in field_names),
376 field_defs = '\n'.join(_field_template.format(index=index, name=name)
377 for index, name in enumerate(field_names))
378 )
379 if verbose:
380 print(class_definition)
381
382 # Execute the template string in a temporary namespace and support
383 # tracing utilities by setting a value for frame.f_globals['__name__']
384 namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
385 OrderedDict=OrderedDict, _property=property, _tuple=tuple)
386 try:
387 exec class_definition in namespace
388 except SyntaxError as e:
389 raise SyntaxError(e.message + ':\n' + class_definition)
390 result = namespace[typename]
391
392 # For pickling to work, the __module__ variable needs to be set to the frame
393 # where the named tuple is created. Bypass this step in environments where
394 # sys._getframe is not defined (Jython for example) or sys._getframe is not
395 # defined for arguments greater than 0 (IronPython).
396 try:
397 result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
398 except (AttributeError, ValueError):
399 pass
400
401 return result
402
403
404########################################################################
405### Counter
406########################################################################
407
408class Counter(dict):
409 '''Dict subclass for counting hashable items. Sometimes called a bag
410 or multiset. Elements are stored as dictionary keys and their counts
411 are stored as dictionary values.
412
413 >>> c = Counter('abcdeabcdabcaba') # count elements from a string
414
415 >>> c.most_common(3) # three most common elements
416 [('a', 5), ('b', 4), ('c', 3)]
417 >>> sorted(c) # list all unique elements
418 ['a', 'b', 'c', 'd', 'e']
419 >>> ''.join(sorted(c.elements())) # list elements with repetitions
420 'aaaaabbbbcccdde'
421 >>> sum(c.values()) # total of all counts
422 15
423
424 >>> c['a'] # count of letter 'a'
425 5
426 >>> for elem in 'shazam': # update counts from an iterable
427 ... c[elem] += 1 # by adding 1 to each element's count
428 >>> c['a'] # now there are seven 'a'
429 7
430 >>> del c['b'] # remove all 'b'
431 >>> c['b'] # now there are zero 'b'
432 0
433
434 >>> d = Counter('simsalabim') # make another counter
435 >>> c.update(d) # add in the second counter
436 >>> c['a'] # now there are nine 'a'
437 9
438
439 >>> c.clear() # empty the counter
440 >>> c
441 Counter()
442
443 Note: If a count is set to zero or reduced to zero, it will remain
444 in the counter until the entry is deleted or the counter is cleared:
445
446 >>> c = Counter('aaabbc')
447 >>> c['b'] -= 2 # reduce the count of 'b' by two
448 >>> c.most_common() # 'b' is still in, but its count is zero
449 [('a', 3), ('c', 1), ('b', 0)]
450
451 '''
452 # References:
453 # http://en.wikipedia.org/wiki/Multiset
454 # http://www.gnu.org/software/smalltalk/manual-base/html_node/Bag.html
455 # http://www.demo2s.com/Tutorial/Cpp/0380__set-multiset/Catalog0380__set-multiset.htm
456 # http://code.activestate.com/recipes/259174/
457 # Knuth, TAOCP Vol. II section 4.6.3
458
459 def __init__(*args, **kwds):
460 '''Create a new, empty Counter object. And if given, count elements
461 from an input iterable. Or, initialize the count from another mapping
462 of elements to their counts.
463
464 >>> c = Counter() # a new, empty counter
465 >>> c = Counter('gallahad') # a new counter from an iterable
466 >>> c = Counter({'a': 4, 'b': 2}) # a new counter from a mapping
467 >>> c = Counter(a=4, b=2) # a new counter from keyword args
468
469 '''
470 if not args:
471 raise TypeError("descriptor '__init__' of 'Counter' object "
472 "needs an argument")
473 self = args[0]
474 args = args[1:]
475 if len(args) > 1:
476 raise TypeError('expected at most 1 arguments, got %d' % len(args))
477 super(Counter, self).__init__()
478 self.update(*args, **kwds)
479
480 def __missing__(self, key):
481 'The count of elements not in the Counter is zero.'
482 # Needed so that self[missing_item] does not raise KeyError
483 return 0
484
485 def most_common(self, n=None):
486 '''List the n most common elements and their counts from the most
487 common to the least. If n is None, then list all element counts.
488
489 >>> Counter('abcdeabcdabcaba').most_common(3)
490 [('a', 5), ('b', 4), ('c', 3)]
491
492 '''
493 # Emulate Bag.sortedByCount from Smalltalk
494 if n is None:
495 return sorted(self.iteritems(), key=_itemgetter(1), reverse=True)
496 return _heapq.nlargest(n, self.iteritems(), key=_itemgetter(1))
497
498 def elements(self):
499 '''Iterator over elements repeating each as many times as its count.
500
501 >>> c = Counter('ABCABC')
502 >>> sorted(c.elements())
503 ['A', 'A', 'B', 'B', 'C', 'C']
504
505 # Knuth's example for prime factors of 1836: 2**2 * 3**3 * 17**1
506 >>> prime_factors = Counter({2: 2, 3: 3, 17: 1})
507 >>> product = 1
508 >>> for factor in prime_factors.elements(): # loop over factors
509 ... product *= factor # and multiply them
510 >>> product
511 1836
512
513 Note, if an element's count has been set to zero or is a negative
514 number, elements() will ignore it.
515
516 '''
517 # Emulate Bag.do from Smalltalk and Multiset.begin from C++.
518 return _chain.from_iterable(_starmap(_repeat, self.iteritems()))
519
520 # Override dict methods where necessary
521
522 @classmethod
523 def fromkeys(cls, iterable, v=None):
524 # There is no equivalent method for counters because setting v=1
525 # means that no element can have a count greater than one.
526 raise NotImplementedError(
527 'Counter.fromkeys() is undefined. Use Counter(iterable) instead.')
528
529 def update(*args, **kwds):
530 '''Like dict.update() but add counts instead of replacing them.
531
532 Source can be an iterable, a dictionary, or another Counter instance.
533
534 >>> c = Counter('which')
535 >>> c.update('witch') # add elements from another iterable
536 >>> d = Counter('watch')
537 >>> c.update(d) # add elements from another counter
538 >>> c['h'] # four 'h' in which, witch, and watch
539 4
540
541 '''
542 # The regular dict.update() operation makes no sense here because the
543 # replace behavior results in the some of original untouched counts
544 # being mixed-in with all of the other counts for a mismash that
545 # doesn't have a straight-forward interpretation in most counting
546 # contexts. Instead, we implement straight-addition. Both the inputs
547 # and outputs are allowed to contain zero and negative counts.
548
549 if not args:
550 raise TypeError("descriptor 'update' of 'Counter' object "
551 "needs an argument")
552 self = args[0]
553 args = args[1:]
554 if len(args) > 1:
555 raise TypeError('expected at most 1 arguments, got %d' % len(args))
556 iterable = args[0] if args else None
557 if iterable is not None:
558 if isinstance(iterable, Mapping):
559 if self:
560 self_get = self.get
561 for elem, count in iterable.iteritems():
562 self[elem] = self_get(elem, 0) + count
563 else:
564 super(Counter, self).update(iterable) # fast path when counter is empty
565 else:
566 self_get = self.get
567 for elem in iterable:
568 self[elem] = self_get(elem, 0) + 1
569 if kwds:
570 self.update(kwds)
571
572 def subtract(*args, **kwds):
573 '''Like dict.update() but subtracts counts instead of replacing them.
574 Counts can be reduced below zero. Both the inputs and outputs are
575 allowed to contain zero and negative counts.
576
577 Source can be an iterable, a dictionary, or another Counter instance.
578
579 >>> c = Counter('which')
580 >>> c.subtract('witch') # subtract elements from another iterable
581 >>> c.subtract(Counter('watch')) # subtract elements from another counter
582 >>> c['h'] # 2 in which, minus 1 in witch, minus 1 in watch
583 0
584 >>> c['w'] # 1 in which, minus 1 in witch, minus 1 in watch
585 -1
586
587 '''
588 if not args:
589 raise TypeError("descriptor 'subtract' of 'Counter' object "
590 "needs an argument")
591 self = args[0]
592 args = args[1:]
593 if len(args) > 1:
594 raise TypeError('expected at most 1 arguments, got %d' % len(args))
595 iterable = args[0] if args else None
596 if iterable is not None:
597 self_get = self.get
598 if isinstance(iterable, Mapping):
599 for elem, count in iterable.items():
600 self[elem] = self_get(elem, 0) - count
601 else:
602 for elem in iterable:
603 self[elem] = self_get(elem, 0) - 1
604 if kwds:
605 self.subtract(kwds)
606
607 def copy(self):
608 'Return a shallow copy.'
609 return self.__class__(self)
610
611 def __reduce__(self):
612 return self.__class__, (dict(self),)
613
614 def __delitem__(self, elem):
615 'Like dict.__delitem__() but does not raise KeyError for missing values.'
616 if elem in self:
617 super(Counter, self).__delitem__(elem)
618
619 def __repr__(self):
620 if not self:
621 return '%s()' % self.__class__.__name__
622 items = ', '.join(map('%r: %r'.__mod__, self.most_common()))
623 return '%s({%s})' % (self.__class__.__name__, items)
624
625 # Multiset-style mathematical operations discussed in:
626 # Knuth TAOCP Volume II section 4.6.3 exercise 19
627 # and at http://en.wikipedia.org/wiki/Multiset
628 #
629 # Outputs guaranteed to only include positive counts.
630 #
631 # To strip negative and zero counts, add-in an empty counter:
632 # c += Counter()
633
634 def __add__(self, other):
635 '''Add counts from two counters.
636
637 >>> Counter('abbb') + Counter('bcc')
638 Counter({'b': 4, 'c': 2, 'a': 1})
639
640 '''
641 if not isinstance(other, Counter):
642 return NotImplemented
643 result = Counter()
644 for elem, count in self.items():
645 newcount = count + other[elem]
646 if newcount > 0:
647 result[elem] = newcount
648 for elem, count in other.items():
649 if elem not in self and count > 0:
650 result[elem] = count
651 return result
652
653 def __sub__(self, other):
654 ''' Subtract count, but keep only results with positive counts.
655
656 >>> Counter('abbbc') - Counter('bccd')
657 Counter({'b': 2, 'a': 1})
658
659 '''
660 if not isinstance(other, Counter):
661 return NotImplemented
662 result = Counter()
663 for elem, count in self.items():
664 newcount = count - other[elem]
665 if newcount > 0:
666 result[elem] = newcount
667 for elem, count in other.items():
668 if elem not in self and count < 0:
669 result[elem] = 0 - count
670 return result
671
672 def __or__(self, other):
673 '''Union is the maximum of value in either of the input counters.
674
675 >>> Counter('abbb') | Counter('bcc')
676 Counter({'b': 3, 'c': 2, 'a': 1})
677
678 '''
679 if not isinstance(other, Counter):
680 return NotImplemented
681 result = Counter()
682 for elem, count in self.items():
683 other_count = other[elem]
684 newcount = other_count if count < other_count else count
685 if newcount > 0:
686 result[elem] = newcount
687 for elem, count in other.items():
688 if elem not in self and count > 0:
689 result[elem] = count
690 return result
691
692 def __and__(self, other):
693 ''' Intersection is the minimum of corresponding counts.
694
695 >>> Counter('abbb') & Counter('bcc')
696 Counter({'b': 1})
697
698 '''
699 if not isinstance(other, Counter):
700 return NotImplemented
701 result = Counter()
702 for elem, count in self.items():
703 other_count = other[elem]
704 newcount = count if count < other_count else other_count
705 if newcount > 0:
706 result[elem] = newcount
707 return result
708
709
710if __name__ == '__main__':
711 # verify that instances can be pickled
712 from cPickle import loads, dumps
713 Point = namedtuple('Point', 'x, y', True)
714 p = Point(x=10, y=20)
715 assert p == loads(dumps(p))
716
717 # test and demonstrate ability to override methods
718 class Point(namedtuple('Point', 'x y')):
719 __slots__ = ()
720 @property
721 def hypot(self):
722 return (self.x ** 2 + self.y ** 2) ** 0.5
723 def __str__(self):
724 return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
725
726 for p in Point(3, 4), Point(14, 5/7.):
727 print(p)
728
729 class Point(namedtuple('Point', 'x y')):
730 'Point class with optimized _make() and _replace() without error-checking'
731 __slots__ = ()
732 _make = classmethod(tuple.__new__)
733 def _replace(self, _map=map, **kwds):
734 return self._make(_map(kwds.get, ('x', 'y'), self))
735
736 print(Point(11, 22)._replace(x=100))
737
738 Point3D = namedtuple('Point3D', Point._fields + ('z',))
739 print(Point3D.__doc__)
740
741 import doctest
742 TestResults = namedtuple('TestResults', 'failed attempted')
743 print(TestResults(*doctest.testmod()))