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