Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-144207: Syntax highlighting(theming support) for dis module#144208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
AbduazizZiyodov wants to merge14 commits intopython:main
base:main
Choose a base branch
Loading
fromAbduaziz-Forks:dis-theme-support
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
14 commits
Select commitHold shift + click to select a range
1e0b1ab
Add support for syntax highlighting(via themes).
AbduazizZiyodovJan 23, 2026
468e561
Set NO_COLOR to 1 for test_compiler_assemble & test_dis test cases, b…
AbduazizZiyodovJan 23, 2026
9b637d1
Merge branch 'main' into dis-theme-support
AbduazizZiyodovJan 24, 2026
4067c9b
Replace NO_COLOR=1 trick with `@force_not_colorized*` helpers
AbduazizZiyodovJan 24, 2026
f3a0d2e
Move `Dis` to top, keep alphabetical order
AbduazizZiyodovJan 24, 2026
a87f3cf
Revert unrelated(type annotation) change(s)
AbduazizZiyodovJan 24, 2026
71c37a1
re-add removed line, remove(revert) return type annotation
AbduazizZiyodovJan 25, 2026
3e9a547
Wrap long lines
AbduazizZiyodovJan 25, 2026
9310e30
Update What's new section, add news entry
AbduazizZiyodovJan 25, 2026
8d389be
Make get_dis_theme protected function
AbduazizZiyodovJan 25, 2026
f9ede48
Update Doc/whatsnew/3.15.rst
AbduazizZiyodovJan 25, 2026
103124d
Wrap lines, refer to proper documentation section for controlling color
AbduazizZiyodovJan 25, 2026
9284ae4
Merge branch 'main' into dis-theme-support
AbduazizZiyodovJan 25, 2026
7ed9c9e
Remove unused link
AbduazizZiyodovJan 25, 2026
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

Some comments aren't visible on the classic Files Changed page.

9 changes: 9 additions & 0 deletionsDoc/whatsnew/3.15.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -534,6 +534,15 @@ difflib
(Contributed by Jiahao Li in:gh:`134580`.)


dis
---

*:func:`dis.dis` supports colored output by default, which can also be
:ref:`controlled<using-on-controlling-color>` through ``NO_COLOR=1``
environment variable.
(Contributed by Abduaziz Ziyodov in:gh:`144207`.)


functools
---------

Expand Down
84 changes: 84 additions & 0 deletionsLib/_colorize.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -200,6 +200,88 @@ class Difflib(ThemeSection):
reset: str = ANSIColors.RESET


@dataclass(frozen=True, kw_only=True)
class Dis(ThemeSection):
label_bg: str = ANSIColors.BACKGROUND_BLUE
label_fg: str = ANSIColors.BLACK
exception_label: str = ANSIColors.CYAN
argument_detail: str = ANSIColors.GREY

op_stack: str = ANSIColors.BOLD_YELLOW
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

How were those categories determined? are they determined already like that in dis.rst?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

are they determined already like that in dis.rst?

Almost, I first grouped according todis.rst then re-categorized them according to my understanding (how these opcodes relate, semantically) -- which might need some refinement too.

op_load_store: str = ANSIColors.BOLD_CYAN
op_call_return: str = ANSIColors.BOLD_MAGENTA
op_binary_unary: str = ANSIColors.BOLD_BLUE
op_control_flow: str = ANSIColors.BOLD_GREEN
op_build: str = ANSIColors.BOLD_WHITE
op_exceptions: str = ANSIColors.BOLD_RED
op_other: str = ANSIColors.GREY

reset: str = ANSIColors.RESET

def color_by_opname(self, opname: str) -> str:
if opname in (
"POP_TOP",
"POP_ITER",
"END_FOR",
"END_SEND",
"COPY",
"SWAP",
"PUSH_NULL",
"PUSH_EXC_INFO",
"NOP",
"CACHE",
):
return self.op_stack

if opname.startswith(("LOAD_", "STORE_", "DELETE_", "IMPORT_")):
return self.op_load_store

if opname.startswith(("CALL", "RETURN")) or opname in (
"YIELD_VALUE",
"MAKE_FUNCTION",
"SET_FUNCTION_ATTRIBUTE",
"RESUME",
):
return self.op_call_return

if opname.startswith(("BINARY_", "UNARY_")) or opname in (
"COMPARE_OP",
"IS_OP",
"CONTAINS_OP",
"GET_ITER",
"GET_YIELD_FROM_ITER",
"TO_BOOL",
"DELETE_SUBSCR",
):
return self.op_binary_unary

if opname.startswith(("JUMP_", "POP_JUMP_", "FOR_ITER")) or opname in (
"SEND",
"GET_AWAITABLE",
"GET_AITER",
"GET_ANEXT",
"END_ASYNC_FOR",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I am a bit confused with END_FOR and END_ASYNC_FOR being colored differently but I do not remember the exact effect of the latter.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'veread thatEND_FOR is equivalent(or alias I would say) toPOP_TOP:

Removes the top-of-stack item. Equivalent to POP_TOP. Used to clean up at the end of loops, hence the name.

and it is specified under "General instructions" section whileEND_ASYNC_FOR in "Coroutine opcodes" (I generalized this into "control flow" opcodes).

Almost all opcodes are dealing with stack, butEND_ASYNC_FOR is puttinglittle more effort thanEND_FOR which is just stack.pop(), that's why I thoughtEND_ASYNC_FOR is different thanEND_FOR.

That's my understanding.

We might elaborate our discussion on categories in your next comment too.

"CLEANUP_THROW",
):
return self.op_control_flow

if opname.startswith(
("BUILD_", "LIST_", "DICT_", "UNPACK_")
) or opname in ("SET_ADD", "MAP_ADD", "SET_UPDATE"):
return self.op_build

if opname.startswith(("SETUP_", "CHECK_")) or opname in (
"POP_EXCEPT",
"RERAISE",
"WITH_EXCEPT_START",
"RAISE_VARARGS",
"POP_BLOCK",
):
return self.op_exceptions

return self.op_other


@dataclass(frozen=True, kw_only=True)
class LiveProfiler(ThemeSection):
"""Theme section for the live profiling TUI (Tachyon profiler).
Expand DownExpand Up@@ -357,6 +439,7 @@ class Theme:
syntax: Syntax = field(default_factory=Syntax)
traceback: Traceback = field(default_factory=Traceback)
unittest: Unittest = field(default_factory=Unittest)
dis: Dis = field(default_factory=Dis)

def copy_with(
self,
Expand DownExpand Up@@ -397,6 +480,7 @@ def no_colors(cls) -> Self:
syntax=Syntax.no_colors(),
traceback=Traceback.no_colors(),
unittest=Unittest.no_colors(),
dis=Dis.no_colors(),
)


Expand Down
19 changes: 15 additions & 4 deletionsLib/dis.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -436,6 +436,9 @@ def __str__(self):
formatter.print_instruction(self, False)
return output.getvalue()

def _get_dis_theme():
from _colorize import get_theme
return get_theme().dis

class Formatter:

Expand DownExpand Up@@ -480,6 +483,7 @@ def print_instruction(self, instr, mark_as_current=False):

def print_instruction_line(self, instr, mark_as_current):
"""Format instruction details for inclusion in disassembly output."""
theme = _get_dis_theme()
lineno_width = self.lineno_width
offset_width = self.offset_width
label_width = self.label_width
Expand DownExpand Up@@ -527,7 +531,7 @@ def print_instruction_line(self, instr, mark_as_current):
else:
fields.append(' ')
# Column: Opcode name
fields.append(instr.opname.ljust(_OPNAME_WIDTH))
fields.append(f"{theme.color_by_opname(instr.opname)}{instr.opname.ljust(_OPNAME_WIDTH)}{theme.reset}")
# Column: Opcode argument
if instr.arg is not None:
# If opname is longer than _OPNAME_WIDTH, we allow it to overflow into
Expand All@@ -537,19 +541,25 @@ def print_instruction_line(self, instr, mark_as_current):
fields.append(repr(instr.arg).rjust(_OPARG_WIDTH - opname_excess))
# Column: Opcode argument details
if instr.argrepr:
fields.append('(' + instr.argrepr +')')
fields.append(f'{theme.argument_detail}(' + instr.argrepr +f'){theme.reset}')
print(' '.join(fields).rstrip(), file=self.file)

def print_exception_table(self, exception_entries):
file = self.file
theme = _get_dis_theme()
if exception_entries:
print("ExceptionTable:", file=file)
for entry in exception_entries:
lasti = " lasti" if entry.lasti else ""
start = entry.start_label
end = entry.end_label
target = entry.target_label
print(f" L{start} to L{end} -> L{target} [{entry.depth}]{lasti}", file=file)
print(
f" {theme.exception_label}L{start}{theme.reset} to "
f"{theme.exception_label}L{end}{theme.reset} "
f"-> {theme.exception_label}L{target}{theme.reset} [{entry.depth}]{lasti}",
file=file,
)


class ArgResolver:
Expand DownExpand Up@@ -833,13 +843,14 @@ def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False,

def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False):
disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions)
theme = _get_dis_theme()
if depth is None or depth > 0:
if depth is not None:
depth = depth - 1
for x in co.co_consts:
if hasattr(x, 'co_code'):
print(file=file)
print("Disassembly of%r:" % (x,), file=file)
print(f"{theme.label_bg}{theme.label_fg}Disassembly of{x!r}:{theme.reset}", file=file)
_disassemble_recursive(
x, file=file, depth=depth, show_caches=show_caches,
adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions
Expand Down
3 changes: 2 additions & 1 deletionLib/test/test_compiler_assemble.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@
importtypes

fromtest.support.bytecode_helperimportAssemblerTestCase

fromtest.supportimportforce_not_colorized

# Tests for the code-object creation stage of the compiler.

Expand DownExpand Up@@ -115,6 +115,7 @@ def inner():
self.assemble_test(instructions,metadata,expected)


@force_not_colorized
deftest_exception_table(self):
metadata= {
'filename' :'exc.py',
Expand Down
13 changes: 10 additions & 3 deletionsLib/test/test_dis.py
View file
Open in desktop

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Please add tests for the colouration.

AbduazizZiyodov reacted with thumbs up emoji
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,9 +13,9 @@
import textwrap
import types
import unittest
from test.support import (captured_stdout,requires_debug_ranges,
requires_specialization, cpython_only,
os_helper, import_helper, reset_code)
from test.support import (captured_stdout,force_not_colorized_test_class,
requires_debug_ranges, requires_specialization,
cpython_only,os_helper, import_helper, reset_code)
from test.support.bytecode_helper import BytecodeTestCase


Expand DownExpand Up@@ -992,6 +992,7 @@ def do_disassembly_compare(self, got, expected):
self.assertEqual(got, expected)


@force_not_colorized_test_class
class DisTests(DisTestBase):

maxDiff = None
Expand DownExpand Up@@ -1468,6 +1469,7 @@ def f():
self.assertEqual(assem_op, assem_cache)


@force_not_colorized_test_class
class DisWithFileTests(DisTests):

# Run the tests again, using the file arg instead of print
Expand DownExpand Up@@ -1990,6 +1992,7 @@ def assertInstructionsEqual(self, instrs_1, instrs_2, /):
instrs_2 = [instr_2._replace(positions=None, cache_info=None) for instr_2 in instrs_2]
self.assertEqual(instrs_1, instrs_2)

@force_not_colorized_test_class
class InstructionTests(InstructionTestCase):

def __init__(self, *args):
Expand DownExpand Up@@ -2311,6 +2314,7 @@ def test_cache_offset_and_end_offset(self):

# get_instructions has its own tests above, so can rely on it to validate
# the object oriented API
@force_not_colorized_test_class
class BytecodeTests(InstructionTestCase, DisTestBase):

def test_instantiation(self):
Expand DownExpand Up@@ -2442,6 +2446,7 @@ def func():
self.assertEqual(offsets, [0, 2])


@force_not_colorized_test_class
class TestDisTraceback(DisTestBase):
def setUp(self) -> None:
try: # We need to clean up existing tracebacks
Expand DownExpand Up@@ -2479,6 +2484,7 @@ def test_distb_explicit_arg(self):
self.do_disassembly_compare(self.get_disassembly(tb), dis_traceback)


@force_not_colorized_test_class
class TestDisTracebackWithFile(TestDisTraceback):
# Run the `distb` tests again, using the file arg instead of print
def get_disassembly(self, tb):
Expand DownExpand Up@@ -2513,6 +2519,7 @@ def _unroll_caches_as_Instructions(instrs, show_caches=False):
False, None, None, instr.positions)


@force_not_colorized_test_class
class TestDisCLI(unittest.TestCase):

def setUp(self):
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
:func:`dis.dis` supports colored output by default which can also be
:ref:`controlled <using-on-controlling-color>` through ``NO_COLOR=1``
environment variable. Contributed by Abduaziz Ziyodov.
Loading

[8]ページ先頭

©2009-2026 Movatter.jp