Skip to content

Commit 494360a

Browse files
authored
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
1 parent a6d48e8 commit 494360a

File tree

3 files changed

+191
-44
lines changed

3 files changed

+191
-44
lines changed

Doc/reference/simple_stmts.rst

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -966,25 +966,14 @@ The :keyword:`!global` statement
966966
.. productionlist:: python-grammar
967967
global_stmt: "global" `identifier` ("," `identifier`)*
968968

969-
The :keyword:`global` statement is a declaration which holds for the entire
970-
current code block. It means that the listed identifiers are to be interpreted
971-
as globals. It would be impossible to assign to a global variable without
969+
The :keyword:`global` causes the listed identifiers to be interpreted
970+
as globals. It would be impossible to assign to a global variable without
972971
:keyword:`!global`, although free variables may refer to globals without being
973972
declared global.
974973

975-
Names listed in a :keyword:`global` statement must not be used in the same code
976-
block textually preceding that :keyword:`!global` statement.
977-
978-
Names listed in a :keyword:`global` statement must not be defined as formal
979-
parameters, or as targets in :keyword:`with` statements or :keyword:`except` clauses, or in a :keyword:`for` target list, :keyword:`class`
980-
definition, function definition, :keyword:`import` statement, or
981-
:term:`variable annotations <variable annotation>`.
982-
983-
.. impl-detail::
984-
985-
The current implementation does not enforce some of these restrictions, but
986-
programs should not abuse this freedom, as future implementations may enforce
987-
them or silently change the meaning of the program.
974+
The global statement applies to the entire scope of a function or
975+
class body. A :exc:`SyntaxError` is raised if a variable is used or
976+
assigned to prior to its global declaration in the scope.
988977

989978
.. index::
990979
pair: built-in function; exec

Lib/test/test_global.py

Lines changed: 185 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
1-
"""Verify that warnings are issued for global statements following use."""
1+
"""This module includes tests for syntax errors that occur when a name
2+
declared as `global` is used in ways that violate the language
3+
specification, such as after assignment, usage, or annotation. The tests
4+
verify that syntax errors are correctly raised for improper `global`
5+
statements following variable use or assignment within functions.
6+
Additionally, it tests various name-binding scenarios for global
7+
variables to ensure correct behavior.
28
9+
See `test_scope.py` for additional related behavioral tests covering
10+
variable scoping and usage in different contexts.
11+
"""
12+
13+
import contextlib
314
from test.support import check_syntax_error
415
from test.support.warnings_helper import check_warnings
16+
from types import SimpleNamespace
517
import unittest
618
import warnings
719

@@ -12,40 +24,185 @@ def setUp(self):
1224
self.enterContext(check_warnings())
1325
warnings.filterwarnings("error", module="<test string>")
1426

15-
def test1(self):
16-
prog_text_1 = """\
17-
def wrong1():
18-
a = 1
19-
b = 2
20-
global a
21-
global b
27+
######################################################
28+
### Syntax error cases as covered in Python/symtable.c
29+
######################################################
30+
31+
def test_name_param(self):
32+
prog_text = """\
33+
def fn(name_param):
34+
global name_param
2235
"""
23-
check_syntax_error(self, prog_text_1, lineno=4, offset=5)
36+
check_syntax_error(self, prog_text, lineno=2, offset=5)
2437

25-
def test2(self):
26-
prog_text_2 = """\
27-
def wrong2():
28-
print(x)
29-
global x
38+
def test_name_after_assign(self):
39+
prog_text = """\
40+
def fn():
41+
name_assign = 1
42+
global name_assign
3043
"""
31-
check_syntax_error(self, prog_text_2, lineno=3, offset=5)
44+
check_syntax_error(self, prog_text, lineno=3, offset=5)
3245

33-
def test3(self):
34-
prog_text_3 = """\
35-
def wrong3():
36-
print(x)
37-
x = 2
38-
global x
46+
def test_name_after_use(self):
47+
prog_text = """\
48+
def fn():
49+
print(name_use)
50+
global name_use
3951
"""
40-
check_syntax_error(self, prog_text_3, lineno=4, offset=5)
52+
check_syntax_error(self, prog_text, lineno=3, offset=5)
4153

42-
def test4(self):
43-
prog_text_4 = """\
44-
global x
45-
x = 2
54+
def test_name_annot(self):
55+
prog_text_3 = """\
56+
def fn():
57+
name_annot: int
58+
global name_annot
4659
"""
47-
# this should work
48-
compile(prog_text_4, "<test string>", "exec")
60+
check_syntax_error(self, prog_text_3, lineno=3, offset=5)
61+
62+
#############################################################
63+
### Tests for global variables across all name binding cases,
64+
### as described in executionmodel.rst
65+
#############################################################
66+
67+
def test_assignment_statement(self):
68+
global name_assignment_statement
69+
value = object()
70+
name_assignment_statement = value
71+
self.assertIs(globals()["name_assignment_statement"], value)
72+
del name_assignment_statement
73+
74+
def test_unpacking_assignment(self):
75+
global name_unpacking_assignment
76+
value = object()
77+
_, name_unpacking_assignment = [None, value]
78+
self.assertIs(globals()["name_unpacking_assignment"], value)
79+
del name_unpacking_assignment
80+
81+
def test_assignment_expression(self):
82+
global name_assignment_expression
83+
value = object()
84+
if name_assignment_expression := value:
85+
pass
86+
self.assertIs(globals()["name_assignment_expression"], value)
87+
del name_assignment_expression
88+
89+
def test_iteration_variable(self):
90+
global name_iteration_variable
91+
value = object()
92+
for name_iteration_variable in [value]:
93+
pass
94+
self.assertIs(globals()["name_iteration_variable"], value)
95+
del name_iteration_variable
96+
97+
def test_func_def(self):
98+
global name_func_def
99+
100+
def name_func_def():
101+
pass
102+
103+
value = name_func_def
104+
self.assertIs(globals()["name_func_def"], value)
105+
del name_func_def
106+
107+
def test_class_def(self):
108+
global name_class_def
109+
110+
class name_class_def:
111+
pass
112+
113+
value = name_class_def
114+
self.assertIs(globals()["name_class_def"], value)
115+
del name_class_def
116+
117+
def test_type_alias(self):
118+
global name_type_alias
119+
type name_type_alias = tuple[int, int]
120+
value = name_type_alias
121+
self.assertIs(globals()["name_type_alias"], value)
122+
del name_type_alias
123+
124+
def test_caught_exception(self):
125+
global name_caught_exc
126+
127+
try:
128+
1 / 0
129+
except ZeroDivisionError as name_caught_exc:
130+
value = name_caught_exc
131+
# `name_caught_exc` is cleared automatically after the except block
132+
self.assertIs(globals()["name_caught_exc"], value)
133+
134+
def test_caught_exception_group(self):
135+
global name_caught_exc_group
136+
try:
137+
try:
138+
1 / 0
139+
except ZeroDivisionError as exc:
140+
raise ExceptionGroup("eg", [exc])
141+
except* ZeroDivisionError as name_caught_exc_group:
142+
value = name_caught_exc_group
143+
# `name_caught_exc` is cleared automatically after the except block
144+
self.assertIs(globals()["name_caught_exc_group"], value)
145+
146+
def test_enter_result(self):
147+
global name_enter_result
148+
value = object()
149+
with contextlib.nullcontext(value) as name_enter_result:
150+
pass
151+
self.assertIs(globals()["name_enter_result"], value)
152+
del name_enter_result
153+
154+
def test_import_result(self):
155+
global name_import_result
156+
value = contextlib
157+
import contextlib as name_import_result
158+
159+
self.assertIs(globals()["name_import_result"], value)
160+
del name_import_result
161+
162+
def test_match(self):
163+
global name_match
164+
value = object()
165+
match value:
166+
case name_match:
167+
pass
168+
self.assertIs(globals()["name_match"], value)
169+
del name_match
170+
171+
def test_match_as(self):
172+
global name_match_as
173+
value = object()
174+
match value:
175+
case _ as name_match_as:
176+
pass
177+
self.assertIs(globals()["name_match_as"], value)
178+
del name_match_as
179+
180+
def test_match_seq(self):
181+
global name_match_seq
182+
value = object()
183+
match (None, value):
184+
case (_, name_match_seq):
185+
pass
186+
self.assertIs(globals()["name_match_seq"], value)
187+
del name_match_seq
188+
189+
def test_match_map(self):
190+
global name_match_map
191+
value = object()
192+
match {"key": value}:
193+
case {"key": name_match_map}:
194+
pass
195+
self.assertIs(globals()["name_match_map"], value)
196+
del name_match_map
197+
198+
def test_match_attr(self):
199+
global name_match_attr
200+
value = object()
201+
match SimpleNamespace(key=value):
202+
case SimpleNamespace(key=name_match_attr):
203+
pass
204+
self.assertIs(globals()["name_match_attr"], value)
205+
del name_match_attr
49206

50207

51208
def setUpModule():

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,7 @@ Sanyam Khurana
953953
Tyler Kieft
954954
Mads Kiilerich
955955
Jason Killen
956+
Beomsoo Bombs Kim
956957
Derek D. Kim
957958
Gihwan Kim
958959
Jan Kim

0 commit comments

Comments
 (0)