Skip to content

Commit 9716b02

Browse files
gh-88580: Run profile scripts in __main__ namespace
The profile CLI executed script globals separately from the module stored in sys.modules["__main__"]. Deferred dataclass annotations therefore resolved against the profiler module and failed to recognize InitVar fields. Create a real __main__ module and execute the profiled script in its namespace, matching direct script execution and cProfile.
1 parent ee68f5f commit 9716b02

3 files changed

Lines changed: 37 additions & 3 deletions

File tree

Lib/profile.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525

2626
import importlib.machinery
27+
import importlib.util
2728
import io
2829
import sys
2930
import time
@@ -602,12 +603,20 @@ def main():
602603
code = compile(fp.read(), progname, 'exec')
603604
spec = importlib.machinery.ModuleSpec(name='__main__', loader=None,
604605
origin=progname)
605-
globs = {
606-
'__spec__': spec,
606+
module = importlib.util.module_from_spec(spec)
607+
# Set __main__ so that importing __main__ in the profiled code will
608+
# return the same namespace that the code is executing under.
609+
sys.modules['__main__'] = module
610+
# Ensure that we're using the same __dict__ instance as the module
611+
# for the global variables so that updates to globals are reflected
612+
# in the module's namespace.
613+
globs = module.__dict__
614+
globs.update({
615+
'__spec__': None,
607616
'__file__': spec.origin,
608617
'__name__': spec.name,
609618
'__package__': None,
610-
}
619+
})
611620
try:
612621
runctx(code, globs, None, options.outfile, options.sort)
613622
except BrokenPipeError as exc:

Lib/test/test_profile.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,28 @@ def test_run_profile_as_module(self):
120120
assert_python_ok('-m', self.profilermodule.__name__,
121121
'-m', 'timeit', '-n', '1')
122122

123+
def test_script_with_deferred_dataclass_annotations(self):
124+
# gh-88580: the profiled script must be available as __main__ so
125+
# dataclasses can resolve string annotations in its global namespace.
126+
with temp_dir() as tmpdir:
127+
script = os.path.join(tmpdir, 'profile_dataclass.py')
128+
with open(script, 'w', encoding='utf-8') as f:
129+
f.write("""\
130+
from __future__ import annotations
131+
from dataclasses import dataclass, InitVar
132+
133+
@dataclass
134+
class C:
135+
value: InitVar[int]
136+
137+
def __post_init__(self, value):
138+
assert value == 42
139+
140+
C(42)
141+
""")
142+
143+
assert_python_ok('-m', self.profilermodule.__name__, script)
144+
123145
def test_output_file_when_changing_directory(self):
124146
with temp_dir() as tmpdir, change_cwd(tmpdir):
125147
os.mkdir('dest')
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix the :mod:`profile` command-line interface to execute scripts in the actual
2+
``__main__`` module namespace. This fixes profiling dataclasses that use
3+
deferred :class:`~dataclasses.InitVar` annotations.

0 commit comments

Comments
 (0)