Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Changes
* FIX: Fixed namespace bug when running ``kernprof -m`` on certain modules (e.g. ``calendar`` on Python 3.12+).
* FIX: Fixed ``@contextlib.contextmanager`` bug where the cleanup code (e.g. restoration of ``sys`` attributes) is not run if exceptions occurred inside the context
* ENH: Added CLI arguments ``-c`` to ``kernprof`` for (auto-)profiling module/package/inline-script execution instead of that of script files; passing ``'-'`` as the script-file name now also reads from and profiles ``stdin``
* ENH: In Python >=3.11, profiled objects are reported using their qualified name.
* ENH: Highlight final summary using rich if enabled

4.2.0
Expand Down
17 changes: 14 additions & 3 deletions line_profiler/_line_profiler.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ from libc.stdint cimport int64_t
from libcpp.unordered_map cimport unordered_map
import threading
import opcode
import types

NOP_VALUE: int = opcode.opmap['NOP']

Expand All @@ -32,6 +33,9 @@ NOP_VALUE: int = opcode.opmap['NOP']
# if sys.version_info[0:2] >= (3, 11):
NOP_BYTES: bytes = NOP_VALUE.to_bytes(2, byteorder=byteorder)

# This should be true for Python >=3.11a1
HAS_CO_QUALNAME: bool = hasattr(types.CodeType, 'co_qualname')

# long long int is at least 64 bytes assuming c99
ctypedef unsigned long long int uint64
ctypedef long long int int64
Expand Down Expand Up @@ -155,14 +159,21 @@ else:

def label(code):
"""
Return a (filename, first_lineno, func_name) tuple for a given code object.
Return a (filename, first_lineno, _name) tuple for a given code object.

This is the similar labelling as used by the cProfile module in Python 2.5.

This is the same labelling as used by the cProfile module in Python 2.5.
Note:
In Python >=3.11 we use we return qualname for _name.
In older versions of Python we just return name.
"""
if isinstance(code, str):
return ('~', 0, code) # built-in functions ('~' sorts at the end)
else:
return (code.co_filename, code.co_firstlineno, code.co_name)
if HAS_CO_QUALNAME:
return (code.co_filename, code.co_firstlineno, code.co_qualname)
else:
return (code.co_filename, code.co_firstlineno, code.co_name)


cpdef _code_replace(func, co_code):
Expand Down
15 changes: 7 additions & 8 deletions tests/test_line_profiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def foo(cls) -> str:
output = strip(sio.getvalue())
print(output)
# Check that we have profiled `Object.foo()`
assert output.endswith('- foo')
assert output.endswith('foo')
line, = (line for line in output.splitlines() if line.endswith('* 2'))
# Check that it has been run twice
assert int(line.split()[1]) == 2
Expand Down Expand Up @@ -291,7 +291,7 @@ def foo(x: int) -> int:
output = strip(sio.getvalue())
print(output)
# Check that we have profiled `Object.foo()`
assert output.endswith('- foo')
assert output.endswith('foo')
line, = (line for line in output.splitlines() if line.endswith('* 2'))
# Check that it has been run twice
assert int(line.split()[1]) == 2
Expand Down Expand Up @@ -332,7 +332,7 @@ def foo(self, x: int) -> int:
output = strip(sio.getvalue())
print(output)
# Check that we have profiled `Object.foo()`
assert output.endswith('- foo')
assert output.endswith('foo')
line, = (line for line in output.splitlines() if line.endswith('* x'))
# Check that the wrapped methods has been run twice in total
assert int(line.split()[1]) == 2
Expand Down Expand Up @@ -368,7 +368,7 @@ def foo(self, x: int) -> int:
output = strip(sio.getvalue())
print(output)
# Check that we have profiled `Object.foo()` (via `.bar()`)
assert output.endswith('- foo')
assert output.endswith('foo')
line, = (line for line in output.splitlines() if line.endswith('* x'))
# Check that the wrapped method has been run once
assert int(line.split()[1]) == 1
Expand Down Expand Up @@ -408,7 +408,7 @@ def foo(x: int, y: int) -> int:
output = strip(sio.getvalue())
print(output)
# Check that we have profiled `foo()`
assert output.endswith('- foo')
assert output.endswith('foo')
line, = (line for line in output.splitlines() if line.endswith('x + y'))
# Check that the wrapped partials has been run twice in total
assert int(line.split()[1]) == 2
Expand Down Expand Up @@ -457,7 +457,7 @@ def foo(self, foo) -> None:
output = strip(sio.getvalue())
print(output)
# Check that we have profiled `Object.foo`
assert output.endswith('- foo')
assert output.endswith('foo')
getter_line, = (line for line in output.splitlines()
if line.endswith('* 2'))
setter_line, = (line for line in output.splitlines()
Expand Down Expand Up @@ -498,9 +498,8 @@ def foo(self) -> int:
with io.StringIO() as sio:
profile.print_stats(stream=sio, summarize=True)
output = strip(sio.getvalue())
print(output)
# Check that we have profiled `Object.foo`
assert output.endswith('- foo')
assert output.endswith('foo')
line, = (line for line in output.splitlines() if line.endswith('* 2'))
# Check that the getter has been run once
assert int(line.split()[1]) == 1
Expand Down
Loading