https://github.com/python/cpython/commit/494360afd00dc8f6b549f160525c3e86ec14905d
commit: 494360afd00dc8f6b549f160525c3e86ec14905d
branch: main
author: Beomsoo Kim <beoms...@gmail.com>
committer: ncoghlan <ncogh...@gmail.com>
date: 2024-11-12T10:11:40+10:00
summary:

gh-58749: Remove incorrect language spec claims about the global statement 
(GH-126523)

* Removes erroneous explanation of the `global` statement restrictions; a name 
declared as global can be subsequently bound using any kind of name binding 
operation.
* Updates `test_global.py` to also test various name-binding scenarios for 
global
variables to ensure correct behavior

files:
M Doc/reference/simple_stmts.rst
M Lib/test/test_global.py
M Misc/ACKS

diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst
index 24df4a6ba7b678..a005395bfc402e 100644
--- a/Doc/reference/simple_stmts.rst
+++ b/Doc/reference/simple_stmts.rst
@@ -966,25 +966,14 @@ The :keyword:`!global` statement
 .. productionlist:: python-grammar
    global_stmt: "global" `identifier` ("," `identifier`)*
 
-The :keyword:`global` statement is a declaration which holds for the entire
-current code block.  It means that the listed identifiers are to be interpreted
-as globals.  It would be impossible to assign to a global variable without
+The :keyword:`global` causes the listed identifiers to be interpreted
+as globals. It would be impossible to assign to a global variable without
 :keyword:`!global`, although free variables may refer to globals without being
 declared global.
 
-Names listed in a :keyword:`global` statement must not be used in the same code
-block textually preceding that :keyword:`!global` statement.
-
-Names listed in a :keyword:`global` statement must not be defined as formal
-parameters, or as targets in :keyword:`with` statements or :keyword:`except` 
clauses, or in a :keyword:`for` target list, :keyword:`class`
-definition, function definition, :keyword:`import` statement, or
-:term:`variable annotations <variable annotation>`.
-
-.. impl-detail::
-
-   The current implementation does not enforce some of these restrictions, but
-   programs should not abuse this freedom, as future implementations may 
enforce
-   them or silently change the meaning of the program.
+The global statement applies to the entire scope of a function or
+class body. A :exc:`SyntaxError` is raised if a variable is used or
+assigned to prior to its global declaration in the scope.
 
 .. index::
    pair: built-in function; exec
diff --git a/Lib/test/test_global.py b/Lib/test/test_global.py
index f5b38c25ea0728..11d0bd54e8b69b 100644
--- a/Lib/test/test_global.py
+++ b/Lib/test/test_global.py
@@ -1,7 +1,19 @@
-"""Verify that warnings are issued for global statements following use."""
+"""This module includes tests for syntax errors that occur when a name
+declared as `global` is used in ways that violate the language
+specification, such as after assignment, usage, or annotation. The tests
+verify that syntax errors are correctly raised for improper `global`
+statements following variable use or assignment within functions.
+Additionally, it tests various name-binding scenarios for global
+variables to ensure correct behavior.
 
+See `test_scope.py` for additional related behavioral tests covering
+variable scoping and usage in different contexts.
+"""
+
+import contextlib
 from test.support import check_syntax_error
 from test.support.warnings_helper import check_warnings
+from types import SimpleNamespace
 import unittest
 import warnings
 
@@ -12,40 +24,185 @@ def setUp(self):
         self.enterContext(check_warnings())
         warnings.filterwarnings("error", module="<test string>")
 
-    def test1(self):
-        prog_text_1 = """\
-def wrong1():
-    a = 1
-    b = 2
-    global a
-    global b
+    ######################################################
+    ### Syntax error cases as covered in Python/symtable.c
+    ######################################################
+
+    def test_name_param(self):
+        prog_text = """\
+def fn(name_param):
+    global name_param
 """
-        check_syntax_error(self, prog_text_1, lineno=4, offset=5)
+        check_syntax_error(self, prog_text, lineno=2, offset=5)
 
-    def test2(self):
-        prog_text_2 = """\
-def wrong2():
-    print(x)
-    global x
+    def test_name_after_assign(self):
+        prog_text = """\
+def fn():
+    name_assign = 1
+    global name_assign
 """
-        check_syntax_error(self, prog_text_2, lineno=3, offset=5)
+        check_syntax_error(self, prog_text, lineno=3, offset=5)
 
-    def test3(self):
-        prog_text_3 = """\
-def wrong3():
-    print(x)
-    x = 2
-    global x
+    def test_name_after_use(self):
+        prog_text = """\
+def fn():
+    print(name_use)
+    global name_use
 """
-        check_syntax_error(self, prog_text_3, lineno=4, offset=5)
+        check_syntax_error(self, prog_text, lineno=3, offset=5)
 
-    def test4(self):
-        prog_text_4 = """\
-global x
-x = 2
+    def test_name_annot(self):
+        prog_text_3 = """\
+def fn():
+    name_annot: int
+    global name_annot
 """
-        # this should work
-        compile(prog_text_4, "<test string>", "exec")
+        check_syntax_error(self, prog_text_3, lineno=3, offset=5)
+
+    #############################################################
+    ### Tests for global variables across all name binding cases,
+    ### as described in executionmodel.rst
+    #############################################################
+
+    def test_assignment_statement(self):
+        global name_assignment_statement
+        value = object()
+        name_assignment_statement = value
+        self.assertIs(globals()["name_assignment_statement"], value)
+        del name_assignment_statement
+
+    def test_unpacking_assignment(self):
+        global name_unpacking_assignment
+        value = object()
+        _, name_unpacking_assignment = [None, value]
+        self.assertIs(globals()["name_unpacking_assignment"], value)
+        del name_unpacking_assignment
+
+    def test_assignment_expression(self):
+        global name_assignment_expression
+        value = object()
+        if name_assignment_expression := value:
+            pass
+        self.assertIs(globals()["name_assignment_expression"], value)
+        del name_assignment_expression
+
+    def test_iteration_variable(self):
+        global name_iteration_variable
+        value = object()
+        for name_iteration_variable in [value]:
+            pass
+        self.assertIs(globals()["name_iteration_variable"], value)
+        del name_iteration_variable
+
+    def test_func_def(self):
+        global name_func_def
+
+        def name_func_def():
+            pass
+
+        value = name_func_def
+        self.assertIs(globals()["name_func_def"], value)
+        del name_func_def
+
+    def test_class_def(self):
+        global name_class_def
+
+        class name_class_def:
+            pass
+
+        value = name_class_def
+        self.assertIs(globals()["name_class_def"], value)
+        del name_class_def
+
+    def test_type_alias(self):
+        global name_type_alias
+        type name_type_alias = tuple[int, int]
+        value = name_type_alias
+        self.assertIs(globals()["name_type_alias"], value)
+        del name_type_alias
+
+    def test_caught_exception(self):
+        global name_caught_exc
+
+        try:
+            1 / 0
+        except ZeroDivisionError as name_caught_exc:
+            value = name_caught_exc
+            # `name_caught_exc` is cleared automatically after the except block
+            self.assertIs(globals()["name_caught_exc"], value)
+
+    def test_caught_exception_group(self):
+        global name_caught_exc_group
+        try:
+            try:
+                1 / 0
+            except ZeroDivisionError as exc:
+                raise ExceptionGroup("eg", [exc])
+        except* ZeroDivisionError as name_caught_exc_group:
+            value = name_caught_exc_group
+            # `name_caught_exc` is cleared automatically after the except block
+            self.assertIs(globals()["name_caught_exc_group"], value)
+
+    def test_enter_result(self):
+        global name_enter_result
+        value = object()
+        with contextlib.nullcontext(value) as name_enter_result:
+            pass
+        self.assertIs(globals()["name_enter_result"], value)
+        del name_enter_result
+
+    def test_import_result(self):
+        global name_import_result
+        value = contextlib
+        import contextlib as name_import_result
+
+        self.assertIs(globals()["name_import_result"], value)
+        del name_import_result
+
+    def test_match(self):
+        global name_match
+        value = object()
+        match value:
+            case name_match:
+                pass
+        self.assertIs(globals()["name_match"], value)
+        del name_match
+
+    def test_match_as(self):
+        global name_match_as
+        value = object()
+        match value:
+            case _ as name_match_as:
+                pass
+        self.assertIs(globals()["name_match_as"], value)
+        del name_match_as
+
+    def test_match_seq(self):
+        global name_match_seq
+        value = object()
+        match (None, value):
+            case (_, name_match_seq):
+                pass
+        self.assertIs(globals()["name_match_seq"], value)
+        del name_match_seq
+
+    def test_match_map(self):
+        global name_match_map
+        value = object()
+        match {"key": value}:
+            case {"key": name_match_map}:
+                pass
+        self.assertIs(globals()["name_match_map"], value)
+        del name_match_map
+
+    def test_match_attr(self):
+        global name_match_attr
+        value = object()
+        match SimpleNamespace(key=value):
+            case SimpleNamespace(key=name_match_attr):
+                pass
+        self.assertIs(globals()["name_match_attr"], value)
+        del name_match_attr
 
 
 def setUpModule():
diff --git a/Misc/ACKS b/Misc/ACKS
index 1a25088052f4e1..6b5d7bf80cee6d 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -953,6 +953,7 @@ Sanyam Khurana
 Tyler Kieft
 Mads Kiilerich
 Jason Killen
+Beomsoo Bombs Kim
 Derek D. Kim
 Gihwan Kim
 Jan Kim

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to