diff --git a/qupulse/pulses/parameters.py b/qupulse/pulses/parameters.py
index f4fbbc94..06d1cb1c 100644
--- a/qupulse/pulses/parameters.py
+++ b/qupulse/pulses/parameters.py
@@ -192,6 +192,9 @@ class ParameterConstraint(AnonymousSerializable):
         else:
             return str(self._expression.sympified_expression)
 
+    def __repr__(self):
+        return 'ParameterConstraint(%s)' % repr(str(self))
+
     def get_serialization_data(self) -> str:
         return str(self)
 
diff --git a/qupulse/pulses/pulse_template.py b/qupulse/pulses/pulse_template.py
index 7296da62..ff0d055d 100644
--- a/qupulse/pulses/pulse_template.py
+++ b/qupulse/pulses/pulse_template.py
@@ -45,6 +45,9 @@ class PulseTemplate(Serializable, SequencingElement, metaclass=DocStringABCMeta)
     called instantiation of the PulseTemplate and achieved by invoking the sequencing process.
     """
 
+    """This is not stable"""
+    _DEFAULT_FORMAT_SPEC = 'identifier'
+
     def __init__(self, *,
                  identifier: Optional[str]) -> None:
         super().__init__(identifier=identifier)
@@ -218,6 +221,29 @@ class PulseTemplate(Serializable, SequencingElement, metaclass=DocStringABCMeta)
                                           global_transformation=global_transformation,
                                           parent_loop=parent_loop)
 
+    def __format__(self, format_spec: str):
+        if format_spec == '':
+            format_spec = self._DEFAULT_FORMAT_SPEC
+        formatted = []
+        for attr in format_spec.split(';'):
+            value = getattr(self, attr)
+            if value is None:
+                continue
+            # the repr(str(value)) is to avoid very deep nesting. If needed one should use repr
+            formatted.append('{attr}={value}'.format(attr=attr, value=repr(str(value))))
+        type_name = type(self).__name__
+        return '{type_name}({attrs})'.format(type_name=type_name, attrs=', '.join(formatted))
+
+    def __str__(self):
+        return format(self)
+
+    def __repr__(self):
+        type_name = type(self).__name__
+        kwargs = ','.join('%s=%r' % (key, value)
+                          for key, value in self.get_serialization_data().items()
+                          if key.isidentifier() and value is not None)
+        return '{type_name}({kwargs})'.format(type_name=type_name, kwargs=kwargs)
+
 
 class AtomicPulseTemplate(PulseTemplate, MeasurementDefiner):
     """A PulseTemplate that does not imply any control flow disruptions and can be directly

Implement __str__ and/or __repr__ for easier inspection
- Implement `__repr__` for ParameterConstraint
 - Implement `__format__` for PulseTemplates to make printing customizable with some easy syntax:
```python
fpt = FunctionPT('sin(omega*t)', 't_dur', channel='X', identifier='my_fpt')

assert f'{fpt}' == "FunctionPT(identifier='my_fpt')"
assert f'{fpt:expression;defined_channels}' == "FunctionPT(expression='sin(omega*t)', defined_channels={'X'})"
```
 - Implement `__str__` for PulseTemplates using `__format__()` with default

diff --git a/tests/pulses/parameters_tests.py b/tests/pulses/parameters_tests.py
index ac3b78cb..e280775e 100644
--- a/tests/pulses/parameters_tests.py
+++ b/tests/pulses/parameters_tests.py
@@ -114,6 +114,10 @@ class ParameterConstraintTest(unittest.TestCase):
         self.assertEqual(str(ParameterConstraint('a==b')), 'a==b')
         self.assertEqual(ParameterConstraint('a==b').get_serialization_data(), 'a==b')
 
+    def test_repr(self):
+        pc = ParameterConstraint('a < b')
+        self.assertEqual("ParameterConstraint('a < b')", repr(pc))
+
 
 class ParameterNotProvidedExceptionTests(unittest.TestCase):
 
diff --git a/tests/pulses/pulse_template_tests.py b/tests/pulses/pulse_template_tests.py
index 304f13a7..6c551b81 100644
--- a/tests/pulses/pulse_template_tests.py
+++ b/tests/pulses/pulse_template_tests.py
@@ -351,6 +351,13 @@ class PulseTemplateTest(unittest.TestCase):
             self.assertEqual(b @ a, 'concat')
             mock_concatenate.assert_called_once_with(b, a)
 
+    def test_format(self):
+        a = PulseTemplateStub(identifier='asd', duration=Expression(5))
+        self.assertEqual("PulseTemplateStub(identifier='asd')", str(a))
+        self.assertEqual("PulseTemplateStub(identifier='asd')", format(a))
+        self.assertEqual("PulseTemplateStub(identifier='asd', duration='5')",
+                         "{:identifier;duration}".format(a))
+
 
 class AtomicPulseTemplateTests(unittest.TestCase):
 

pip3 install .[plotting,zurich-instruments,tektronix,Faster-fractions]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
apt-get update apt-get install -y libgmp-dev libmpfr-dev libmpc-dev
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
cached-property==1.5.2
certifi==2021.5.30
coverage==6.2
cycler==0.11.0
dataclasses==0.8
execnet==1.9.0
gmpy2==2.1.5
importlib-metadata==4.8.3
iniconfig==1.1.1
kiwisolver==1.3.1
matplotlib==3.3.4
more-itertools==8.14.0
mpmath==1.3.0
numpy==1.19.5
packaging==21.3
Pillow==8.4.0
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
pytest-asyncio==0.16.0
pytest-cov==4.0.0
pytest-mock==3.6.1
pytest-xdist==3.0.2
python-dateutil==2.9.0.post0
PyVISA==1.11.3
qupulse @ file:///qupulse
six==1.17.0
sympy==1.9
tek-awg==0.2.1
tomli==1.2.3
typing_extensions==4.1.1
zhinst==22.2.29711
zipp==3.6.0

name: qupulse
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - cached-property==1.5.2
      - coverage==6.2
      - cycler==0.11.0
      - dataclasses==0.8
      - execnet==1.9.0
      - gmpy2==2.1.5
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - kiwisolver==1.3.1
      - matplotlib==3.3.4
      - more-itertools==8.14.0
      - mpmath==1.3.0
      - numpy==1.19.5
      - packaging==21.3
      - pillow==8.4.0
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-asyncio==0.16.0
      - pytest-cov==4.0.0
      - pytest-mock==3.6.1
      - pytest-xdist==3.0.2
      - python-dateutil==2.9.0.post0
      - pyvisa==1.11.3
      - qupulse==0.4
      - six==1.17.0
      - sympy==1.9
      - tek-awg==0.2.1
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zhinst==22.2.29711
      - zipp==3.6.0
prefix: /opt/conda/envs/qupulse

tests/pulses/pulse_template_tests.py::PulseTemplateTest::test_format
tests/pulses/parameters_tests.py::ParameterConstraintTest::test_equal tests/pulses/parameters_tests.py::ParameterConstraintTest::test_expressions tests/pulses/parameters_tests.py::ParameterConstraintTest::test_no_relation tests/pulses/parameters_tests.py::ParameterConstraintTest::test_ordering tests/pulses/parameters_tests.py::ParameterConstraintTest::test_repr tests/pulses/parameters_tests.py::ParameterConstraintTest::test_str_and_serialization
tests/pulses/parameters_tests.py::ConstantParameterTest::test_expression_value tests/pulses/parameters_tests.py::ConstantParameterTest::test_float_values tests/pulses/parameters_tests.py::ConstantParameterTest::test_invalid_expression_value tests/pulses/parameters_tests.py::ConstantParameterTest::test_numpy_value tests/pulses/parameters_tests.py::ConstantParameterTest::test_repr tests/pulses/parameters_tests.py::ConstantParameterTest::test_requires_stop tests/pulses/parameters_tests.py::MappedParameterTest::test_repr tests/pulses/parameters_tests.py::MappedParameterTest::test_requires_stop_and_get_value tests/pulses/parameters_tests.py::ParameterNotProvidedExceptionTests::test tests/pulses/parameters_tests.py::InvalidParameterNameExceptionTests::test tests/pulses/pulse_template_tests.py::PulseTemplateTest::test__create_program tests/pulses/pulse_template_tests.py::PulseTemplateTest::test__create_program_single_waveform tests/pulses/pulse_template_tests.py::PulseTemplateTest::test_create_program tests/pulses/pulse_template_tests.py::PulseTemplateTest::test_create_program_channel_mapping tests/pulses/pulse_template_tests.py::PulseTemplateTest::test_create_program_defaults tests/pulses/pulse_template_tests.py::PulseTemplateTest::test_create_program_none tests/pulses/pulse_template_tests.py::PulseTemplateTest::test_matmul tests/pulses/pulse_template_tests.py::PulseTemplateTest::test_rmatmul tests/pulses/pulse_template_tests.py::AtomicPulseTemplateTests::test_build_sequence tests/pulses/pulse_template_tests.py::AtomicPulseTemplateTests::test_build_sequence_no_waveform tests/pulses/pulse_template_tests.py::AtomicPulseTemplateTests::test_internal_create_program tests/pulses/pulse_template_tests.py::AtomicPulseTemplateTests::test_internal_create_program_missing_parameters tests/pulses/pulse_template_tests.py::AtomicPulseTemplateTests::test_internal_create_program_no_waveform tests/pulses/pulse_template_tests.py::AtomicPulseTemplateTests::test_internal_create_program_transformation tests/pulses/pulse_template_tests.py::AtomicPulseTemplateTests::test_is_interruptable
qupulse/pulses/pulse_template.py qupulse/pulses/parameters.py
qupulse/pulses/pulse_template.py qupulse/pulses/parameters.py