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
2 changes: 2 additions & 0 deletions CHANGE_LOG
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
2026-XX-XX 3.8.2:
-------------------
* drop Python 3.6 support
* expose the `decodeiterator` class, and add the `skipbits` method and `index`
data descriptor


2026-04-02 3.8.1:
Expand Down
5 changes: 3 additions & 2 deletions bitarray/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
from collections import namedtuple

from bitarray._bitarray import (
bitarray, decodetree, bits2bytes, _bitarray_reconstructor,
bitarray, decodetree, bits2bytes, decodeiterator, _bitarray_reconstructor,
get_default_endian, _sysinfo, BITARRAY_VERSION as __version__
)

__all__ = ['bitarray', 'frozenbitarray', 'decodetree', 'bits2bytes']
__all__ = ['bitarray', 'frozenbitarray', 'decodetree', 'bits2bytes',
'decodeiterator']

BufferInfo = namedtuple('BufferInfo',
['address', 'nbytes', 'endian', 'padbits',
Expand Down
9 changes: 8 additions & 1 deletion bitarray/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ class decodetree:
def todict(self) -> CodeDict: ...


class decodeiterator(Iterator):
@property
def index(self) -> int: ...

def skipbits(self, count: int = ...) -> bitarray: ...


class bitarray:
def __init__(self,
initializer: Union[int, str, Iterable[int], None] = ...,
Expand All @@ -55,7 +62,7 @@ class bitarray:

def encode(self, code: CodeDict, x: Iterable) -> None: ...
def decode(self,
code: Union[CodeDict, decodetree]) -> Iterator: ...
code: Union[CodeDict, decodetree]) -> decodeiterator: ...

def extend(self, x: Union[str, Iterable[int]]) -> None: ...
def fill(self) -> int: ...
Expand Down
79 changes: 69 additions & 10 deletions bitarray/_bitarray.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#define PY_SSIZE_T_CLEAN
#include "Python.h"
#include "pythoncapi_compat.h"
#include "structmember.h"
#include "bitarray.h"

/* size used when reading / writing blocks from files (in bytes) */
Expand Down Expand Up @@ -2081,18 +2082,13 @@ static PySequenceMethods bitarray_as_sequence = {

/* ----------------------- bitarray_as_mapping ------------------------- */

/* return new bitarray with item in self, specified by slice */
/* return new bitarray with item in self, specified by slice indices */
static PyObject *
getslice(bitarrayobject *self, PyObject *slice)
getslice_indices(bitarrayobject *self, Py_ssize_t start, Py_ssize_t step,
Py_ssize_t slicelength)
{
Py_ssize_t start, stop, step, slicelength;
bitarrayobject *res;

assert(PySlice_Check(slice));
if (PySlice_GetIndicesEx(slice, self->nbits,
&start, &stop, &step, &slicelength) < 0)
return NULL;

res = newbitarrayobject(Py_TYPE(self), slicelength, self->endian);
if (res == NULL)
return NULL;
Expand All @@ -2109,6 +2105,20 @@ getslice(bitarrayobject *self, PyObject *slice)
return freeze_if_frozen(res);
}

/* return new bitarray with item in self, specified by slice */
static PyObject *
getslice(bitarrayobject *self, PyObject *slice)
{
Py_ssize_t start, stop, step, slicelength;

assert(PySlice_Check(slice));
if (PySlice_GetIndicesEx(slice, self->nbits,
&start, &stop, &step, &slicelength) < 0)
return NULL;

return getslice_indices(self, start, step, slicelength);
}

static int
ensure_mask_size(bitarrayobject *self, bitarrayobject *mask)
{
Expand Down Expand Up @@ -3319,7 +3329,7 @@ bitarray_decode(bitarrayobject *self, PyObject *obj)
}

PyDoc_STRVAR(decode_doc,
"decode(code, /) -> iterator\n\
"decode(code, /) -> decodeiterator\n\
\n\
Given a prefix code (a dict mapping symbols to bitarrays, or `decodetree`\n\
object), decode content of bitarray and return an iterator over\n\
Expand Down Expand Up @@ -3359,6 +3369,52 @@ decodeiter_traverse(decodeiterobject *it, visitproc visit, void *arg)
return 0;
}

static PyObject *
decodeiter_skipbits(decodeiterobject *it, PyObject *args)
{
Py_ssize_t count = 1;
if (!PyArg_ParseTuple(args, "|n:skipbits", &count)) {
return NULL;
}

if (count < 0) {
return PyErr_Format(PyExc_ValueError, "negative skip count %zd", count);
}

Py_ssize_t new_index = it->index + count;
if (new_index > it->self->nbits) {
return PyErr_Format(PyExc_ValueError,
"new index %zd out of range %zd",
new_index, it->self->nbits);
}

PyObject *skipped = getslice_indices(it->self, it->index, 1, count);
it->index = new_index;
return skipped;
}

PyDoc_STRVAR(decodeiter_skipbits_doc,
"skipbits(count=1, /) -> bitarray\n\
\n\
Skips over the next `count` bits (default 1) and returns them.\n\
Raises `ValueError` if count is out of range.");

static PyMethodDef decodeiter_methods[] = {
{"skipbits", (PyCFunction) decodeiter_skipbits, METH_VARARGS,
decodeiter_skipbits_doc},
{NULL}
};

PyDoc_STRVAR(decodeiter_index_doc,
"The position in the underlying bitarray to be decoded by the subsequent\n"
"call to `next`.");

static PyMemberDef decodeiter_members[] = {
{"index", Py_T_PYSSIZET, offsetof(decodeiterobject, index), Py_READONLY,
decodeiter_index_doc},
{NULL}
};

static PyTypeObject DecodeIter_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"bitarray.decodeiterator", /* tp_name */
Expand Down Expand Up @@ -3388,7 +3444,8 @@ static PyTypeObject DecodeIter_Type = {
0, /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
(iternextfunc) decodeiter_next, /* tp_iternext */
0, /* tp_methods */
decodeiter_methods, /* tp_methods */
decodeiter_members, /* tp_members */
};

/*********************** (Bitarray) Search Iterator ***********************/
Expand Down Expand Up @@ -4283,6 +4340,8 @@ PyInit__bitarray(void)
if (PyType_Ready(&DecodeIter_Type) < 0)
return NULL;
Py_SET_TYPE(&DecodeIter_Type, &PyType_Type);
Py_INCREF((PyObject *) &DecodeIter_Type);
PyModule_AddObject(m, "decodeiterator", (PyObject *) &DecodeIter_Type);

if (PyType_Ready(&BitarrayIter_Type) < 0)
return NULL;
Expand Down
25 changes: 25 additions & 0 deletions bitarray/test_bitarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -4480,7 +4480,9 @@ def test_decode_type(self):
a = bitarray('0110')
it = a.decode(alphabet_code)
self.assertIsType(it, 'decodeiterator')
self.assertEqual(it.index, 0)
self.assertEqual(list(it), ['a'])
self.assertEqual(it.index, len(a))

def test_decode_remove(self):
d = {'I': bitarray('1'), 'l': bitarray('01'),
Expand Down Expand Up @@ -4598,6 +4600,29 @@ def test_decode_ambiguous_code(self):
self.assertRaises(ValueError, a.decode, d)
self.check_obj(a)

def test_decode_skipbits(self):
d = {'a': bitarray('0'), 'b': bitarray('10'), 'c': bitarray('11')}
a = bitarray('0 10 0 11 10101 0110')
it = a.decode(d)
self.assertEqual(it.index, 0)
self.assertEqual(next(it), 'a')
self.assertEqual(it.index, 1)
self.assertEqual(next(it), 'b')
self.assertEqual(it.index, 3)
self.assertEqual(it.skipbits(), bitarray('0'))
self.assertEqual(it.index, 4)
self.assertEqual(next(it), 'c')
self.assertEqual(it.index, 6)
self.assertEqual(it.skipbits(5), bitarray('10101'))
self.assertEqual(it.index, 11)
self.assertRaises(ValueError, it.skipbits, -1)
self.assertRaises(ValueError, it.skipbits, 5)
self.assertEqual(it.index, 11)
self.assertEqual(it.skipbits(4), bitarray('0110'))
self.assertEqual(it.index, 15)
self.assertRaises(StopIteration, next, it)
self.assertEqual(it.index, 15)

def test_miscitems(self):
d = {None : bitarray('00'),
0 : bitarray('110'),
Expand Down
44 changes: 27 additions & 17 deletions update_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
'3.0: returns iterator (equivalent to past '
'`.itersearch()`)'],
'bitarray.to01': '3.3: optional `group` and `sep` arguments',
'decodeiterator.index': '3.8.2',
'decodeiterator.skipbits':'3.8.2',
'decodetree': '1.6',
'frozenbitarray': '1.1',
'get_default_endian': '1.3',
Expand Down Expand Up @@ -124,6 +126,7 @@
'bitarray.nbytes': 'int',
'bitarray.padbits': 'int',
'bitarray.readonly': 'bool',
'decodeiterator.index':'int',
}

_NAMES = set()
Expand All @@ -146,7 +149,7 @@ def get_doc(name):

lines = obj.__doc__.splitlines()

if len(lines) == 1:
if name in GETSET:
sig = '``%s`` -> %s' % (obj.__name__, GETSET[name])
return sig, lines

Expand Down Expand Up @@ -187,6 +190,27 @@ def write_doc(fo, name):
fo.write('\n\n')


def write_reference_for_class(fo, cl):
class_name = cl.__name__
heading = "%s methods:" % class_name
fo.write("%s\n%s\n\n" % (heading, '-' * len(heading)))
for method in sorted(dir(cl)):
if method.startswith('_'):
continue
name = '%s.%s' % (class_name, method)
if name not in GETSET:
write_doc(fo, name)

heading = "%s data descriptors:" % class_name
fo.write("%s\n%s\n\n" % (heading, '-' * len(heading)))
if class_name == "bitarray":
fo.write("Data descriptors were added in version 2.6.\n\n")
for getset in sorted(dir(bitarray.bitarray)):
name = '%s.%s' % (class_name, getset)
if name in GETSET:
write_doc(fo, name)


def write_reference(fo):
fo.write("""\
Reference
Expand All @@ -206,22 +230,8 @@ def write_reference(fo):
""" % (bitarray.__version__, BASE_URL + "/blob/master/doc/changelog.rst"))
write_doc(fo, 'bitarray')

fo.write("bitarray methods:\n"
"-----------------\n\n")
for method in sorted(dir(bitarray.bitarray)):
if method.startswith('_'):
continue
name = 'bitarray.%s' % method
if name not in GETSET:
write_doc(fo, name)

fo.write("bitarray data descriptors:\n"
"--------------------------\n\n"
"Data descriptors were added in version 2.6.\n\n")
for getset in sorted(dir(bitarray.bitarray)):
name = 'bitarray.%s' % getset
if name in GETSET:
write_doc(fo, name)
write_reference_for_class(fo, bitarray.bitarray)
write_reference_for_class(fo, bitarray.decodeiterator)

fo.write("Other objects:\n"
"--------------\n\n")
Expand Down
Loading