Skip to content

Commit 56eb6b6

Browse files
authored
gh-138122: Implement PEP 799 (#138142)
1 parent f733e42 commit 56eb6b6

23 files changed

+497
-386
lines changed

Lib/cProfile.py

Lines changed: 8 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -1,205 +1,14 @@
1-
"""Python interface for the 'lsprof' profiler.
2-
Compatible with the 'profile' module.
3-
"""
4-
5-
__all__ = ["run", "runctx", "Profile"]
6-
7-
import _lsprof
8-
import importlib.machinery
9-
import importlib.util
10-
import io
11-
import profile as _pyprofile
12-
13-
# ____________________________________________________________
14-
# Simple interface
15-
16-
def run(statement, filename=None, sort=-1):
17-
return _pyprofile._Utils(Profile).run(statement, filename, sort)
18-
19-
def runctx(statement, globals, locals, filename=None, sort=-1):
20-
return _pyprofile._Utils(Profile).runctx(statement, globals, locals,
21-
filename, sort)
22-
23-
run.__doc__ = _pyprofile.run.__doc__
24-
runctx.__doc__ = _pyprofile.runctx.__doc__
25-
26-
# ____________________________________________________________
27-
28-
class Profile(_lsprof.Profiler):
29-
"""Profile(timer=None, timeunit=None, subcalls=True, builtins=True)
30-
31-
Builds a profiler object using the specified timer function.
32-
The default timer is a fast built-in one based on real time.
33-
For custom timer functions returning integers, timeunit can
34-
be a float specifying a scale (i.e. how long each integer unit
35-
is, in seconds).
36-
"""
37-
38-
# Most of the functionality is in the base class.
39-
# This subclass only adds convenient and backward-compatible methods.
40-
41-
def print_stats(self, sort=-1):
42-
import pstats
43-
if not isinstance(sort, tuple):
44-
sort = (sort,)
45-
pstats.Stats(self).strip_dirs().sort_stats(*sort).print_stats()
46-
47-
def dump_stats(self, file):
48-
import marshal
49-
with open(file, 'wb') as f:
50-
self.create_stats()
51-
marshal.dump(self.stats, f)
52-
53-
def create_stats(self):
54-
self.disable()
55-
self.snapshot_stats()
1+
"""Compatibility wrapper for cProfile module.
562
57-
def snapshot_stats(self):
58-
entries = self.getstats()
59-
self.stats = {}
60-
callersdicts = {}
61-
# call information
62-
for entry in entries:
63-
func = label(entry.code)
64-
nc = entry.callcount # ncalls column of pstats (before '/')
65-
cc = nc - entry.reccallcount # ncalls column of pstats (after '/')
66-
tt = entry.inlinetime # tottime column of pstats
67-
ct = entry.totaltime # cumtime column of pstats
68-
callers = {}
69-
callersdicts[id(entry.code)] = callers
70-
self.stats[func] = cc, nc, tt, ct, callers
71-
# subcall information
72-
for entry in entries:
73-
if entry.calls:
74-
func = label(entry.code)
75-
for subentry in entry.calls:
76-
try:
77-
callers = callersdicts[id(subentry.code)]
78-
except KeyError:
79-
continue
80-
nc = subentry.callcount
81-
cc = nc - subentry.reccallcount
82-
tt = subentry.inlinetime
83-
ct = subentry.totaltime
84-
if func in callers:
85-
prev = callers[func]
86-
nc += prev[0]
87-
cc += prev[1]
88-
tt += prev[2]
89-
ct += prev[3]
90-
callers[func] = nc, cc, tt, ct
91-
92-
# The following two methods can be called by clients to use
93-
# a profiler to profile a statement, given as a string.
94-
95-
def run(self, cmd):
96-
import __main__
97-
dict = __main__.__dict__
98-
return self.runctx(cmd, dict, dict)
99-
100-
def runctx(self, cmd, globals, locals):
101-
self.enable()
102-
try:
103-
exec(cmd, globals, locals)
104-
finally:
105-
self.disable()
106-
return self
107-
108-
# This method is more useful to profile a single function call.
109-
def runcall(self, func, /, *args, **kw):
110-
self.enable()
111-
try:
112-
return func(*args, **kw)
113-
finally:
114-
self.disable()
115-
116-
def __enter__(self):
117-
self.enable()
118-
return self
119-
120-
def __exit__(self, *exc_info):
121-
self.disable()
122-
123-
# ____________________________________________________________
3+
This module maintains backward compatibility by importing from the new
4+
profiling.tracing module.
5+
"""
1246

125-
def label(code):
126-
if isinstance(code, str):
127-
return ('~', 0, code) # built-in functions ('~' sorts at the end)
128-
else:
129-
return (code.co_filename, code.co_firstlineno, code.co_name)
7+
from profiling.tracing import run, runctx, Profile
1308

131-
# ____________________________________________________________
9+
__all__ = ["run", "runctx", "Profile"]
13210

133-
def main():
134-
import os
11+
if __name__ == "__main__":
13512
import sys
136-
import runpy
137-
import pstats
138-
from optparse import OptionParser
139-
usage = "cProfile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..."
140-
parser = OptionParser(usage=usage)
141-
parser.allow_interspersed_args = False
142-
parser.add_option('-o', '--outfile', dest="outfile",
143-
help="Save stats to <outfile>", default=None)
144-
parser.add_option('-s', '--sort', dest="sort",
145-
help="Sort order when printing to stdout, based on pstats.Stats class",
146-
default=2,
147-
choices=sorted(pstats.Stats.sort_arg_dict_default))
148-
parser.add_option('-m', dest="module", action="store_true",
149-
help="Profile a library module", default=False)
150-
151-
if not sys.argv[1:]:
152-
parser.print_usage()
153-
sys.exit(2)
154-
155-
(options, args) = parser.parse_args()
156-
sys.argv[:] = args
157-
158-
# The script that we're profiling may chdir, so capture the absolute path
159-
# to the output file at startup.
160-
if options.outfile is not None:
161-
options.outfile = os.path.abspath(options.outfile)
162-
163-
if len(args) > 0:
164-
if options.module:
165-
code = "run_module(modname, run_name='__main__')"
166-
globs = {
167-
'run_module': runpy.run_module,
168-
'modname': args[0]
169-
}
170-
else:
171-
progname = args[0]
172-
sys.path.insert(0, os.path.dirname(progname))
173-
with io.open_code(progname) as fp:
174-
code = compile(fp.read(), progname, 'exec')
175-
spec = importlib.machinery.ModuleSpec(name='__main__', loader=None,
176-
origin=progname)
177-
module = importlib.util.module_from_spec(spec)
178-
# Set __main__ so that importing __main__ in the profiled code will
179-
# return the same namespace that the code is executing under.
180-
sys.modules['__main__'] = module
181-
# Ensure that we're using the same __dict__ instance as the module
182-
# for the global variables so that updates to globals are reflected
183-
# in the module's namespace.
184-
globs = module.__dict__
185-
globs.update({
186-
'__spec__': spec,
187-
'__file__': spec.origin,
188-
'__name__': spec.name,
189-
'__package__': None,
190-
'__cached__': None,
191-
})
192-
193-
try:
194-
runctx(code, globs, None, options.outfile, options.sort)
195-
except BrokenPipeError as exc:
196-
# Prevent "Exception ignored" during interpreter shutdown.
197-
sys.stdout = None
198-
sys.exit(exc.errno)
199-
else:
200-
parser.print_usage()
201-
return parser
202-
203-
# When invoked as main program, invoke the profiler on a script
204-
if __name__ == '__main__':
13+
from profiling.tracing.__main__ import main
20514
main()

Lib/profile/profile.py renamed to Lib/profile.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,18 @@
2828
import sys
2929
import time
3030
import marshal
31+
import warnings
3132

3233
__all__ = ["run", "runctx", "Profile"]
3334

35+
# Emit deprecation warning as per PEP 799
36+
warnings.warn(
37+
"The profile module is deprecated and will be removed in Python 3.17. "
38+
"Use profiling.tracing (or cProfile) for tracing profilers instead.",
39+
DeprecationWarning,
40+
stacklevel=2
41+
)
42+
3443
# Sample timer for use with
3544
#i_count = 0
3645
#def integer_timer():
@@ -550,3 +559,66 @@ def f(m, f1=f1):
550559
return mean
551560

552561
#****************************************************************************
562+
563+
def main():
564+
import os
565+
from optparse import OptionParser
566+
567+
usage = "profile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..."
568+
parser = OptionParser(usage=usage)
569+
parser.allow_interspersed_args = False
570+
parser.add_option('-o', '--outfile', dest="outfile",
571+
help="Save stats to <outfile>", default=None)
572+
parser.add_option('-m', dest="module", action="store_true",
573+
help="Profile a library module.", default=False)
574+
parser.add_option('-s', '--sort', dest="sort",
575+
help="Sort order when printing to stdout, based on pstats.Stats class",
576+
default=-1)
577+
578+
if not sys.argv[1:]:
579+
parser.print_usage()
580+
sys.exit(2)
581+
582+
(options, args) = parser.parse_args()
583+
sys.argv[:] = args
584+
585+
# The script that we're profiling may chdir, so capture the absolute path
586+
# to the output file at startup.
587+
if options.outfile is not None:
588+
options.outfile = os.path.abspath(options.outfile)
589+
590+
if len(args) > 0:
591+
if options.module:
592+
import runpy
593+
code = "run_module(modname, run_name='__main__')"
594+
globs = {
595+
'run_module': runpy.run_module,
596+
'modname': args[0]
597+
}
598+
else:
599+
progname = args[0]
600+
sys.path.insert(0, os.path.dirname(progname))
601+
with io.open_code(progname) as fp:
602+
code = compile(fp.read(), progname, 'exec')
603+
spec = importlib.machinery.ModuleSpec(name='__main__', loader=None,
604+
origin=progname)
605+
globs = {
606+
'__spec__': spec,
607+
'__file__': spec.origin,
608+
'__name__': spec.name,
609+
'__package__': None,
610+
'__cached__': None,
611+
}
612+
try:
613+
runctx(code, globs, None, options.outfile, options.sort)
614+
except BrokenPipeError as exc:
615+
# Prevent "Exception ignored" during interpreter shutdown.
616+
sys.stdout = None
617+
sys.exit(exc.errno)
618+
else:
619+
parser.print_usage()
620+
return parser
621+
622+
# When invoked as main program, invoke the profiler on a script
623+
if __name__ == '__main__':
624+
main()

Lib/profile/__init__.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

Lib/profile/__main__.py

Lines changed: 0 additions & 69 deletions
This file was deleted.

Lib/profiling/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""Python profiling tools.
2+
3+
This package provides two types of profilers:
4+
5+
- profiling.tracing: Deterministic tracing profiler that instruments every
6+
function call and return. Higher overhead but provides exact call counts
7+
and timing.
8+
9+
- profiling.sampling: Statistical sampling profiler that periodically samples
10+
the call stack. Low overhead and suitable for production use.
11+
"""
12+
13+
__all__ = ("tracing", "sampling")

0 commit comments

Comments
 (0)