From 96dc6b7d542c3298e22f800f153c51582c30bc9c Mon Sep 17 00:00:00 2001 From: AC Date: Thu, 15 Oct 2026 13:21:32 +0800 Subject: [PATCH] Support current dtype shorthand in Array.pp --- bitstring/array_.py | 9 ++++----- doc/array.rst | 6 ++++++ release_notes.md | 5 +++++ tests/test_array.py | 31 +++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/bitstring/array_.py b/bitstring/array_.py index ad72bfca..5846b096 100644 --- a/bitstring/array_.py +++ b/bitstring/array_.py @@ -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. @@ -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: diff --git a/doc/array.rst b/doc/array.rst index ab6ee3e1..0f7748c7 100644 --- a/doc/array.rst +++ b/doc/array.rst @@ -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'``. diff --git a/release_notes.md b/release_notes.md index 9f0dc24d..3ccd053a 100644 --- a/release_notes.md +++ b/release_notes.md @@ -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. diff --git a/tests/test_array.py b/tests/test_array.py index 6e4323b6..deb4870f 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -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() == " [\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