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