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