1# Copyright 2017 The TensorFlow Authors. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# ============================================================================== 15"""Activity analysis. 16 17Requires qualified name annotations (see qual_names.py). 18""" 19 20from __future__ import absolute_import 21from __future__ import division 22from __future__ import print_function 23 24import copy 25import weakref 26 27import gast 28import six 29 30from tensorflow.python.autograph.pyct import anno 31from tensorflow.python.autograph.pyct import qual_names 32from tensorflow.python.autograph.pyct import transformer 33from tensorflow.python.autograph.pyct.static_analysis.annos import NodeAnno 34 35 36class Scope(object): 37 """Encloses local symbol definition and usage information. 38 39 This can track for instance whether a symbol is modified in the current scope. 40 Note that scopes do not necessarily align with Python's scopes. For example, 41 the body of an if statement may be considered a separate scope. 42 43 Caution - the AST references held by this object are weak. 44 45 Scope objects are mutable during construction only, and must be frozen using 46 `Scope.finalize()` before use. Furthermore, a scope is consistent only after 47 all its children have been frozen. While analysing code blocks, scopes are 48 being gradually built, from the innermost scope outward. Freezing indicates 49 that the analysis of a code block is complete. Once frozen, mutation is no 50 longer allowed. `is_final` tracks whether the scope is frozen or not. Certain 51 properties, like `referenced`, are only accurate when called on frozen scopes. 52 53 Attributes: 54 parent: Optional[Scope], the parent scope, if any. 55 isolated: bool, whether the scope is a true Python scope (e.g. the scope of 56 a function), or just a surrogate tracking an ordinary code block. Using 57 the terminology of the Python 3 reference documentation, True roughly 58 represents an actual scope, whereas False represents an ordinary code 59 block. 60 function_name: Optional[str], name of the function owning this scope. 61 isolated_names: Set[qual_names.QN], identifiers that are isolated to this 62 scope (even if the scope is not isolated). 63 annotations: Set[qual_names.QN], identifiers used as type annotations 64 in this scope. 65 read: Set[qual_names.QN], identifiers read in this scope. 66 modified: Set[qual_names.QN], identifiers modified in this scope. 67 deleted: Set[qual_names.QN], identifiers deleted in this scope. 68 bound: Set[qual_names.QN], names that are bound to this scope. See 69 https://docs.python.org/3/reference/executionmodel.html#binding-of-names 70 for a precise definition. 71 globals: Set[qual_names.QN], names that are explicitly marked as global in 72 this scope. Note that this doesn't include free read-only vars bound to 73 global symbols. 74 nonlocals: Set[qual_names.QN], names that are explicitly marked as nonlocal 75 in this scope. Note that this doesn't include free read-only vars bound to 76 global symbols. 77 free_vars: Set[qual_names.QN], the free variables in this scope. See 78 https://docs.python.org/3/reference/executionmodel.html for a precise 79 definition. 80 params: WeakValueDictionary[qual_names.QN, ast.Node], function arguments 81 visible in this scope, mapped to the function node that defines them. 82 enclosing_scope: Scope, the innermost isolated scope that is a transitive 83 parent of this scope. May be the scope itself. 84 referenced: Set[qual_names.QN], the totality of the symbols used by this 85 scope and its parents. 86 is_final: bool, whether the scope is frozen or not. 87 88 Note - simple statements may never delete and modify a symbol at the same 89 time. However, compound ones like if statements can. In that latter case, it's 90 undefined whether the symbol is actually modified or deleted upon statement 91 exit. Certain analyses like reaching definitions need to be careful about 92 this. 93 """ 94 95 # Note: this mutable-immutable pattern is used because using a builder would 96 # have taken a lot more boilerplate. 97 98 def __init__(self, parent, isolated=True, function_name=None): 99 """Create a new scope. 100 101 Args: 102 parent: A Scope or None. 103 isolated: Whether the scope is isolated, that is, whether variables 104 modified in this scope should be considered modified in the parent 105 scope. 106 function_name: Name of the function owning this scope. 107 """ 108 self.parent = parent 109 self.isolated = isolated 110 self.function_name = function_name 111 112 self.isolated_names = set() 113 114 self.read = set() 115 self.modified = set() 116 self.deleted = set() 117 118 self.bound = set() 119 self.globals = set() 120 self.nonlocals = set() 121 self.annotations = set() 122 123 self.params = weakref.WeakValueDictionary() 124 125 # Certain fields can only be accessed after the scope and all its parent 126 # scopes have been fully built. This field guards that. 127 self.is_final = False 128 129 @property 130 def enclosing_scope(self): 131 assert self.is_final 132 if self.parent is not None and not self.isolated: 133 return self.parent 134 return self 135 136 @property 137 def referenced(self): 138 if self.parent is not None: 139 return self.read | self.parent.referenced 140 return self.read 141 142 @property 143 def free_vars(self): 144 enclosing_scope = self.enclosing_scope 145 return enclosing_scope.read - enclosing_scope.bound 146 147 def copy_from(self, other): 148 """Recursively copies the contents of this scope from another scope.""" 149 assert not self.is_final 150 if self.parent is not None: 151 assert other.parent is not None 152 self.parent.copy_from(other.parent) 153 self.isolated_names = copy.copy(other.isolated_names) 154 self.modified = copy.copy(other.modified) 155 self.read = copy.copy(other.read) 156 self.deleted = copy.copy(other.deleted) 157 self.bound = copy.copy(other.bound) 158 self.annotations = copy.copy(other.annotations) 159 self.params = copy.copy(other.params) 160 161 @classmethod 162 def copy_of(cls, other): 163 if other.parent is not None: 164 assert other.parent is not None 165 parent = cls.copy_of(other.parent) 166 else: 167 parent = None 168 new_copy = cls(parent) 169 new_copy.copy_from(other) 170 return new_copy 171 172 def merge_from(self, other): 173 """Adds all activity from another scope to this scope.""" 174 assert not self.is_final 175 if self.parent is not None: 176 assert other.parent is not None 177 self.parent.merge_from(other.parent) 178 self.isolated_names.update(other.isolated_names) 179 self.read.update(other.read) 180 self.modified.update(other.modified) 181 self.bound.update(other.bound) 182 self.deleted.update(other.deleted) 183 self.annotations.update(other.annotations) 184 self.params.update(other.params) 185 186 def finalize(self): 187 """Freezes this scope.""" 188 assert not self.is_final 189 # TODO(mdan): freeze read, modified, bound. 190 if self.parent is not None: 191 assert not self.parent.is_final 192 if not self.isolated: 193 self.parent.read.update(self.read - self.isolated_names) 194 self.parent.modified.update(self.modified - self.isolated_names) 195 self.parent.bound.update(self.bound - self.isolated_names) 196 self.parent.globals.update(self.globals) 197 self.parent.nonlocals.update(self.nonlocals) 198 self.parent.annotations.update(self.annotations) 199 else: 200 # TODO(mdan): This is not accurate. 201 self.parent.read.update(self.read - self.bound) 202 self.parent.annotations.update(self.annotations - self.bound) 203 self.is_final = True 204 205 def __repr__(self): 206 return 'Scope{r=%s, w=%s}' % (tuple(self.read), tuple(self.modified)) 207 208 def mark_param(self, name, owner): 209 # Assumption: all AST nodes have the same life span. This lets us use 210 # a weak reference to mark the connection between a symbol node and the 211 # function node whose argument that symbol is. 212 self.params[name] = owner 213 214 215class _Comprehension(object): 216 217 no_root = True 218 219 def __init__(self): 220 # TODO(mdan): Consider using an enum. 221 self.is_list_comp = False 222 self.targets = set() 223 224 225class _FunctionOrClass(object): 226 227 def __init__(self): 228 self.node = None 229 230 231class ActivityAnalyzer(transformer.Base): 232 """Annotates nodes with local scope information. 233 234 See Scope. 235 236 The use of this class requires that qual_names.resolve() has been called on 237 the node. This class will ignore nodes have not been 238 annotated with their qualified names. 239 """ 240 241 def __init__(self, context, parent_scope=None): 242 super(ActivityAnalyzer, self).__init__(context) 243 self.allow_skips = False 244 self.scope = Scope(parent_scope, isolated=True) 245 246 # Note: all these flags crucially rely on the respective nodes are 247 # leaves in the AST, that is, they cannot contain other statements. 248 self._in_aug_assign = False 249 self._in_annotation = False 250 self._track_annotations_only = False 251 252 @property 253 def _in_constructor(self): 254 context = self.state[_FunctionOrClass] 255 if context.level > 2: 256 innermost = context.stack[-1].node 257 parent = context.stack[-2].node 258 return (isinstance(parent, gast.ClassDef) and 259 (isinstance(innermost, gast.FunctionDef) and 260 innermost.name == '__init__')) 261 return False 262 263 def _node_sets_self_attribute(self, node): 264 if anno.hasanno(node, anno.Basic.QN): 265 qn = anno.getanno(node, anno.Basic.QN) 266 # TODO(mdan): The 'self' argument is not guaranteed to be called 'self'. 267 if qn.has_attr and qn.parent.qn == ('self',): 268 return True 269 return False 270 271 def _track_symbol(self, node, composite_writes_alter_parent=False): 272 if self._track_annotations_only and not self._in_annotation: 273 return 274 275 # A QN may be missing when we have an attribute (or subscript) on a function 276 # call. Example: a().b 277 if not anno.hasanno(node, anno.Basic.QN): 278 return 279 qn = anno.getanno(node, anno.Basic.QN) 280 281 # When inside a comprehension, ignore reads to any of the comprehensions's 282 # targets. This includes attributes or slices of those arguments. 283 for l in self.state[_Comprehension]: 284 if qn in l.targets: 285 return 286 if qn.owner_set & set(l.targets): 287 return 288 289 if isinstance(node.ctx, gast.Store): 290 # In comprehensions, modified symbols are the comprehension targets. 291 if self.state[_Comprehension].level > 0: 292 self.state[_Comprehension].targets.add(qn) 293 # List comprehension targets leak in Python 2. 294 # For details, see: 295 # https://stackoverflow.com/questions/4198906/list-comprehension-rebinds-names-even-after-scope-of-comprehension-is-this-righ 296 if not (six.PY2 and self.state[_Comprehension].is_list_comp): 297 return 298 299 self.scope.modified.add(qn) 300 self.scope.bound.add(qn) 301 if qn.is_composite and composite_writes_alter_parent: 302 self.scope.modified.add(qn.parent) 303 if self._in_aug_assign: 304 self.scope.read.add(qn) 305 306 elif isinstance(node.ctx, gast.Load): 307 self.scope.read.add(qn) 308 if self._in_annotation: 309 self.scope.annotations.add(qn) 310 311 elif isinstance(node.ctx, gast.Param): 312 self.scope.bound.add(qn) 313 self.scope.mark_param(qn, self.state[_FunctionOrClass].node) 314 315 elif isinstance(node.ctx, gast.Del): 316 # The read matches the Python semantics - attempting to delete an 317 # undefined symbol is illegal. 318 self.scope.read.add(qn) 319 # Targets of del are considered bound: 320 # https://docs.python.org/3/reference/executionmodel.html#binding-of-names 321 self.scope.bound.add(qn) 322 self.scope.deleted.add(qn) 323 324 else: 325 raise ValueError('Unknown context {} for node "{}".'.format( 326 type(node.ctx), qn)) 327 328 def _enter_scope(self, isolated, f_name=None): 329 self.scope = Scope(self.scope, isolated=isolated, function_name=f_name) 330 331 def _exit_scope(self): 332 exited_scope = self.scope 333 exited_scope.finalize() 334 self.scope = exited_scope.parent 335 return exited_scope 336 337 def _exit_and_record_scope(self, node, tag=anno.Static.SCOPE): 338 node_scope = self._exit_scope() 339 anno.setanno(node, tag, node_scope) 340 return node_scope 341 342 def _process_statement(self, node): 343 self._enter_scope(False) 344 node = self.generic_visit(node) 345 self._exit_and_record_scope(node) 346 return node 347 348 def _process_annotation(self, node): 349 self._in_annotation = True 350 node = self.visit(node) 351 self._in_annotation = False 352 return node 353 354 def visit_Import(self, node): 355 return self._process_statement(node) 356 357 def visit_ImportFrom(self, node): 358 return self._process_statement(node) 359 360 def visit_Global(self, node): 361 self._enter_scope(False) 362 for name in node.names: 363 qn = qual_names.QN(name) 364 self.scope.read.add(qn) 365 self.scope.globals.add(qn) 366 self._exit_and_record_scope(node) 367 return node 368 369 def visit_Nonlocal(self, node): 370 self._enter_scope(False) 371 for name in node.names: 372 qn = qual_names.QN(name) 373 self.scope.read.add(qn) 374 self.scope.bound.add(qn) 375 self.scope.nonlocals.add(qn) 376 self._exit_and_record_scope(node) 377 return node 378 379 def visit_Expr(self, node): 380 return self._process_statement(node) 381 382 def visit_Raise(self, node): 383 return self._process_statement(node) 384 385 def visit_Return(self, node): 386 return self._process_statement(node) 387 388 def visit_Assign(self, node): 389 return self._process_statement(node) 390 391 def visit_AnnAssign(self, node): 392 self._enter_scope(False) 393 node.target = self.visit(node.target) 394 node.value = self.visit(node.value) 395 if node.annotation: 396 node.annotation = self._process_annotation(node.annotation) 397 self._exit_and_record_scope(node) 398 return node 399 400 def visit_AugAssign(self, node): 401 # Special rules for AugAssign. Here, the AST only shows the target as 402 # written, when it is in fact also read. 403 self._enter_scope(False) 404 405 self._in_aug_assign = True 406 node.target = self.visit(node.target) 407 self._in_aug_assign = False 408 409 node.op = self.visit(node.op) 410 node.value = self.visit(node.value) 411 self._exit_and_record_scope(node) 412 return node 413 414 def visit_Delete(self, node): 415 return self._process_statement(node) 416 417 def visit_Name(self, node): 418 if node.annotation: 419 node.annotation = self._process_annotation(node.annotation) 420 self._track_symbol(node) 421 return node 422 423 def visit_alias(self, node): 424 node = self.generic_visit(node) 425 426 if node.asname is None: 427 # Only the root name is a real symbol operation. 428 qn = qual_names.QN(node.name.split('.')[0]) 429 else: 430 qn = qual_names.QN(node.asname) 431 432 self.scope.modified.add(qn) 433 self.scope.bound.add(qn) 434 return node 435 436 def visit_Attribute(self, node): 437 node = self.generic_visit(node) 438 if self._in_constructor and self._node_sets_self_attribute(node): 439 self._track_symbol(node, composite_writes_alter_parent=True) 440 else: 441 self._track_symbol(node) 442 return node 443 444 def visit_Subscript(self, node): 445 node = self.generic_visit(node) 446 # Subscript writes (e.g. a[b] = "value") are considered to modify 447 # both the element itself (a[b]) and its parent (a). 448 self._track_symbol(node) 449 return node 450 451 def visit_Print(self, node): 452 self._enter_scope(False) 453 node.values = self.visit_block(node.values) 454 node_scope = self._exit_and_record_scope(node) 455 anno.setanno(node, NodeAnno.ARGS_SCOPE, node_scope) 456 return node 457 458 def visit_Assert(self, node): 459 return self._process_statement(node) 460 461 def visit_Call(self, node): 462 self._enter_scope(False) 463 node.args = self.visit_block(node.args) 464 node.keywords = self.visit_block(node.keywords) 465 # TODO(mdan): Account starargs, kwargs 466 self._exit_and_record_scope(node, tag=NodeAnno.ARGS_SCOPE) 467 468 node.func = self.visit(node.func) 469 return node 470 471 def _process_block_node(self, node, block, scope_name): 472 self._enter_scope(False) 473 block = self.visit_block(block) 474 self._exit_and_record_scope(node, tag=scope_name) 475 return node 476 477 def _process_parallel_blocks(self, parent, children): 478 # Because the scopes are not isolated, processing any child block 479 # modifies the parent state causing the other child blocks to be 480 # processed incorrectly. So we need to checkpoint the parent scope so that 481 # each child sees the same context. 482 before_parent = Scope.copy_of(self.scope) 483 after_children = [] 484 for child, scope_name in children: 485 self.scope.copy_from(before_parent) 486 parent = self._process_block_node(parent, child, scope_name) 487 after_child = Scope.copy_of(self.scope) 488 after_children.append(after_child) 489 for after_child in after_children: 490 self.scope.merge_from(after_child) 491 return parent 492 493 def _process_comprehension(self, 494 node, 495 is_list_comp=False, 496 is_dict_comp=False): 497 with self.state[_Comprehension] as comprehension_: 498 comprehension_.is_list_comp = is_list_comp 499 # Note: it's important to visit the generators first to properly account 500 # for the variables local to these generators. Example: `x` is local to 501 # the expression `z for x in y for z in x`. 502 node.generators = self.visit_block(node.generators) 503 if is_dict_comp: 504 node.key = self.visit(node.key) 505 node.value = self.visit(node.value) 506 else: 507 node.elt = self.visit(node.elt) 508 return node 509 510 def visit_comprehension(self, node): 511 # It is important to visit children in this order so that the reads to 512 # the target name are appropriately ignored. 513 node.iter = self.visit(node.iter) 514 node.target = self.visit(node.target) 515 return self.generic_visit(node) 516 517 def visit_DictComp(self, node): 518 return self._process_comprehension(node, is_dict_comp=True) 519 520 def visit_ListComp(self, node): 521 return self._process_comprehension(node, is_list_comp=True) 522 523 def visit_SetComp(self, node): 524 return self._process_comprehension(node) 525 526 def visit_GeneratorExp(self, node): 527 return self._process_comprehension(node) 528 529 def visit_ClassDef(self, node): 530 with self.state[_FunctionOrClass] as fn: 531 fn.node = node 532 # The ClassDef node itself has a Scope object that tracks the creation 533 # of its name, along with the usage of any decorator accompanying it. 534 self._enter_scope(False) 535 node.decorator_list = self.visit_block(node.decorator_list) 536 self.scope.modified.add(qual_names.QN(node.name)) 537 self.scope.bound.add(qual_names.QN(node.name)) 538 node.bases = self.visit_block(node.bases) 539 node.keywords = self.visit_block(node.keywords) 540 self._exit_and_record_scope(node) 541 542 # A separate Scope tracks the actual class definition. 543 self._enter_scope(True) 544 node = self.generic_visit(node) 545 self._exit_scope() 546 return node 547 548 def _visit_node_list(self, nodes): 549 return [(None if n is None else self.visit(n)) for n in nodes] 550 551 def _visit_arg_annotations(self, node): 552 node.args.kw_defaults = self._visit_node_list(node.args.kw_defaults) 553 node.args.defaults = self._visit_node_list(node.args.defaults) 554 self._track_annotations_only = True 555 node = self._visit_arg_declarations(node) 556 self._track_annotations_only = False 557 return node 558 559 def _visit_arg_declarations(self, node): 560 node.args.posonlyargs = self._visit_node_list(node.args.posonlyargs) 561 node.args.args = self._visit_node_list(node.args.args) 562 if node.args.vararg is not None: 563 node.args.vararg = self.visit(node.args.vararg) 564 node.args.kwonlyargs = self._visit_node_list(node.args.kwonlyargs) 565 if node.args.kwarg is not None: 566 node.args.kwarg = self.visit(node.args.kwarg) 567 return node 568 569 def visit_FunctionDef(self, node): 570 with self.state[_FunctionOrClass] as fn: 571 fn.node = node 572 # The FunctionDef node itself has a Scope object that tracks the creation 573 # of its name, along with the usage of any decorator accompanying it. 574 self._enter_scope(False) 575 node.decorator_list = self.visit_block(node.decorator_list) 576 if node.returns: 577 node.returns = self._process_annotation(node.returns) 578 # Argument annotartions (includeing defaults) affect the defining context. 579 node = self._visit_arg_annotations(node) 580 581 function_name = qual_names.QN(node.name) 582 self.scope.modified.add(function_name) 583 self.scope.bound.add(function_name) 584 self._exit_and_record_scope(node) 585 586 # A separate Scope tracks the actual function definition. 587 self._enter_scope(True, node.name) 588 589 # Keep a separate scope for the arguments node, which is used in the CFG. 590 self._enter_scope(False, node.name) 591 592 # Arg declarations only affect the function itself, and have no effect 593 # in the defining context whatsoever. 594 node = self._visit_arg_declarations(node) 595 596 self._exit_and_record_scope(node.args) 597 598 # Track the body separately. This is for compatibility reasons, it may not 599 # be strictly needed. 600 self._enter_scope(False, node.name) 601 node.body = self.visit_block(node.body) 602 self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE) 603 604 self._exit_and_record_scope(node, NodeAnno.ARGS_AND_BODY_SCOPE) 605 return node 606 607 def visit_Lambda(self, node): 608 # Lambda nodes are treated in roughly the same way as FunctionDef nodes. 609 with self.state[_FunctionOrClass] as fn: 610 fn.node = node 611 # The Lambda node itself has a Scope object that tracks the creation 612 # of its name, along with the usage of any decorator accompanying it. 613 self._enter_scope(False) 614 node = self._visit_arg_annotations(node) 615 self._exit_and_record_scope(node) 616 617 # A separate Scope tracks the actual function definition. 618 self._enter_scope(True) 619 620 # Keep a separate scope for the arguments node, which is used in the CFG. 621 self._enter_scope(False) 622 node = self._visit_arg_declarations(node) 623 self._exit_and_record_scope(node.args) 624 625 # Track the body separately. This is for compatibility reasons, it may not 626 # be strictly needed. 627 # TODO(mdan): Do remove it, it's confusing. 628 self._enter_scope(False) 629 node.body = self.visit(node.body) 630 631 # The lambda body can contain nodes of types normally not found as 632 # statements, and may not have the SCOPE annotation needed by the CFG. 633 # So we attach one if necessary. 634 if not anno.hasanno(node.body, anno.Static.SCOPE): 635 anno.setanno(node.body, anno.Static.SCOPE, self.scope) 636 637 self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE) 638 639 lambda_scope = self.scope 640 self._exit_and_record_scope(node, NodeAnno.ARGS_AND_BODY_SCOPE) 641 642 # Exception: lambdas are assumed to be used in the place where 643 # they are defined. Therefore, their activity is passed on to the 644 # calling statement. 645 self.scope.read.update(lambda_scope.read - lambda_scope.bound) 646 647 return node 648 649 def visit_With(self, node): 650 self._enter_scope(False) 651 node = self.generic_visit(node) 652 self._exit_and_record_scope(node, NodeAnno.BODY_SCOPE) 653 return node 654 655 def visit_withitem(self, node): 656 return self._process_statement(node) 657 658 def visit_If(self, node): 659 self._enter_scope(False) 660 node.test = self.visit(node.test) 661 node_scope = self._exit_and_record_scope(node.test) 662 anno.setanno(node, NodeAnno.COND_SCOPE, node_scope) 663 664 node = self._process_parallel_blocks(node, 665 ((node.body, NodeAnno.BODY_SCOPE), 666 (node.orelse, NodeAnno.ORELSE_SCOPE))) 667 return node 668 669 def visit_For(self, node): 670 self._enter_scope(False) 671 node.target = self.visit(node.target) 672 node.iter = self.visit(node.iter) 673 self._exit_and_record_scope(node.iter) 674 675 self._enter_scope(False) 676 self.visit(node.target) 677 if anno.hasanno(node, anno.Basic.EXTRA_LOOP_TEST): 678 self._process_statement(anno.getanno(node, anno.Basic.EXTRA_LOOP_TEST)) 679 self._exit_and_record_scope(node, tag=NodeAnno.ITERATE_SCOPE) 680 681 node = self._process_parallel_blocks(node, 682 ((node.body, NodeAnno.BODY_SCOPE), 683 (node.orelse, NodeAnno.ORELSE_SCOPE))) 684 return node 685 686 def visit_While(self, node): 687 self._enter_scope(False) 688 node.test = self.visit(node.test) 689 node_scope = self._exit_and_record_scope(node.test) 690 anno.setanno(node, NodeAnno.COND_SCOPE, node_scope) 691 692 node = self._process_parallel_blocks(node, 693 ((node.body, NodeAnno.BODY_SCOPE), 694 (node.orelse, NodeAnno.ORELSE_SCOPE))) 695 return node 696 697 def visit_ExceptHandler(self, node): 698 self._enter_scope(False) 699 # try/except oddity: as expected, it leaks any names you defined inside the 700 # except block, but not the name of the exception variable. 701 if node.name is not None: 702 self.scope.isolated_names.add(anno.getanno(node.name, anno.Basic.QN)) 703 node = self.generic_visit(node) 704 self._exit_scope() 705 return node 706 707 708def resolve(node, context, parent_scope=None): 709 return ActivityAnalyzer(context, parent_scope).visit(node) 710