Skip to content
Open
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
9 changes: 4 additions & 5 deletions bitstring/array_.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,8 @@ def pp(self, fmt: str | None = None, width: int = 120, sep: str = ' ',
show_offset: bool = True, stream: TextIO | None = None, color: bool | None = None) -> None:
"""Pretty-print the Array contents.

fmt -- Data format string. Defaults to current Array dtype.
fmt -- Data format string. Defaults to current Array dtype. Use '_' to refer to
the current dtype within a format, e.g. '_, bin'.
width -- Max width of printed lines in characters. Defaults to 120. A single group will always
be printed per line even if it exceeds the max width.
sep -- A separator string to insert between groups. Defaults to a single space.
Expand All @@ -593,11 +594,9 @@ def pp(self, fmt: str | None = None, width: int = 120, sep: str = ' ',
token_list = utils.preprocess_tokens(fmt)
if len(token_list) not in [1, 2]:
raise ValueError(f"Only one or two tokens can be used in an Array.pp() format - '{fmt}' has {len(token_list)} tokens.")
name1, length1 = utils.parse_name_length_token(token_list[0])
dtype1 = Dtype(name1, length1)
dtype1 = self.dtype if token_list[0] == '_' else Dtype(token_list[0])
if len(token_list) == 2:
name2, length2 = utils.parse_name_length_token(token_list[1])
dtype2 = Dtype(name2, length2)
dtype2 = self.dtype if token_list[1] == '_' else Dtype(token_list[1])

token_length = dtype1.bitlength
if dtype2 is not None:
Expand Down
6 changes: 6 additions & 0 deletions doc/array.rst
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,12 @@ Methods

The format string `fmt` defaults to the Array's current :attr:`dtype`, but any other valid Array format string can be used.

Use ``_`` for the current :attr:`dtype` in either position of the format string. For example,
``a.pp('_, bin')`` prints each element using its current dtype alongside its binary representation.
The dtype is resolved each time ``pp()`` is called, so changing ``a.dtype`` also changes
the interpretation used by ``_``. A second format with an explicit length must have the
same bit length as the current dtype.

If a `fmt` doesn't have an explicit length, the Array's :attr:`itemsize` will be used.

A pair of comma-separated format strings can also be used - if both formats specify a length they must be the same. For example ``'f, hex16'`` or ``'u4, bin4'``.
Expand Down
5 changes: 5 additions & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@

# Release Notes

## Unreleased

* `Array.pp()` accepts `_` as shorthand for the current array dtype, including
in two-format output such as `a.pp('_, bin')`.

## October 2026: version 5.0.0 released

A major release which completes the move to using the `tibs` Rust library as the core.
Expand Down
31 changes: 31 additions & 0 deletions tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,37 @@ def test_pp_sep(self):
" 0: c0403f804000\n" \
"]\n"

@pytest.mark.parametrize('fmt', ['_, bin', 'bin, _', '_', '_, _'])
def test_pp_current_dtype(self, fmt):
a = Array('u8', [1, 255])
actual, expected = io.StringIO(), io.StringIO()
a.pp(fmt, stream=actual, color=False)
a.pp(fmt.replace('_', 'u8'), stream=expected, color=False)
assert actual.getvalue() == expected.getvalue()
assert a.tolist() == [1, 255]

def test_pp_current_dtype_after_change(self):
a = Array('u8', [255])
a.dtype = 'i8'
a.data += '0b11'
s = io.StringIO()
a.pp('_, hex', stream=s, color=False, show_offset=False)
assert s.getvalue() == "<Array fmt='i8, hex', length=1, itemsize=8 bits, total data size=2 bytes> [\n" \
" -1 : ff\n] + trailing_bits = 0b11\n"

def test_pp_current_dtype_bytes(self):
a = Array('bytes2', [b'ab'])
s = io.StringIO()
a.pp('_, hex', stream=s, color=False, show_offset=False)
assert "itemsize=16 bits" in s.getvalue()
assert "ab : 6162" in s.getvalue()

@pytest.mark.parametrize('fmt', ['_, hex16', 'hex16, _'])
def test_pp_current_dtype_mismatched_length(self, fmt):
a = Array('u8', [1])
with pytest.raises(ValueError, match='Two different format lengths'):
a.pp(fmt)

def test_pp_signature_matches_bits(self):
# The two pp() methods should stay positionally interchangeable.
import inspect
Expand Down