From 2b8d6241a9ba49eab26d1176ad719879dd2fbdb1 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Mon, 23 Jun 2025 12:47:27 +0200 Subject: [PATCH 1/5] Enable ruff rule PLC0415 This commit enables ruff rule PLC0415. Suggested by EarlMilktea: https://github.com/TeamGraphix/graphix/pull/301#issuecomment-2993259490 --- graphix/device_interface.py | 3 ++- graphix/fundamentals.py | 10 ++++----- graphix/pretty_print.py | 7 ++---- graphix/pyzx.py | 6 ----- graphix/random_objects.py | 4 +--- graphix/utils.py | 3 +-- pyproject.toml | 1 - tests/test_parameter.py | 3 ++- tests/test_pyzx.py | 45 +++++++++++++------------------------ 9 files changed, 28 insertions(+), 54 deletions(-) diff --git a/graphix/device_interface.py b/graphix/device_interface.py index 2c984774f..2b751b5c3 100644 --- a/graphix/device_interface.py +++ b/graphix/device_interface.py @@ -34,7 +34,8 @@ def __init__(self, pattern: Pattern, backend: str = "ibmq", **kwargs) -> None: if self.backend_name == "ibmq": try: - from graphix_ibmq.runner import IBMQBackend + # This will be removed by #261 + from graphix_ibmq.runner import IBMQBackend # noqa: PLC0415 except Exception as e: raise ImportError( "Failed to import graphix_ibmq. Please install graphix_ibmq by `pip install graphix-ibmq`." diff --git a/graphix/fundamentals.py b/graphix/fundamentals.py index 8967c7174..d11fb02e8 100644 --- a/graphix/fundamentals.py +++ b/graphix/fundamentals.py @@ -10,9 +10,9 @@ import typing_extensions +from graphix import pretty_print from graphix.ops import Ops from graphix.parameter import cos_sin -from graphix.pretty_print import EnumPrettyPrintMixin if TYPE_CHECKING: import numpy as np @@ -29,7 +29,7 @@ SupportsComplexCtor = Union[SupportsComplex, SupportsFloat, SupportsIndex, complex] -class Sign(EnumPrettyPrintMixin, Enum): +class Sign(pretty_print.EnumPrettyPrintMixin, Enum): """Sign, plus or minus.""" PLUS = 1 @@ -112,7 +112,7 @@ def __complex__(self) -> complex: return complex(self.value) -class ComplexUnit(EnumPrettyPrintMixin, Enum): +class ComplexUnit(pretty_print.EnumPrettyPrintMixin, Enum): """ Complex unit: 1, -1, j, -j. @@ -214,7 +214,7 @@ def matrix(self) -> npt.NDArray[np.complex128]: typing_extensions.assert_never(self) -class Axis(EnumPrettyPrintMixin, Enum): +class Axis(pretty_print.EnumPrettyPrintMixin, Enum): """Axis: *X*, *Y* or *Z*.""" X = enum.auto() @@ -233,7 +233,7 @@ def matrix(self) -> npt.NDArray[np.complex128]: typing_extensions.assert_never(self) -class Plane(EnumPrettyPrintMixin, Enum): +class Plane(pretty_print.EnumPrettyPrintMixin, Enum): # TODO: Refactor using match """Plane: *XY*, *YZ* or *XZ*.""" diff --git a/graphix/pretty_print.py b/graphix/pretty_print.py index b28fe6038..d3fdc7993 100644 --- a/graphix/pretty_print.py +++ b/graphix/pretty_print.py @@ -11,7 +11,7 @@ from fractions import Fraction from typing import TYPE_CHECKING, SupportsFloat -from graphix import command +from graphix import command, fundamentals if TYPE_CHECKING: from collections.abc import Container @@ -106,9 +106,6 @@ def command_to_str(cmd: command.Command, output: OutputFormat) -> str: output: OutputFormat The expected format. """ - # Circumvent circular import - from graphix.fundamentals import Plane - out = [cmd.kind.name] if cmd.kind == command.CommandKind.E: @@ -128,7 +125,7 @@ def command_to_str(cmd: command.Command, output: OutputFormat) -> str: # with some other arguments and/or domains. arguments = [] if cmd.kind == command.CommandKind.M: - if cmd.plane != Plane.XY: + if cmd.plane != fundamentals.Plane.XY: arguments.append(cmd.plane.name) # We use `SupportsFloat` since `isinstance(cmd.angle, float)` # is `False` if `cmd.angle` is an integer. diff --git a/graphix/pyzx.py b/graphix/pyzx.py index 6c0305a0d..7646d2ca4 100644 --- a/graphix/pyzx.py +++ b/graphix/pyzx.py @@ -37,12 +37,6 @@ def to_pyzx_graph(og: OpenGraph) -> BaseGraph[int, tuple[int, int]]: >>> og = OpenGraph(g, measurements, inputs, outputs) >>> reconstructed_pyzx_graph = to_pyzx_graph(og) """ - # check pyzx availability and version - try: - import pyzx as zx - except ModuleNotFoundError as e: - msg = "Cannot find pyzx (optional dependency)." - raise RuntimeError(msg) from e if zx.__version__ != "0.9.0": warnings.warn( "`to_pyzx_graph` is guaranteed to work only with pyzx==0.9.0 due to possible breaking changes in `pyzx`.", diff --git a/graphix/random_objects.py b/graphix/random_objects.py index f4c8f50c6..d47e96f2e 100644 --- a/graphix/random_objects.py +++ b/graphix/random_objects.py @@ -13,6 +13,7 @@ from graphix.channels import KrausChannel, KrausData from graphix.ops import Ops from graphix.rng import ensure_rng +from graphix.sim.density_matrix import DensityMatrix from graphix.transpiler import Circuit if TYPE_CHECKING: @@ -21,7 +22,6 @@ from numpy.random import Generator from graphix.parameter import Parameter - from graphix.sim.density_matrix import DensityMatrix def rand_herm(sz: int, rng: Generator | None = None) -> npt.NDArray: @@ -80,8 +80,6 @@ def rand_dm( dm = rand_u @ dm @ rand_u.transpose().conj() if dm_dtype: - from graphix.sim.density_matrix import DensityMatrix # circumvent circular import - # will raise an error if incorrect dimension return DensityMatrix(data=dm) return dm diff --git a/graphix/utils.py b/graphix/utils.py index e6817b17b..326ce421b 100644 --- a/graphix/utils.py +++ b/graphix/utils.py @@ -2,6 +2,7 @@ from __future__ import annotations +import inspect import sys import typing from typing import TYPE_CHECKING, Any, ClassVar, Literal, SupportsInt, TypeVar @@ -31,8 +32,6 @@ def check_kind(cls: type, scope: dict[str, Any]) -> None: # MEMO: `inspect.get_annotations` unavailable return - import inspect - ann = inspect.get_annotations(cls, eval_str=True, locals=scope).get("kind") if ann is None: msg = "kind must be annotated." diff --git a/pyproject.toml b/pyproject.toml index 3797c14b2..e22def912 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,6 @@ extend-ignore = [ "DOC", # Docstring "E501", # Line too long "EM10", # Raise string - "PLC0415", # Import not at the top level "PLR1702", # Too many nests "PLW1641", # __hash__ missing "PT011", # pytest raises too broad diff --git a/tests/test_parameter.py b/tests/test_parameter.py index 820bfc234..11aaab403 100644 --- a/tests/test_parameter.py +++ b/tests/test_parameter.py @@ -196,7 +196,8 @@ def test_simulation_exception() -> None: reason="qiskit and/or graphix-ibmq not installed", ) def test_ibmq_backend() -> None: - import qiskit.circuit.exceptions + # This will be removed by #261 + import qiskit.circuit.exceptions # noqa: PLC0415 circuit = graphix.Circuit(1) alpha = Placeholder("alpha") diff --git a/tests/test_pyzx.py b/tests/test_pyzx.py index ea3ca6275..29ed7513e 100644 --- a/tests/test_pyzx.py +++ b/tests/test_pyzx.py @@ -1,6 +1,5 @@ from __future__ import annotations -import importlib.util # Use fully-qualified import to avoid name conflict (util) import random from copy import deepcopy from typing import TYPE_CHECKING @@ -13,22 +12,24 @@ from graphix.random_objects import rand_circuit from graphix.transpiler import Circuit +try: + import pyzx as zx + from pyzx.generate import cliffordT as clifford_t # noqa: N813 + + from graphix.pyzx import from_pyzx_graph, to_pyzx_graph + + _HAS_PYZX = True +except ImportError: + _HAS_PYZX = False + if TYPE_CHECKING: from pyzx.graph.base import BaseGraph SEED = 123 -def _pyzx_notfound() -> bool: - return importlib.util.find_spec("pyzx") is None - - -@pytest.mark.skipif(_pyzx_notfound(), reason="pyzx not installed") +@pytest.mark.skipif(not _HAS_PYZX, reason="pyzx not installed") def test_graph_equality() -> None: - from pyzx.generate import cliffordT as clifford_t # noqa: N813 - - from graphix.pyzx import from_pyzx_graph - random.seed(SEED) g = clifford_t(4, 10, 0.1) @@ -42,10 +43,6 @@ def test_graph_equality() -> None: def assert_reconstructed_pyzx_graph_equal(g: BaseGraph[int, tuple[int, int]]) -> None: """Convert a graph to and from an Open graph and then checks the resulting pyzx graph is equal to the original.""" - import pyzx as zx - - from graphix.pyzx import from_pyzx_graph, to_pyzx_graph - zx.simplify.to_graph_like(g) g_copy = deepcopy(g) @@ -66,20 +63,16 @@ def assert_reconstructed_pyzx_graph_equal(g: BaseGraph[int, tuple[int, int]]) -> # Tests that compiling from a pyzx graph to an OpenGraph returns the same # graph. Only works with small circuits up to 4 qubits since PyZX's `tensorfy` # function seems to consume huge amount of memory for larger qubit -@pytest.mark.skipif(_pyzx_notfound(), reason="pyzx not installed") +@pytest.mark.skipif(not _HAS_PYZX, reason="pyzx not installed") def test_random_clifford_t() -> None: - from pyzx.generate import cliffordT as clifford_t # noqa: N813 - for _ in range(15): g = clifford_t(4, 10, 0.1) assert_reconstructed_pyzx_graph_equal(g) -@pytest.mark.skipif(_pyzx_notfound(), reason="pyzx not installed") +@pytest.mark.skipif(not _HAS_PYZX, reason="pyzx not installed") @pytest.mark.parametrize("jumps", range(1, 11)) def test_random_circuit(fx_bg: PCG64, jumps: int) -> None: - from graphix.pyzx import from_pyzx_graph, to_pyzx_graph - rng = Generator(fx_bg.jumped(jumps)) nqubits = 5 depth = 5 @@ -98,12 +91,8 @@ def test_random_circuit(fx_bg: PCG64, jumps: int) -> None: assert np.abs(np.dot(state.flatten().conjugate(), state2.flatten())) == pytest.approx(1) -@pytest.mark.skipif(_pyzx_notfound(), reason="pyzx not installed") +@pytest.mark.skipif(not _HAS_PYZX, reason="pyzx not installed") def test_rz() -> None: - import pyzx as zx - - from graphix.pyzx import from_pyzx_graph - circuit = Circuit(2) circuit.rz(0, np.pi / 4) pattern = circuit.transpile().pattern @@ -117,12 +106,8 @@ def test_rz() -> None: # Issue #235 -@pytest.mark.skipif(_pyzx_notfound(), reason="pyzx not installed") +@pytest.mark.skipif(not _HAS_PYZX, reason="pyzx not installed") def test_full_reduce_toffoli() -> None: - import pyzx as zx - - from graphix.pyzx import from_pyzx_graph, to_pyzx_graph - c = Circuit(3) c.ccx(0, 1, 2) p = c.transpile().pattern From c0cc172e10f6065774cf1bdac820a22b9905d710 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Mon, 23 Jun 2025 13:55:54 +0200 Subject: [PATCH 2/5] Fix pyright error --- tests/test_pyzx.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/test_pyzx.py b/tests/test_pyzx.py index 29ed7513e..cd0d0486d 100644 --- a/tests/test_pyzx.py +++ b/tests/test_pyzx.py @@ -17,18 +17,23 @@ from pyzx.generate import cliffordT as clifford_t # noqa: N813 from graphix.pyzx import from_pyzx_graph, to_pyzx_graph - - _HAS_PYZX = True except ImportError: - _HAS_PYZX = False + pytestmark = pytest.mark.skip(reason="pyzx not installed") + + if TYPE_CHECKING: + import sys + + # We skip type-checking the case where there is no pyzx, since + # pyright cannot figure out that tests are skipped in this + # case. + sys.exit(1) + if TYPE_CHECKING: from pyzx.graph.base import BaseGraph - SEED = 123 -@pytest.mark.skipif(not _HAS_PYZX, reason="pyzx not installed") def test_graph_equality() -> None: random.seed(SEED) g = clifford_t(4, 10, 0.1) @@ -63,14 +68,12 @@ def assert_reconstructed_pyzx_graph_equal(g: BaseGraph[int, tuple[int, int]]) -> # Tests that compiling from a pyzx graph to an OpenGraph returns the same # graph. Only works with small circuits up to 4 qubits since PyZX's `tensorfy` # function seems to consume huge amount of memory for larger qubit -@pytest.mark.skipif(not _HAS_PYZX, reason="pyzx not installed") def test_random_clifford_t() -> None: for _ in range(15): g = clifford_t(4, 10, 0.1) assert_reconstructed_pyzx_graph_equal(g) -@pytest.mark.skipif(not _HAS_PYZX, reason="pyzx not installed") @pytest.mark.parametrize("jumps", range(1, 11)) def test_random_circuit(fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) @@ -91,7 +94,6 @@ def test_random_circuit(fx_bg: PCG64, jumps: int) -> None: assert np.abs(np.dot(state.flatten().conjugate(), state2.flatten())) == pytest.approx(1) -@pytest.mark.skipif(not _HAS_PYZX, reason="pyzx not installed") def test_rz() -> None: circuit = Circuit(2) circuit.rz(0, np.pi / 4) @@ -106,7 +108,6 @@ def test_rz() -> None: # Issue #235 -@pytest.mark.skipif(not _HAS_PYZX, reason="pyzx not installed") def test_full_reduce_toffoli() -> None: c = Circuit(3) c.ccx(0, 1, 2) From f09beea6dbef66d04b68e11bea2a26b369e4658f Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Tue, 24 Jun 2025 15:30:42 +0200 Subject: [PATCH 3/5] Introduce `repr_mixins` for `DataclassReprMixin` and `EnumReprMixin` --- graphix/repr_mixins.py | 72 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 graphix/repr_mixins.py diff --git a/graphix/repr_mixins.py b/graphix/repr_mixins.py new file mode 100644 index 000000000..543baf15b --- /dev/null +++ b/graphix/repr_mixins.py @@ -0,0 +1,72 @@ +"""Mixins for eval-friendly `repr` for dataclasses and Enum members.""" +from __future__ import annotations + +import dataclasses +from dataclasses import MISSING +from enum import Enum +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + # these live only in the stub package, not at runtime + from _typeshed import DataclassInstance + + +class DataclassReprMixin: + """ + Mixin for a concise, eval-friendly `repr` of dataclasses. + + Compared to the default dataclass `repr`: + - Class variables are omitted (dataclasses.fields only returns actual fields). + - Fields whose values equal their defaults are omitted. + - Field names are only shown when preceding fields have been omitted, ensuring positional listings when possible. + + Use with `@dataclass(repr=False)` on the target class. + """ + + def __repr__(self: DataclassInstance) -> str: + """Return a representation string for a dataclass.""" + cls_name = type(self).__name__ + arguments = [] + saw_omitted = False + for field in dataclasses.fields(self): + value = getattr(self, field.name) + if field.default is not MISSING or field.default_factory is not MISSING: + default = field.default_factory() if field.default_factory is not MISSING else field.default + if value == default: + saw_omitted = True + continue + custom_repr = field.metadata.get("repr") + value_str = custom_repr(value) if custom_repr else repr(value) + if saw_omitted: + arguments.append(f"{field.name}={value_str}") + else: + arguments.append(value_str) + arguments_str = ", ".join(arguments) + return f"{cls_name}({arguments_str})" + + +class EnumReprMixin: + """ + Mixin to provide a concise, eval-friendly repr for Enum members. + + Compared to the default ``, this mixin's `__repr__` + returns `ClassName.MEMBER_NAME`, which can be evaluated in Python (assuming the + enum class is in scope) to retrieve the same member. + """ + + def __repr__(self) -> str: + """ + Return a representation string of an Enum member. + + Returns + ------- + str + A string in the form `ClassName.MEMBER_NAME`. + """ + # Equivalently (as of Python 3.12), `str(value)` also produces + # "ClassName.MEMBER_NAME", but we build it explicitly here for + # clarity. + if not isinstance(self, Enum): + msg = "EnumMixin can only be used with Enum classes." + raise TypeError(msg) + return f"{self.__class__.__name__}.{self.name}" From b3e16c4aa1f420b6b9059639ca59dc44f028be5d Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Tue, 24 Jun 2025 15:36:19 +0200 Subject: [PATCH 4/5] ruff format and forgotten changes --- graphix/command.py | 16 +++++----- graphix/fundamentals.py | 10 +++---- graphix/instruction.py | 31 +++++++++---------- graphix/pretty_print.py | 66 ----------------------------------------- graphix/repr_mixins.py | 1 + 5 files changed, 30 insertions(+), 94 deletions(-) diff --git a/graphix/command.py b/graphix/command.py index 97c6d656a..a2fc52b1d 100644 --- a/graphix/command.py +++ b/graphix/command.py @@ -18,7 +18,7 @@ # Ruff suggests to move this import to a type-checking block, but dataclass requires it here from graphix.parameter import ExpressionOrFloat # noqa: TC001 from graphix.pauli import Pauli -from graphix.pretty_print import DataclassPrettyPrintMixin +from graphix.repr_mixins import DataclassReprMixin from graphix.states import BasicStates, State Node = int @@ -46,7 +46,7 @@ def __init_subclass__(cls) -> None: @dataclasses.dataclass(repr=False) -class N(_KindChecker, DataclassPrettyPrintMixin): +class N(_KindChecker, DataclassReprMixin): r"""Preparation command. Parameters @@ -63,7 +63,7 @@ class N(_KindChecker, DataclassPrettyPrintMixin): @dataclasses.dataclass(repr=False) -class M(_KindChecker, DataclassPrettyPrintMixin): +class M(_KindChecker, DataclassReprMixin): r"""Measurement command. Parameters @@ -112,7 +112,7 @@ def clifford(self, clifford_gate: Clifford) -> M: @dataclasses.dataclass(repr=False) -class E(_KindChecker, DataclassPrettyPrintMixin): +class E(_KindChecker, DataclassReprMixin): r"""Entanglement command between two qubits. Parameters @@ -126,7 +126,7 @@ class E(_KindChecker, DataclassPrettyPrintMixin): @dataclasses.dataclass(repr=False) -class C(_KindChecker, DataclassPrettyPrintMixin): +class C(_KindChecker, DataclassReprMixin): r"""Local Clifford gate command. Parameters @@ -143,7 +143,7 @@ class C(_KindChecker, DataclassPrettyPrintMixin): @dataclasses.dataclass(repr=False) -class X(_KindChecker, DataclassPrettyPrintMixin): +class X(_KindChecker, DataclassReprMixin): r"""X correction command. Parameters @@ -160,7 +160,7 @@ class X(_KindChecker, DataclassPrettyPrintMixin): @dataclasses.dataclass(repr=False) -class Z(_KindChecker, DataclassPrettyPrintMixin): +class Z(_KindChecker, DataclassReprMixin): r"""Z correction command. Parameters @@ -177,7 +177,7 @@ class Z(_KindChecker, DataclassPrettyPrintMixin): @dataclasses.dataclass(repr=False) -class S(_KindChecker, DataclassPrettyPrintMixin): +class S(_KindChecker, DataclassReprMixin): r"""S command. Parameters diff --git a/graphix/fundamentals.py b/graphix/fundamentals.py index d11fb02e8..3f5de0103 100644 --- a/graphix/fundamentals.py +++ b/graphix/fundamentals.py @@ -10,9 +10,9 @@ import typing_extensions -from graphix import pretty_print from graphix.ops import Ops from graphix.parameter import cos_sin +from graphix.repr_mixins import EnumReprMixin if TYPE_CHECKING: import numpy as np @@ -29,7 +29,7 @@ SupportsComplexCtor = Union[SupportsComplex, SupportsFloat, SupportsIndex, complex] -class Sign(pretty_print.EnumPrettyPrintMixin, Enum): +class Sign(EnumReprMixin, Enum): """Sign, plus or minus.""" PLUS = 1 @@ -112,7 +112,7 @@ def __complex__(self) -> complex: return complex(self.value) -class ComplexUnit(pretty_print.EnumPrettyPrintMixin, Enum): +class ComplexUnit(EnumReprMixin, Enum): """ Complex unit: 1, -1, j, -j. @@ -214,7 +214,7 @@ def matrix(self) -> npt.NDArray[np.complex128]: typing_extensions.assert_never(self) -class Axis(pretty_print.EnumPrettyPrintMixin, Enum): +class Axis(EnumReprMixin, Enum): """Axis: *X*, *Y* or *Z*.""" X = enum.auto() @@ -233,7 +233,7 @@ def matrix(self) -> npt.NDArray[np.complex128]: typing_extensions.assert_never(self) -class Plane(pretty_print.EnumPrettyPrintMixin, Enum): +class Plane(EnumReprMixin, Enum): # TODO: Refactor using match """Plane: *XY*, *YZ* or *XZ*.""" diff --git a/graphix/instruction.py b/graphix/instruction.py index b5dfc6256..382cfa257 100644 --- a/graphix/instruction.py +++ b/graphix/instruction.py @@ -14,7 +14,8 @@ # Ruff suggests to move this import to a type-checking block, but dataclass requires it here from graphix.parameter import ExpressionOrFloat # noqa: TC001 -from graphix.pretty_print import DataclassPrettyPrintMixin, OutputFormat, angle_to_str +from graphix.pretty_print import OutputFormat, angle_to_str +from graphix.repr_mixins import DataclassReprMixin def repr_angle(angle: ExpressionOrFloat) -> str: @@ -65,7 +66,7 @@ def __init_subclass__(cls) -> None: @dataclass(repr=False) -class CCX(_KindChecker, DataclassPrettyPrintMixin): +class CCX(_KindChecker, DataclassReprMixin): """Toffoli circuit instruction.""" target: int @@ -74,7 +75,7 @@ class CCX(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class RZZ(_KindChecker, DataclassPrettyPrintMixin): +class RZZ(_KindChecker, DataclassReprMixin): """RZZ circuit instruction.""" target: int @@ -87,7 +88,7 @@ class RZZ(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class CNOT(_KindChecker, DataclassPrettyPrintMixin): +class CNOT(_KindChecker, DataclassReprMixin): """CNOT circuit instruction.""" target: int @@ -96,7 +97,7 @@ class CNOT(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class SWAP(_KindChecker, DataclassPrettyPrintMixin): +class SWAP(_KindChecker, DataclassReprMixin): """SWAP circuit instruction.""" targets: tuple[int, int] @@ -104,7 +105,7 @@ class SWAP(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class H(_KindChecker, DataclassPrettyPrintMixin): +class H(_KindChecker, DataclassReprMixin): """H circuit instruction.""" target: int @@ -112,7 +113,7 @@ class H(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class S(_KindChecker, DataclassPrettyPrintMixin): +class S(_KindChecker, DataclassReprMixin): """S circuit instruction.""" target: int @@ -120,7 +121,7 @@ class S(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class X(_KindChecker, DataclassPrettyPrintMixin): +class X(_KindChecker, DataclassReprMixin): """X circuit instruction.""" target: int @@ -128,7 +129,7 @@ class X(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class Y(_KindChecker, DataclassPrettyPrintMixin): +class Y(_KindChecker, DataclassReprMixin): """Y circuit instruction.""" target: int @@ -136,7 +137,7 @@ class Y(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class Z(_KindChecker, DataclassPrettyPrintMixin): +class Z(_KindChecker, DataclassReprMixin): """Z circuit instruction.""" target: int @@ -144,7 +145,7 @@ class Z(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class I(_KindChecker, DataclassPrettyPrintMixin): +class I(_KindChecker, DataclassReprMixin): """I circuit instruction.""" target: int @@ -152,7 +153,7 @@ class I(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class M(_KindChecker, DataclassPrettyPrintMixin): +class M(_KindChecker, DataclassReprMixin): """M circuit instruction.""" target: int @@ -162,7 +163,7 @@ class M(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class RX(_KindChecker, DataclassPrettyPrintMixin): +class RX(_KindChecker, DataclassReprMixin): """X rotation circuit instruction.""" target: int @@ -172,7 +173,7 @@ class RX(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class RY(_KindChecker, DataclassPrettyPrintMixin): +class RY(_KindChecker, DataclassReprMixin): """Y rotation circuit instruction.""" target: int @@ -182,7 +183,7 @@ class RY(_KindChecker, DataclassPrettyPrintMixin): @dataclass(repr=False) -class RZ(_KindChecker, DataclassPrettyPrintMixin): +class RZ(_KindChecker, DataclassReprMixin): """Z rotation circuit instruction.""" target: int diff --git a/graphix/pretty_print.py b/graphix/pretty_print.py index d3fdc7993..04aa8be95 100644 --- a/graphix/pretty_print.py +++ b/graphix/pretty_print.py @@ -2,11 +2,9 @@ from __future__ import annotations -import dataclasses import enum import math import string -from dataclasses import MISSING from enum import Enum from fractions import Fraction from typing import TYPE_CHECKING, SupportsFloat @@ -16,9 +14,6 @@ if TYPE_CHECKING: from collections.abc import Container - # these live only in the stub package, not at runtime - from _typeshed import DataclassInstance - from graphix.command import Node from graphix.pattern import Pattern @@ -220,64 +215,3 @@ def pattern_to_str( if truncated: return f"{result}...({len(command_list) - limit + 1} more commands)" return result - - -class DataclassPrettyPrintMixin: - """ - Mixin for a concise, eval-friendly `repr` of dataclasses. - - Compared to the default dataclass `repr`: - - Class variables are omitted (dataclasses.fields only returns actual fields). - - Fields whose values equal their defaults are omitted. - - Field names are only shown when preceding fields have been omitted, ensuring positional listings when possible. - - Use with `@dataclass(repr=False)` on the target class. - """ - - def __repr__(self: DataclassInstance) -> str: - """Return a representation string for a dataclass.""" - cls_name = type(self).__name__ - arguments = [] - saw_omitted = False - for field in dataclasses.fields(self): - value = getattr(self, field.name) - if field.default is not MISSING or field.default_factory is not MISSING: - default = field.default_factory() if field.default_factory is not MISSING else field.default - if value == default: - saw_omitted = True - continue - custom_repr = field.metadata.get("repr") - value_str = custom_repr(value) if custom_repr else repr(value) - if saw_omitted: - arguments.append(f"{field.name}={value_str}") - else: - arguments.append(value_str) - arguments_str = ", ".join(arguments) - return f"{cls_name}({arguments_str})" - - -class EnumPrettyPrintMixin: - """ - Mixin to provide a concise, eval-friendly repr for Enum members. - - Compared to the default ``, this mixin's `__repr__` - returns `ClassName.MEMBER_NAME`, which can be evaluated in Python (assuming the - enum class is in scope) to retrieve the same member. - """ - - def __repr__(self) -> str: - """ - Return a representation string of an Enum member. - - Returns - ------- - str - A string in the form `ClassName.MEMBER_NAME`. - """ - # Equivalently (as of Python 3.12), `str(value)` also produces - # "ClassName.MEMBER_NAME", but we build it explicitly here for - # clarity. - if not isinstance(self, Enum): - msg = "EnumMixin can only be used with Enum classes." - raise TypeError(msg) - return f"{self.__class__.__name__}.{self.name}" diff --git a/graphix/repr_mixins.py b/graphix/repr_mixins.py index 543baf15b..70907e904 100644 --- a/graphix/repr_mixins.py +++ b/graphix/repr_mixins.py @@ -1,4 +1,5 @@ """Mixins for eval-friendly `repr` for dataclasses and Enum members.""" + from __future__ import annotations import dataclasses From 8211ab5b9cf33b38b4036bbd1be574e9d9ef7f56 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Thu, 26 Jun 2025 16:34:10 +0200 Subject: [PATCH 5/5] Revert prefixing fundamentals Suggested by @EarlMilktea: https://github.com/TeamGraphix/graphix/pull/307#discussion_r2169076902 --- graphix/pretty_print.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/graphix/pretty_print.py b/graphix/pretty_print.py index 04aa8be95..2892ce174 100644 --- a/graphix/pretty_print.py +++ b/graphix/pretty_print.py @@ -9,7 +9,8 @@ from fractions import Fraction from typing import TYPE_CHECKING, SupportsFloat -from graphix import command, fundamentals +from graphix import command +from graphix.fundamentals import Plane if TYPE_CHECKING: from collections.abc import Container @@ -120,7 +121,7 @@ def command_to_str(cmd: command.Command, output: OutputFormat) -> str: # with some other arguments and/or domains. arguments = [] if cmd.kind == command.CommandKind.M: - if cmd.plane != fundamentals.Plane.XY: + if cmd.plane != Plane.XY: arguments.append(cmd.plane.name) # We use `SupportsFloat` since `isinstance(cmd.angle, float)` # is `False` if `cmd.angle` is an integer.