Commits  committed e054779

Closes #93591: Correctly emit W0623 on multiple assignment of unpackable exceptions

eg for code like

except AnyException as (here, there):

Instead of warning about redefining tuple, recurse into the tuple and check all names.

  • Participants
  • Parent commits 21a44ef
  • Branches default

Comments (0)

Files changed (5)

+    * #93591: Correctly emit warnings about clobbered variable names when an
+      except handler contains a tuple of names instead of a single name.
+      (patch by
     * #7394: W0212 (access to protected member) not emited on assigments
     (patch by

File checkers/

 COMP_NODE_TYPES = astng.ListComp, astng.SetComp, astng.DictComp, astng.GenExpr
 def is_inside_except(node):
-    """Returns true if node is directly inside an exception handler"""
-    return isinstance(node.parent, astng.ExceptHandler)
+    """Returns true if node is inside the name of an except handler."""
+    current = node
+    while current and not isinstance(current.parent, astng.ExceptHandler):
+        current = current.parent
+    return current and current is
+def get_all_elements(node):
+    """Recursively returns all atoms in nested lists and tuples."""
+    if isinstance(node, (astng.Tuple, astng.List)):
+        for child in node.elts:
+            for e in get_all_elements(child):
+                yield e
+    else:
+        yield node
 def clobber_in_except(node):
     if isinstance(node, astng.AssAttr):
         return (True, (node.attrname, 'object %r' % (,)))
-    elif node is not None:
+    elif isinstance(node, astng.AssName):
         name =
         if is_builtin(name):
             return (True, (name, 'builtins'))

File checkers/

 from pylint.checkers import BaseChecker
 from pylint.checkers.utils import (PYMETHODS, is_ancestor_name, is_builtin,
      is_defined_before, is_error, is_func_default, is_func_decorator,
-     assign_parent, check_messages, is_inside_except, clobber_in_except)
+     assign_parent, check_messages, is_inside_except, clobber_in_except,
+     get_all_elements)
 def in_for_else_branch(parent, stmt):
                 self.add_message('W0631', args=name, node=node)
     def visit_excepthandler(self, node):
-        clobbering, args = clobber_in_except(
-        if clobbering:
-            self.add_message('W0623', args=args, node=node)
+        for name in get_all_elements(
+            clobbering, args = clobber_in_except(name)
+            if clobbering:
+                self.add_message('W0623', args=args, node=name)
     def visit_assname(self, node):
         if isinstance(node.ass_type(), astng.AugAssign):
     def visit_delname(self, node):

File test/input/

     print exc5
 except MyOtherError, exc5: # this is fine
     print exc5
+def new_style():
+    """Some exceptions can be unpacked."""
+    try:
+        pass
+    except IOError as (errno, message): # this is fine
+        print errno, message
+    except IOError as (new_style, tuple): # W0623 twice
+        print new_style, tuple

File test/messages/func_w0623.txt

 W: 45: Redefining name 'RuntimeError' from object 'exceptions' in exception handler
 W: 47: Redefining name 'OSError' from builtins in exception handler
 W: 49: Redefining name 'MyOtherError' from outer scope (line 36) in exception handler
+W: 73:new_style: Redefining name 'new_style' from outer scope (line 67) in exception handler
+W: 73:new_style: Redefining name 'tuple' from builtins in exception handler