Skip to content

Commit 4995afa

Browse files
author
Scott Griffiths
committed
Removing hacks for circular import dependencies.
Imports could be improved further, but this at least works.
1 parent 18f5ada commit 4995afa

File tree

6 files changed

+67
-95
lines changed

6 files changed

+67
-95
lines changed

bitstring/__init__.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@
6161

6262
import sys
6363

64-
from .bits import Bits, _initialise_bits_class
64+
from .bits import Bits
6565
from .options import Options
66-
from .bitarray_ import BitArray, _initialise_bitarray_class
66+
from .bitarray_ import BitArray
6767
from .bitstream import ConstBitStream, BitStream
6868
from .methods import pack
6969
from .array_ import Array
@@ -72,12 +72,8 @@
7272
import types
7373
from typing import List, Tuple, Literal
7474

75-
76-
# We initialise the Options singleton after the base classes have been created.
77-
# This avoids a nasty circular import.
75+
# The Options class returns a singleton.
7876
options = Options()
79-
_initialise_bits_class()
80-
_initialise_bitarray_class()
8177

8278
# These get defined properly by the module magic below. This just stops mypy complaining about them.
8379
bytealigned = lsb0 = None
@@ -161,7 +157,7 @@ def bool_bits2chars(bitlength: Literal[1]):
161157
# Bools are printed as 1 or 0, not True or False, so are one character each
162158
return 1
163159

164-
dtypes = [
160+
dtype_definitions = [
165161
# Integer types
166162
DtypeDefinition('uint', Bits._setuint, Bits._getuint, int, False, uint_bits2chars,
167163
description="a two's complement unsigned int"),
@@ -249,7 +245,7 @@ def bool_bits2chars(bitlength: Literal[1]):
249245
])
250246

251247

252-
for dt in dtypes:
248+
for dt in dtype_definitions:
253249
dtype_register.add_dtype(dt)
254250
for alias in aliases:
255251
dtype_register.add_dtype_alias(alias[0], alias[1])

bitstring/bitarray_.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,7 @@
99
from bitstring.exceptions import CreationError, Error
1010
from bitstring.bits import Bits, BitsType, TBits
1111

12-
13-
options = None
14-
15-
def _initialise_bitarray_class() -> None:
16-
from .options import Options
17-
global options
18-
options = Options()
19-
12+
import bitstring.dtypes
2013

2114
class BitArray(Bits):
2215
"""A container holding a mutable sequence of bits.
@@ -163,8 +156,7 @@ def __setattr__(self, attribute, value) -> None:
163156
# First try the ordinary attribute setter
164157
super().__setattr__(attribute, value)
165158
except AttributeError:
166-
from bitstring.dtypes import Dtype
167-
dtype = Dtype(attribute)
159+
dtype = bitstring.dtypes.Dtype(attribute)
168160
x = object.__new__(Bits)
169161
if (set_fn := dtype.set_fn) is None:
170162
raise AttributeError(f"Cannot set attribute '{attribute}' as it does not have a set_fn.")
@@ -308,7 +300,7 @@ def __ixor__(self: TBits, bs: BitsType) -> TBits:
308300

309301
def _replace(self, old: Bits, new: Bits, start: int, end: int, count: int, bytealigned: Optional[bool]) -> int:
310302
if bytealigned is None:
311-
bytealigned = options.bytealigned
303+
bytealigned = bitstring.options.bytealigned
312304
# First find all the places where we want to do the replacements
313305
starting_points: List[int] = []
314306
for x in self.findall(old, start, end, bytealigned=bytealigned):
@@ -329,7 +321,7 @@ def _replace(self, old: Bits, new: Bits, start: int, end: int, count: int, bytea
329321
# Final replacement
330322
replacement_list.append(new._bitstore)
331323
replacement_list.append(self._bitstore.getslice(starting_points[-1] + len(old), None))
332-
if options.lsb0:
324+
if bitstring.options.lsb0:
333325
# Addition of bitarray is always on the right, so assemble from other end
334326
replacement_list.reverse()
335327
self._bitstore.clear()

bitstring/bits.py

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
bin2bitstore, bin2bitstore_unsafe, hex2bitstore, int2bitstore, oct2bitstore, sie2bitstore, uie2bitstore, \
2424
e5m2float2bitstore, e4m3float2bitstore
2525

26+
import bitstring
2627

2728
# Things that can be converted to Bits when a Bits type is needed
2829
BitsType = Union['Bits', str, Iterable[Any], bool, BinaryIO, bytearray, bytes, memoryview, bitarray.bitarray]
@@ -32,21 +33,6 @@
3233
# Maximum number of digits to use in __str__ and __repr__.
3334
MAX_CHARS: int = 250
3435

35-
dtype_register = None
36-
Dtype = None
37-
options = None
38-
39-
def _initialise_bits_class() -> None:
40-
from bitstring.dtypes import dtype_register as _dtype_register
41-
global dtype_register
42-
dtype_register = _dtype_register
43-
from bitstring.dtypes import Dtype as _Dtype
44-
global Dtype
45-
Dtype = _Dtype
46-
from bitstring.options import Options
47-
global options
48-
options = Options()
49-
5036

5137
class Bits:
5238
"""A container holding an immutable sequence of bits.
@@ -193,7 +179,7 @@ def _initialise(self, auto: Any, /, length: Optional[int], offset: Optional[int]
193179
self._setbytes_with_truncation(v, length, offset)
194180
return
195181
try:
196-
setting_function = dtype_register[k].set_fn
182+
setting_function = bitstring.dtypes.dtype_register[k].set_fn
197183
except KeyError:
198184
if k == 'filename':
199185
self._setfile(v, length, offset)
@@ -213,7 +199,7 @@ def _initialise(self, auto: Any, /, length: Optional[int], offset: Optional[int]
213199
def __getattr__(self, attribute: str) -> Any:
214200
# Support for arbitrary attributes like u16 or f64.
215201
try:
216-
d = Dtype(attribute)
202+
d = bitstring.dtypes.Dtype(attribute)
217203
except ValueError as e:
218204
raise AttributeError(e)
219205
if d.bitlength is not None and len(self) != d.bitlength:
@@ -825,7 +811,7 @@ def _setue(self, i: int, length: None = None) -> None:
825811
"""
826812
if length is not None:
827813
raise CreationError("Cannot specify a length for exponential-Golomb codes.")
828-
if options.lsb0:
814+
if bitstring.options.lsb0:
829815
raise CreationError("Exp-Golomb codes cannot be used in lsb0 mode.")
830816
self._bitstore = ue2bitstore(i)
831817

@@ -837,7 +823,7 @@ def _readue(self, pos: int) -> Tuple[int, int]:
837823
838824
"""
839825
# _length is ignored - it's only present to make the function signature consistent.
840-
if options.lsb0:
826+
if bitstring.options.lsb0:
841827
raise ReadError("Exp-Golomb codes cannot be read in lsb0 mode.")
842828
oldpos = pos
843829
try:
@@ -861,7 +847,7 @@ def _setse(self, i: int, length: None = None) -> None:
861847
"""Initialise bitstring with signed exponential-Golomb code for integer i."""
862848
if length is not None:
863849
raise CreationError("Cannot specify a length for exponential-Golomb codes.")
864-
if options.lsb0:
850+
if bitstring.options.lsb0:
865851
raise CreationError("Exp-Golomb codes cannot be used in lsb0 mode.")
866852
self._bitstore = se2bitstore(i)
867853

@@ -889,7 +875,7 @@ def _setuie(self, i: int, length: None = None) -> None:
889875
"""
890876
if length is not None:
891877
raise CreationError("Cannot specify a length for exponential-Golomb codes.")
892-
if options.lsb0:
878+
if bitstring.options.lsb0:
893879
raise CreationError("Exp-Golomb codes cannot be used in lsb0 mode.")
894880
self._bitstore = uie2bitstore(i)
895881

@@ -900,7 +886,7 @@ def _readuie(self, pos: int) -> Tuple[int, int]:
900886
reading the code.
901887
902888
"""
903-
if options.lsb0:
889+
if bitstring.options.lsb0:
904890
raise ReadError("Exp-Golomb codes cannot be read in lsb0 mode.")
905891
try:
906892
codenum: int = 1
@@ -919,7 +905,7 @@ def _setsie(self, i: int, length: None = None) -> None:
919905
"""Initialise bitstring with signed interleaved exponential-Golomb code for integer i."""
920906
if length is not None:
921907
raise CreationError("Cannot specify a length for exponential-Golomb codes.")
922-
if options.lsb0:
908+
if bitstring.options.lsb0:
923909
raise CreationError("Exp-Golomb codes cannot be used in lsb0 mode.")
924910
self._bitstore = sie2bitstore(i)
925911

@@ -1029,7 +1015,7 @@ def _absolute_slice(self: TBits, start: int, end: int) -> TBits:
10291015

10301016
def _readtoken(self, name: str, pos: int, length: Optional[int]) -> Tuple[Union[float, int, str, None, Bits], int]:
10311017
"""Reads a token from the bitstring and returns the result."""
1032-
dtype = dtype_register.get_dtype(name, length)
1018+
dtype = bitstring.dtypes.dtype_register.get_dtype(name, length)
10331019
if dtype.bitlength is not None and dtype.bitlength > len(self) - pos:
10341020
raise ReadError("Reading off the end of the data. "
10351021
f"Tried to read {dtype.bitlength} bits when only {len(self) - pos} available.")
@@ -1188,18 +1174,18 @@ def _readlist(self, fmt: Union[str, List[Union[str, int, Dtype]]], pos: int, **k
11881174
dtype_list = []
11891175
for f_item in fmt:
11901176
if isinstance(f_item, numbers.Integral):
1191-
dtype_list.append(Dtype('bits', f_item))
1192-
elif isinstance(f_item, Dtype):
1177+
dtype_list.append(bitstring.dtypes.Dtype('bits', f_item))
1178+
elif isinstance(f_item, bitstring.dtypes.Dtype):
11931179
dtype_list.append(f_item)
11941180
else:
11951181
token_list = preprocess_tokens(f_item)
11961182
for t in token_list:
11971183
try:
11981184
name, length = parse_name_length_token(t, **kwargs)
11991185
except ValueError:
1200-
dtype_list.append(Dtype('bits', int(t)))
1186+
dtype_list.append(bitstring.dtypes.Dtype('bits', int(t)))
12011187
else:
1202-
dtype_list.append(Dtype(name, length))
1188+
dtype_list.append(bitstring.dtypes.Dtype(name, length))
12031189
return self._read_dtype_list(dtype_list, pos)
12041190

12051191
def _read_dtype_list(self, dtypes: List[Dtype], pos: int) -> Tuple[List[Union[int, float, str, Bits, bool, bytes, None]], int]:
@@ -1229,7 +1215,7 @@ def _read_dtype_list(self, dtypes: List[Dtype], pos: int) -> Tuple[List[Union[in
12291215
raise ValueError(
12301216
f"The '{dtype.name}' type must have a bit length that is a multiple of {dtype.bits_per_item}"
12311217
f" so cannot be created from the {bitlength} bits that are available for this stretchy token.")
1232-
dtype = Dtype(dtype.name, items)
1218+
dtype = bitstring.dtypes.Dtype(dtype.name, items)
12331219
if dtype.bitlength is not None:
12341220
val = dtype.read_fn(self, pos)
12351221
pos += dtype.bitlength
@@ -1265,14 +1251,14 @@ def find(self, bs: BitsType, start: Optional[int] = None, end: Optional[int] = N
12651251
if len(bs) == 0:
12661252
raise ValueError("Cannot find an empty bitstring.")
12671253
start, end = self._validate_slice(start, end)
1268-
ba = options.bytealigned if bytealigned is None else bytealigned
1254+
ba = bitstring.options.bytealigned if bytealigned is None else bytealigned
12691255
p = self._find(bs, start, end, ba)
12701256
return p
12711257

12721258
def _find_lsb0(self, bs: Bits, start: int, end: int, bytealigned: bool) -> Union[Tuple[int], Tuple[()]]:
12731259
# A forward find in lsb0 is very like a reverse find in msb0.
12741260
assert start <= end
1275-
assert options.lsb0
1261+
assert bitstring.options.lsb0
12761262

12771263
new_slice = offset_slice_indices_lsb0(slice(start, end, None), len(self))
12781264
msb0_start, msb0_end = self._validate_slice(new_slice.start, new_slice.stop)
@@ -1310,7 +1296,7 @@ def findall(self, bs: BitsType, start: Optional[int] = None, end: Optional[int]
13101296
raise ValueError("In findall, count must be >= 0.")
13111297
bs = Bits._create_from_bitstype(bs)
13121298
start, end = self._validate_slice(start, end)
1313-
ba = options.bytealigned if bytealigned is None else bytealigned
1299+
ba = bitstring.options.bytealigned if bytealigned is None else bytealigned
13141300
return self._findall(bs, start, end, count, ba)
13151301

13161302
def _findall_msb0(self, bs: Bits, start: int, end: int, count: Optional[int],
@@ -1326,7 +1312,7 @@ def _findall_msb0(self, bs: Bits, start: int, end: int, count: Optional[int],
13261312
def _findall_lsb0(self, bs: Bits, start: int, end: int, count: Optional[int],
13271313
bytealigned: bool) -> Iterable[int]:
13281314
assert start <= end
1329-
assert options.lsb0
1315+
assert bitstring.options.lsb0
13301316

13311317
new_slice = offset_slice_indices_lsb0(slice(start, end, None), len(self))
13321318
msb0_start, msb0_end = self._validate_slice(new_slice.start, new_slice.stop)
@@ -1376,7 +1362,7 @@ def rfind(self, bs: BitsType, start: Optional[int] = None, end: Optional[int] =
13761362
"""
13771363
bs = Bits._create_from_bitstype(bs)
13781364
start, end = self._validate_slice(start, end)
1379-
ba = options.bytealigned if bytealigned is None else bytealigned
1365+
ba = bitstring.options.bytealigned if bytealigned is None else bytealigned
13801366
if len(bs) == 0:
13811367
raise ValueError("Cannot find an empty bitstring.")
13821368
p = self._rfind(bs, start, end, ba)
@@ -1390,7 +1376,7 @@ def _rfind_msb0(self, bs: Bits, start: int, end: int, bytealigned: bool) -> Unio
13901376
def _rfind_lsb0(self, bs: Bits, start: int, end: int, bytealigned: bool) -> Union[Tuple[int], Tuple[()]]:
13911377
# A reverse find in lsb0 is very like a forward find in msb0.
13921378
assert start <= end
1393-
assert options.lsb0
1379+
assert bitstring.options.lsb0
13941380
new_slice = offset_slice_indices_lsb0(slice(start, end, None), len(self))
13951381
msb0_start, msb0_end = self._validate_slice(new_slice.start, new_slice.stop)
13961382

@@ -1451,7 +1437,7 @@ def split(self, delimiter: BitsType, start: Optional[int] = None, end: Optional[
14511437
if len(delimiter) == 0:
14521438
raise ValueError("split delimiter cannot be empty.")
14531439
start, end = self._validate_slice(start, end)
1454-
bytealigned_: bool = options.bytealigned if bytealigned is None else bytealigned
1440+
bytealigned_: bool = bitstring.options.bytealigned if bytealigned is None else bytealigned
14551441
if count is not None and count < 0:
14561442
raise ValueError("Cannot split - count must be >= 0.")
14571443
if count == 0:
@@ -1656,20 +1642,20 @@ def _chars_in_pp_token(fmt: str) -> Tuple[str, Optional[int]]:
16561642

16571643
@staticmethod
16581644
def _format_bits(bits: Bits, bits_per_group: int, sep: str, fmt: str) -> str:
1659-
get_fn = Bits._getbytes_printable if fmt == 'bytes' else dtype_register.get_dtype(fmt, bits_per_group).get_fn
1645+
get_fn = Bits._getbytes_printable if fmt == 'bytes' else bitstring.dtypes.dtype_register.get_dtype(fmt, bits_per_group).get_fn
16601646
if bits_per_group == 0:
16611647
return str(get_fn(bits))
16621648
# Left-align for fixed width types when msb0, otherwise right-align.
1663-
align = '<' if fmt in ['bin', 'oct', 'hex', 'bits', 'bytes'] and not options.lsb0 else '>'
1664-
chars_per_group = dtype_register[fmt].bitlength2chars_fn(bits_per_group)
1649+
align = '<' if fmt in ['bin', 'oct', 'hex', 'bits', 'bytes'] and not bitstring.options.lsb0 else '>'
1650+
chars_per_group = bitstring.dtypes.dtype_register[fmt].bitlength2chars_fn(bits_per_group)
16651651
return sep.join(f"{str(get_fn(b)): {align}{chars_per_group}}" for b in bits.cut(bits_per_group))
16661652

16671653
@staticmethod
16681654
def _chars_per_group(bits_per_group: int, fmt: Optional[str]):
16691655
"""How many characters are needed to represent a number of bits with a given format."""
16701656
if fmt is None:
16711657
return 0
1672-
return dtype_register[fmt].bitlength2chars_fn(bits_per_group)
1658+
return bitstring.dtypes.dtype_register[fmt].bitlength2chars_fn(bits_per_group)
16731659

16741660
def _pp(self, name1: str, name2: Optional[str], bits_per_group: int, width: int, sep: str, format_sep: str,
16751661
show_offset: bool, stream: TextIO, lsb0: bool, offset_factor: int) -> None:
@@ -1709,27 +1695,27 @@ def _pp(self, name1: str, name2: Optional[str], bits_per_group: int, width: int,
17091695
first_fb_width = second_fb_width = None
17101696
for bits in self.cut(max_bits_per_line):
17111697
offset = bitpos // offset_factor
1712-
if options.lsb0:
1698+
if bitstring.options.lsb0:
17131699
offset_str = f'{offset_sep}{offset: >{offset_width - len(offset_sep)}}' if show_offset else ''
17141700
else:
17151701
offset_str = f'{offset: >{offset_width - len(offset_sep)}}{offset_sep}' if show_offset else ''
17161702
fb = Bits._format_bits(bits, bits_per_group, sep, name1)
17171703
if first_fb_width is None:
17181704
first_fb_width = len(fb)
17191705
if len(fb) < first_fb_width: # Pad final line with spaces to align it
1720-
if options.lsb0:
1706+
if bitstring.options.lsb0:
17211707
fb = ' ' * (first_fb_width - len(fb)) + fb
17221708
else:
17231709
fb += ' ' * (first_fb_width - len(fb))
17241710
fb2 = '' if name2 is None else format_sep + Bits._format_bits(bits, bits_per_group, sep, name2)
17251711
if second_fb_width is None:
17261712
second_fb_width = len(fb2)
17271713
if len(fb2) < second_fb_width:
1728-
if options.lsb0:
1714+
if bitstring.options.lsb0:
17291715
fb2 = ' ' * (second_fb_width - len(fb2)) + fb2
17301716
else:
17311717
fb2 += ' ' * (second_fb_width - len(fb2))
1732-
if options.lsb0 is True:
1718+
if bitstring.options.lsb0 is True:
17331719
line_fmt = fb + fb2 + offset_str + '\n'
17341720
else:
17351721
line_fmt = offset_str + fb + fb2 + '\n'
@@ -1799,7 +1785,7 @@ def pp(self, fmt: Optional[str] = None, width: int = 120, sep: str = ' ',
17991785

18001786
format_sep = " " # String to insert on each line between multiple formats
18011787
self._pp(name1, name2 if fmt2 is not None else None, bits_per_group, width, sep, format_sep, show_offset,
1802-
stream, options.lsb0, 1)
1788+
stream, bitstring.options.lsb0, 1)
18031789
return
18041790

18051791
def copy(self: TBits) -> TBits:

bitstring/bitstore_helpers.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from bitstring.exceptions import CreationError
1111
from bitstring.fp8 import e4m3float_fmt, e5m2float_fmt
1212
from bitstring.bitstore import BitStore
13+
import bitstring
14+
1315

1416
byteorder: str = sys.byteorder
1517

@@ -245,17 +247,15 @@ def bytes2bitstore(b: bytes, length: int) -> BitStore:
245247
}
246248

247249
def bitstore_from_token(name: str, token_length: Optional[int], value: Optional[str]) -> BitStore:
248-
from bitstring.dtypes import Dtype
249-
from bitstring.bits import Bits
250250
try:
251-
d = Dtype(name, token_length)
251+
d = bitstring.dtypes.Dtype(name, token_length)
252252
except ValueError as e:
253253
if name in literal_bit_funcs:
254254
bs = literal_bit_funcs[name](value)
255255
else:
256256
raise CreationError(f"Can't parse token: {e}")
257257
else:
258-
b = Bits()
258+
b = bitstring.bits.Bits()
259259
if value is None and name != 'pad':
260260
# 'pad' is a special case - the only dtype that can be constructed from a length with no value.
261261
raise ValueError

0 commit comments

Comments
 (0)