1 #!/usr/bin/env python 2 3 """ 4 Data classes. 5 6 Copyright (C) 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 21 -------- 22 23 The central classes in this module are the following: 24 25 * Class 26 * Function 27 * Module 28 29 All of the above support the Naming interface either explicitly or through 30 general conformance, meaning that all can be asked to provide their 'full_name' 31 using the method of that name. 32 33 Additionally, all of the above also support a dictionary interface in order to 34 access names within their defined scopes. Specific methods also exist in order 35 to distinguish between certain kinds of attributes: 36 37 * Class: (class|all_class|instance|all)_attributes 38 * Function: parameters, locals, all_locals 39 * Module: module_attributes 40 41 These specific methods are useful in certain situations. 42 43 The above classes also provide an 'astnode' attribute, indicating the AST node 44 where each such object is defined. 45 """ 46 47 from micropython.common import * 48 49 def shortrepr(obj): 50 if obj is None: 51 return repr(None) 52 else: 53 return obj.__shortrepr__() 54 55 # Mix-ins and abstract classes. 56 57 class NamespaceDict: 58 59 "A mix-in providing dictionary methods." 60 61 def __init__(self, module=None): 62 self.namespace = {} 63 self.globals = set() 64 self.module = module 65 self.finalised = 0 66 67 def __delitem__(self, name): 68 del self.namespace[name] 69 70 def has_key(self, name): 71 return self.namespace.has_key(name) 72 73 def keys(self): 74 return self.namespace.keys() 75 76 def values(self): 77 return self.namespace.values() 78 79 def items(self): 80 return self.namespace.items() 81 82 def __getitem__(self, name): 83 return self.namespace[name] 84 85 def get(self, name, default=None): 86 return self.namespace.get(name, default) 87 88 def __setitem__(self, name, value): 89 self.set(name, value) 90 91 def set(self, name, value, single_assignment=1): 92 93 """ 94 A more powerful set operation, making 'name' refer to 'value' whilst 95 indicating whether a 'single_assignment' (true by default) occurs in 96 this operation (or whether the operation covers potentially many 97 assignments in the lifetime of a program). 98 """ 99 100 if name in self.globals: 101 self.module.set(name, value, 0) 102 else: 103 attr = self._set(name, value) 104 attr.update(attr.value, single_assignment) 105 106 def set_module(self, name, value): 107 108 """ 109 A specialised set operation, making 'name' refer to 'value' in the 110 context of making a module reference available in association with 111 'name' as part of the import of that module or a submodule of that 112 module. 113 """ 114 115 attr = self._set(name, value) 116 if attr.assignments is None: 117 attr.assignments = 1 118 attr.assignment_values.add(attr.value) 119 120 def _set(self, name, value): 121 122 """ 123 The underlying set operation associating 'name' with 'value'. 124 See: docs/assignment.txt 125 """ 126 127 if not self.namespace.has_key(name): 128 129 # Either accept the attribute as specified. 130 131 if isinstance(value, Attr): 132 if value.context is not None: 133 self.namespace[name] = Attr(None, self, value.context, name, value.value) 134 return self.namespace[name] 135 136 # Or accept the value of the attribute. 137 138 else: 139 value = value.value 140 141 # Then/or attempt to fix the context. 142 143 context = self._context(value) 144 self.namespace[name] = Attr(None, self, context, name, value) 145 146 return self.namespace[name] 147 148 def _context(self, value): 149 150 """ 151 Return the context to be used when storing the given 'value'. 152 See: docs/assignment.txt 153 """ 154 155 # Set the context of instances to themselves. 156 157 if isinstance(value, Instance): 158 return value 159 else: 160 return None 161 162 def make_global(self, name): 163 if not self.namespace.has_key(name): 164 self.globals.add(name) 165 return 1 166 else: 167 return 0 168 169 def get_assignments(self, name): 170 if self.assignments.has_key(name): 171 return max(self.assignments[name], len(self.assignment_values[name])) 172 else: 173 return None 174 175 def attributes_as_list(self): 176 177 "Return the attributes in a list." 178 179 self.finalise_attributes() 180 l = [None] * len(self.keys()) 181 for attr in self.values(): 182 l[attr.position] = attr 183 return l 184 185 def finalise_attributes(self): 186 187 "Make sure all attributes are fully defined." 188 189 if self.finalised: 190 return 191 192 # The default action is to assign attribute positions sequentially. 193 194 for i, attr in enumerate(self.values()): 195 attr.position = i 196 197 self.finalised = 1 198 199 # Program data structures. There are two separate kinds of structures: those 200 # with context, which are the values manipulated by programs, and those without 201 # context, which are typically constant things which are stored alongside the 202 # program but which are wrapped in context-dependent structures in the running 203 # program. 204 205 class Attr: 206 207 "An attribute entry having a context." 208 209 def __init__(self, position, parent, context, name, value=None, assignments=None): 210 211 """ 212 Initialise the attribute with the given 'position' within the collection 213 of attributes of its 'parent', indicating the 'context' or origin of the 214 attribute (where it was first defined), along with its 'name'. 215 216 An optional 'value' indicates the typical contents of the attribute, and 217 the optional number of 'assignments' may be used to determine whether 218 the attribute is effectively constant. 219 """ 220 221 self.position = position 222 self.parent = parent 223 self.context = context 224 self.name = name 225 self.value = value 226 227 # Number of assignments per name. 228 229 self.assignments = assignments 230 self.assignment_values = set() 231 232 def set_referenced(self): 233 234 "Indicate that the contents are referenced via a namespace." 235 236 for value in self.assignment_values: 237 value.set_referenced() 238 239 def update(self, value, single_assignment): 240 241 """ 242 Update the attribute, adding the 'value' provided to the known values 243 associated with the attribute, changing the number of assignments 244 according to the 'single_assignment' status of the operation, where 245 a true value indicates that only one assignment is associated with the 246 update, and a false value indicates that potentially many assignments 247 may be involved. 248 """ 249 250 if self.assignments is None: 251 if single_assignment: 252 self.assignments = 1 253 else: 254 self.assignments = AtLeast(1) 255 else: 256 if single_assignment: 257 self.assignments += 1 258 else: 259 self.assignments += AtLeast(1) 260 261 if value is not None: 262 self.assignment_values.add(value) 263 264 def via_instance(self): 265 266 """ 267 Return either this attribute or a replacement where it is being accessed 268 via an instance. 269 """ 270 271 if self.context is not None: 272 273 # Check compatibility of the context with the parent. 274 # Where the attribute originates within the same hierarchy, use an 275 # instance as the context. 276 277 if self.defined_within_hierarchy(): 278 context = Instance() 279 280 # Otherwise, preserve the existing context. 281 282 else: 283 context = self.context 284 285 return Attr(self.position, self.parent, context, self.name, self.value, self.assignments) 286 287 # Unknown contexts remain in use. 288 289 else: 290 return self 291 292 def is_class_attribute(self): 293 return isinstance(self.parent, Class) 294 295 def defined_within_hierarchy(self): 296 297 """ 298 Return whether the parent and context of the attribute belong to the 299 same class hierarchy. 300 """ 301 302 return isinstance(self.parent, Class) and isinstance(self.context, Class) and ( 303 self.context is self.parent or 304 self.context.has_subclass(self.parent) or 305 self.parent.has_subclass(self.context)) 306 307 def __repr__(self): 308 return "Attr(%r, %s, %s, %r, %s, %r)" % ( 309 self.position, shortrepr(self.parent), shortrepr(self.context), 310 self.name, shortrepr(self.value), self.assignments 311 ) 312 313 # Instances are special in that they need to be wrapped together with context in 314 # a running program, but they are not generally constant. 315 316 class Instance: 317 318 "A placeholder indicating the involvement of an instance." 319 320 def __init__(self): 321 self.parent = None 322 self.referenced = 0 323 324 # Image generation details. 325 326 self.location = None 327 328 def set_referenced(self): 329 self.referenced = 1 330 331 def __repr__(self): 332 return "Instance()" 333 334 __shortrepr__ = __repr__ 335 336 class Constant: 337 338 "A superclass for all constant or context-free structures." 339 340 pass 341 342 # Data objects appearing in programs before run-time. 343 344 class Const(Constant, Instance): 345 346 "A constant object with no context." 347 348 def __init__(self, value): 349 Instance.__init__(self) 350 self.value = value 351 352 def __repr__(self): 353 if self.location is not None: 354 return "Const(%r, location=%r)" % (self.value, self.location) 355 else: 356 return "Const(%r)" % self.value 357 358 __shortrepr__ = __repr__ 359 360 # Support constants as dictionary keys in order to build constant tables. 361 362 def __eq__(self, other): 363 return self.value == other.value and self.value.__class__ is other.value.__class__ 364 365 def __hash__(self): 366 return hash(self.value) 367 368 def value_type_name(self): 369 return "__builtins__." + self.value.__class__.__name__ 370 371 class Class(NamespaceDict, Naming, Constant): 372 373 "An inspected class." 374 375 def __init__(self, name, parent, module=None, node=None): 376 377 """ 378 Initialise the class with the given 'name', 'parent' object, optional 379 'module' and optional AST 'node'. 380 """ 381 382 NamespaceDict.__init__(self, module) 383 self.name = name 384 self.parent = parent 385 self.astnode = node 386 self.referenced = 0 387 388 # Superclasses, descendants and attributes. 389 390 self.bases = [] 391 self.descendants = set() 392 self.instattr = set() # instance attributes 393 self.relocated = set() # attributes which do not have the same 394 # position as those of the same name in 395 # some superclasses 396 397 # Caches. 398 399 self.all_instattr = None # cache for instance_attributes 400 self.all_instattr_names = None # from all_instattr 401 self.all_classattr = None # cache for all_class_attributes 402 self.all_classattr_names = None # from all_classattr 403 self.allattr = None # cache for all_attributes 404 self.allattr_names = None # from allattr 405 406 # Add this class to its attributes. 407 408 self.set("__class__", self) 409 410 # Image generation details. 411 412 self.location = None 413 self.code_location = None 414 self.instantiator = None 415 self.instance_template_location = None # for creating instances at run-time 416 417 # Program-related details. 418 419 self.blocks = None 420 self.temp_usage = 0 421 self.local_usage = 0 422 self.all_local_usage = 0 423 424 def set_referenced(self): 425 self.referenced = 1 426 427 def __repr__(self): 428 if self.location is not None: 429 return "Class(%r, %s, location=%r)" % (self.name, shortrepr(self.parent), self.location) 430 else: 431 return "Class(%r, %s)" % (self.name, shortrepr(self.parent)) 432 433 def __shortrepr__(self): 434 return "Class(%r, %s)" % (self.name, shortrepr(self.parent)) 435 436 def _context(self, value): 437 438 """ 439 Return the context to be used when storing the given 'value'. 440 See: docs/assignment.txt 441 """ 442 443 if value is not None: 444 445 # Change the ownership of functions. 446 447 if isinstance(value, Function): 448 context = value.parent 449 450 if isinstance(context, Module): 451 return self 452 else: 453 return context 454 455 return NamespaceDict._context(self, value) 456 457 def finalise_attributes(self): 458 459 "Make sure that all attributes are fully defined." 460 461 if self.finalised: 462 return 463 464 self.finalise_class_attributes() 465 self.finalise_instance_attributes() 466 self.finalised = 1 467 468 # Convenience methods for accessing functions and methods. 469 470 def get_instantiator(self): 471 472 "Return a function which can be used to instantiate the class." 473 474 if self.instantiator is None: 475 self.instantiator = self.get_init_method().function_from_method() 476 return self.instantiator 477 478 def get_init_method(self): 479 return self.all_class_attributes()["__init__"].value 480 481 # Class-specific methods. 482 483 def add_base(self, base): 484 self.bases.append(base) 485 base.add_descendant(self) 486 487 def add_instance_attribute(self, name): 488 self.instattr.add(name) 489 490 def add_descendant(self, cls): 491 self.descendants.add(cls) 492 for base in self.bases: 493 base.add_descendant(cls) 494 495 def has_subclass(self, other): 496 return other in self.descendants 497 498 def all_descendants(self): 499 d = {} 500 for cls in self.descendants: 501 d[cls.full_name()] = cls 502 return d 503 504 "Return the attribute names provided by this class only." 505 506 class_attribute_names = NamespaceDict.keys 507 508 def class_attributes(self): 509 510 "Return class attributes provided by this class only." 511 512 return dict(self) 513 514 def all_class_attribute_names(self): 515 516 "Return the attribute names provided by classes in this hierarchy." 517 518 if self.all_classattr_names is None: 519 self.all_class_attributes() 520 return self.all_classattr_names 521 522 def all_class_attributes(self): 523 524 "Return all class attributes, indicating the class which provides them." 525 526 self.finalise_class_attributes() 527 return self.all_classattr 528 529 def finalise_class_attributes(self): 530 531 "Make sure that the class attributes are fully defined." 532 533 if self.all_classattr is None: 534 self.all_classattr = {} 535 clsattr = {} 536 537 # Record provisional position information for attributes of this 538 # class. 539 540 for name in self.class_attributes().keys(): 541 clsattr[name] = set() # position not yet defined 542 543 reversed_bases = self.bases[:] 544 reversed_bases.reverse() 545 546 # For the bases in reverse order, acquire class attribute details. 547 548 for cls in reversed_bases: 549 for name, attr in cls.all_class_attributes().items(): 550 self.all_classattr[name] = attr 551 552 # Record previous attribute information. 553 554 if clsattr.has_key(name): 555 clsattr[name].add(attr.position) 556 557 # Record class attributes provided by this class and its bases, 558 # along with their positions. 559 560 self.all_classattr.update(self.class_attributes()) 561 562 if clsattr: 563 for i, name in enumerate(self._get_position_list(clsattr)): 564 self.all_classattr[name].position = i 565 566 return self.all_classattr 567 568 def instance_attribute_names(self): 569 570 "Return the instance attribute names provided by the class." 571 572 if self.all_instattr_names is None: 573 self.instance_attributes() 574 return self.all_instattr_names 575 576 def instance_attributes(self): 577 578 "Return instance-only attributes for instances of this class." 579 580 self.finalise_instance_attributes() 581 return self.all_instattr 582 583 def finalise_instance_attributes(self): 584 585 "Make sure that the instance attributes are fully defined." 586 587 if self.all_instattr is None: 588 self.all_instattr = {} 589 instattr = {} 590 591 # Record provisional position information for attributes of this 592 # instance. 593 594 for name in self.instattr: 595 instattr[name] = set() # position not yet defined 596 597 reversed_bases = self.bases[:] 598 reversed_bases.reverse() 599 600 # For the bases in reverse order, acquire instance attribute 601 # details. 602 603 for cls in reversed_bases: 604 for name, attr in cls.instance_attributes().items(): 605 606 # Record previous attribute information. 607 608 if instattr.has_key(name): 609 instattr[name].add(attr.position) 610 611 # Cache the attributes by converting the positioned attributes into 612 # a dictionary. 613 614 if not instattr: 615 self.all_instattr = {} 616 else: 617 self.all_instattr = self._get_attributes(instattr) 618 619 self.all_instattr_names = self.all_instattr.keys() 620 621 return self.all_instattr 622 623 def _get_position_list(self, positions): 624 625 """ 626 Return a list of attribute names for the given 'positions' mapping from 627 names to positions, indicating the positions of the attributes in the 628 final instance structure. 629 """ 630 631 position_items = positions.items() 632 namearray = [None] * len(position_items) 633 634 # Get the positions in ascending order of list size, with lists 635 # of the same size ordered according to their smallest position 636 # value. 637 638 position_items.sort(self._cmp_positions) 639 640 # Get the names in position order. 641 642 held = [] 643 644 for name, pos in position_items: 645 pos = list(pos) 646 pos.sort() 647 if pos and pos[0] < len(namearray) and namearray[pos[0]] is None: 648 namearray[pos[0]] = name 649 else: 650 if pos: 651 self.relocated.add(name) 652 held.append((name, pos)) 653 654 for i, attr in enumerate(namearray): 655 if attr is None: 656 name, pos = held.pop() 657 namearray[i] = name 658 659 #print self.name, positions 660 #print "->", namearray 661 return namearray 662 663 def _get_attributes(self, positions): 664 665 """ 666 For the given 'positions' mapping from names to positions, return a 667 dictionary mapping names to Attr instances incorporating information 668 about their positions in the final instance structure. 669 """ 670 671 d = {} 672 for i, name in enumerate(self._get_position_list(positions)): 673 d[name] = Attr(i, Instance(), None, name, None) 674 return d 675 676 def _cmp_positions(self, a, b): 677 678 "Compare name plus position list operands 'a' and 'b'." 679 680 name_a, list_a = a 681 name_b, list_b = b 682 if len(list_a) < len(list_b): 683 return -1 684 elif len(list_a) > len(list_b): 685 return 1 686 elif not list_a: 687 return 0 688 else: 689 return cmp(min(list_a), min(list_b)) 690 691 def all_attribute_names(self): 692 693 """ 694 Return the names of all attributes provided by instances of this class. 695 """ 696 697 self.allattr_names = self.allattr_names or self.all_attributes().keys() 698 return self.allattr_names 699 700 def all_attributes(self): 701 702 """ 703 Return all attributes for an instance, indicating either the class which 704 provides them or that the instance itself provides them. 705 """ 706 707 if self.allattr is None: 708 self.allattr = {} 709 self.allattr.update(self.all_class_attributes()) 710 for name, attr in self.instance_attributes().items(): 711 if self.allattr.has_key(name): 712 print "Instance attribute %r in %r overrides class attribute." % (name, self) 713 self.allattr[name] = attr 714 return self.allattr 715 716 class Function(NamespaceDict, Naming, Constant): 717 718 "An inspected function." 719 720 def __init__(self, name, parent, argnames, defaults, has_star, has_dstar, module=None, node=None): 721 722 """ 723 Initialise the function with the given 'name', 'parent', list of 724 'argnames', list of 'defaults', the 'has_star' flag (indicating the 725 presence of a * parameter), the 'has_dstar' flag (indicating the 726 presence of a ** parameter), optional 'module', and optional AST 'node'. 727 """ 728 729 NamespaceDict.__init__(self, module) 730 self.name = name 731 self.parent = parent 732 self.argnames = argnames 733 self.defaults = defaults 734 self.has_star = has_star 735 self.has_dstar = has_dstar 736 self.astnode = node 737 self.referenced = 0 738 739 # Initialise the positional names. 740 741 self.positional_names = self.argnames[:] 742 if has_dstar: 743 self.dstar_name = self.positional_names[-1] 744 del self.positional_names[-1] 745 if has_star: 746 self.star_name = self.positional_names[-1] 747 del self.positional_names[-1] 748 749 # Initialise default storage. 750 # NOTE: This must be initialised separately due to the reliance on node 751 # NOTE: visiting. 752 753 self.default_attrs = [] 754 755 # Caches. 756 757 self.localnames = None # cache for locals 758 759 # Add parameters to the namespace. 760 761 self._add_parameters(argnames) 762 763 # Image generation details. 764 765 self.location = None 766 self.code_location = None 767 768 # Program-related details. 769 770 self.blocks = None 771 self.temp_usage = 0 772 self.local_usage = 0 773 self.all_local_usage = 0 774 775 def set_referenced(self): 776 self.referenced = 1 777 778 def _add_parameters(self, argnames): 779 for name in argnames: 780 if isinstance(name, tuple): 781 self._add_parameters(name) 782 else: 783 self.set(name, None) 784 785 def __repr__(self): 786 if self.location is not None: 787 return "Function(%r, %s, %r, location=%r, code_location=%r)" % ( 788 self.name, shortrepr(self.parent), self.argnames, self.location, self.code_location 789 ) 790 else: 791 return "Function(%r, %s, %r)" % ( 792 self.name, shortrepr(self.parent), self.argnames 793 ) 794 795 def __shortrepr__(self): 796 return "Function(%r, %s)" % ( 797 self.name, shortrepr(self.parent) 798 ) 799 800 def store_default(self, value): 801 attr = Attr(None, self, None, None, value) 802 attr.update(value, 1) 803 self.default_attrs.append(attr) 804 805 def make_global(self, name): 806 if name not in self.argnames and not self.has_key(name): 807 self.globals.add(name) 808 return 1 809 else: 810 return 0 811 812 def parameters(self): 813 814 """ 815 Return a dictionary mapping parameter names to their position in the 816 parameter list. 817 """ 818 819 parameters = {} 820 for i, name in enumerate(self.argnames): 821 parameters[name] = i 822 return parameters 823 824 def all_locals(self): 825 826 "Return a dictionary mapping names to local and parameter details." 827 828 return dict(self) 829 830 def locals(self): 831 832 "Return a dictionary mapping names to local details." 833 834 if self.localnames is None: 835 self.localnames = {} 836 self.localnames.update(self.all_locals()) 837 for name in self.argnames: 838 del self.localnames[name] 839 return self.localnames 840 841 def is_method(self): 842 843 "Return whether this function is a method." 844 845 return isinstance(self.parent, Class) 846 847 def is_relocated(self, name): 848 849 """ 850 Determine whether the given attribute 'name' is relocated for instances 851 having this function as a method. 852 """ 853 854 for cls in self.parent.descendants: 855 if name in cls.relocated: 856 return 1 857 return 0 858 859 def finalise_attributes(self): 860 861 """ 862 Make sure all attributes (locals) are fully defined. Note that locals 863 are not attributes in the sense of class, module or instance attributes. 864 Defaults are also finalised by this method. 865 """ 866 867 for i, default in enumerate(self.default_attrs): 868 default.position = i 869 870 i = None 871 for i, name in enumerate(self.argnames): 872 self[name].position = i 873 874 if i is not None: 875 nparams = i + 1 876 else: 877 nparams = 0 878 879 i = None 880 for i, attr in enumerate(self.locals().values()): 881 attr.position = i + nparams 882 883 if i is not None: 884 nothers = i + 1 885 else: 886 nothers = 0 887 888 self.local_usage = nothers 889 self.all_local_usage = nparams + nothers 890 self.finalised = 1 891 892 def function_from_method(self): 893 894 "Make a function from a method." 895 896 function = Function(self.name, self.parent, self.argnames[1:], self.defaults, 897 self.has_star, self.has_dstar, self.module, self.astnode) 898 function.default_attrs = self.default_attrs 899 return function 900 901 class UnresolvedName(NamespaceDict, Constant): 902 903 "A module, class or function which was mentioned but could not be imported." 904 905 def __init__(self, name, parent_name, module=None): 906 NamespaceDict.__init__(self, module) 907 self.name = name 908 self.parent_name = parent_name 909 self.parent = None 910 self.referenced = 0 911 912 self.descendants = set() 913 914 def set_referenced(self): 915 self.referenced = 1 916 917 def add_descendant(self, cls): 918 self.descendants.add(cls) 919 920 def all_class_attributes(self): 921 return {} 922 923 def instance_attributes(self): 924 return {} 925 926 def __repr__(self): 927 return "UnresolvedName(%r, %r)" % (self.name, self.parent_name) 928 929 __shortrepr__ = __repr__ 930 931 def full_name(self): 932 if self.name is not None: 933 return self.parent_name + "." + self.name 934 else: 935 return self.parent_name 936 937 class Module(NamespaceDict, Constant): 938 939 "An inspected module's core details." 940 941 def __init__(self, name): 942 NamespaceDict.__init__(self, self) 943 self.name = name 944 self.parent = None 945 self.referenced = 0 946 947 # Original location details. 948 949 self.astnode = None 950 951 # Complete lists of classes and functions. 952 953 self.all_objects = set() 954 955 # Keyword records. 956 957 self.keyword_names = set() 958 959 # Image generation details. 960 961 self.location = None 962 self.code_location = None 963 964 # Program-related details. 965 966 self.blocks = None 967 self.temp_usage = 0 968 self.local_usage = 0 969 self.all_local_usage = 0 970 971 def set_referenced(self): 972 self.referenced = 1 973 974 def full_name(self): 975 return self.name 976 977 def __repr__(self): 978 if self.location is not None: 979 return "Module(%r, location=%r)" % (self.name, self.location) 980 else: 981 return "Module(%r)" % self.name 982 983 def __shortrepr__(self): 984 return "Module(%r)" % self.name 985 986 # Attribute methods. 987 988 "Return the module attribute names provided by the module." 989 990 module_attribute_names = NamespaceDict.keys 991 992 def module_attributes(self): 993 994 "Return a dictionary mapping names to module attributes." 995 996 return dict(self) 997 998 # vim: tabstop=4 expandtab shiftwidth=4