Skip to content

Commit 6af8091

Browse files
author
Scott Griffiths
committed
Adding scale to Dtype.
Also making Dtype immutable, which it really should have been already.
1 parent 2adf46c commit 6af8091

9 files changed

Lines changed: 188 additions & 257 deletions

File tree

bitstring/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,6 @@
6767
from .bitstream import ConstBitStream, BitStream
6868
from .methods import pack
6969
from .array_ import Array
70-
from .scaled_array import ScaledArray
71-
from .scaled_dtypes import ScaledDtype
7270
from .exceptions import Error, ReadError, InterpretError, ByteAlignError, CreationError
7371
from .dtypes import DtypeDefinition, dtype_register, Dtype
7472
import types
@@ -292,6 +290,6 @@ def bool_bits2chars(bitlength: Literal[1]):
292290
BitStream.__doc__ = BitStream.__doc__.replace('[GENERATED_PROPERTY_DESCRIPTIONS]', property_docstring)
293291

294292

295-
__all__ = ['ConstBitStream', 'BitStream', 'BitArray', 'Array', 'ScaledArray', 'ScaledDtype',
293+
__all__ = ['ConstBitStream', 'BitStream', 'BitArray', 'Array',
296294
'Bits', 'pack', 'Error', 'ReadError', 'InterpretError',
297295
'ByteAlignError', 'CreationError', 'bytealigned', 'lsb0', 'Dtype', 'options']

bitstring/array_.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def extend(self, iterable: Union[Array, array.array, Iterable[Any]]) -> None:
261261
name_value = utils.parse_single_struct_token('=' + iterable.typecode)
262262
if name_value is None:
263263
raise ValueError(f"Cannot extend from array with typecode {iterable.typecode}.")
264-
other_dtype = dtype_register.get_dtype(*name_value)
264+
other_dtype = dtype_register.get_dtype(*name_value, scale=None)
265265
if self._dtype.name != other_dtype.name or self._dtype.length != other_dtype.length:
266266
raise ValueError(
267267
f"Cannot extend an Array with format '{self._dtype}' from an array with typecode '{iterable.typecode}'.")

bitstring/dtypes.py

Lines changed: 132 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,92 +9,171 @@
99
CACHE_SIZE = 256
1010

1111

12-
class Dtype:
12+
def scaled_get_fn(get_fn, s: Union[int, float]):
13+
def wrapper(*args, scale=s, **kwargs):
14+
return get_fn(*args, **kwargs) * scale
15+
return wrapper
16+
17+
18+
def scaled_set_fn(set_fn, s: Union[int, float]):
19+
def wrapper(bs, value, *args, scale=s, **kwargs):
20+
return set_fn(bs, value / scale, *args, **kwargs)
21+
return wrapper
22+
23+
24+
def scaled_read_fn(read_fn, s: Union[int, float]):
25+
def wrapper(*args, scale=s, **kwargs):
26+
return read_fn(*args, **kwargs) * scale
27+
return wrapper
1328

14-
__slots__ = ('read_fn', 'name', 'set_fn', 'get_fn', 'return_type', 'is_signed', 'set_fn_needs_length', 'variable_length', 'bitlength', 'bits_per_item', 'length')
15-
16-
name: str
17-
read_fn: Callable
18-
set_fn: Callable
19-
get_fn: Callable
20-
return_type: Any
21-
is_signed: bool
22-
set_fn_needs_length: bool
23-
variable_length: bool
24-
bitlength: Optional[int]
25-
bits_per_item: int
26-
length: Optional[int]
27-
28-
def __new__(cls, token: Union[str, Dtype, None] = None, /, length: Optional[int] = None) -> Dtype:
29+
30+
class Dtype:
31+
_name: str
32+
_read_fn: Callable
33+
_set_fn: Callable
34+
_get_fn: Callable
35+
_return_type: Any
36+
_is_signed: bool
37+
_set_fn_needs_length: bool
38+
_variable_length: bool
39+
_bitlength: Optional[int]
40+
_bits_per_item: int
41+
_length: Optional[int]
42+
_scale: Union[None, float, int]
43+
44+
45+
def __new__(cls, token: Union[str, Dtype, None] = None, /, length: Optional[int] = None, scale: Union[None, float, int] = None) -> Dtype:
2946
if isinstance(token, cls):
3047
return token
3148
if token is not None:
3249
if length is None:
33-
return cls._new_from_token(token)
50+
x = cls._new_from_token(token, scale)
51+
return x
3452
else:
35-
return dtype_register.get_dtype(token, length)
53+
x = dtype_register.get_dtype(token, length, scale)
54+
return x
3655
return super().__new__(cls)
3756

57+
@property
58+
def scale(self) -> int:
59+
return self._scale
60+
61+
@property
62+
def name(self) -> str:
63+
return self._name
64+
65+
@property
66+
def length(self) -> int:
67+
return self._length
68+
69+
@property
70+
def bitlength(self) -> int:
71+
return self._bitlength
72+
73+
@property
74+
def bits_per_item(self) -> int:
75+
return self._bits_per_item
76+
77+
@property
78+
def variable_length(self) -> bool:
79+
return self._variable_length
80+
81+
@property
82+
def return_type(self) -> Any:
83+
return self._return_type
84+
85+
@property
86+
def is_signed(self) -> bool:
87+
return self._is_signed
88+
89+
@property
90+
def set_fn(self) -> Optional[Callable]:
91+
return self._set_fn
92+
93+
@property
94+
def get_fn(self) -> Callable:
95+
return self._get_fn
96+
97+
@property
98+
def read_fn(self) -> Callable:
99+
return self._read_fn
100+
101+
def _set_scale(self, value: Union[None, float, int]) -> None:
102+
self._scale = value
103+
if self._scale is None:
104+
return
105+
if not hasattr(self, 'unscaled_get_fn'):
106+
self.unscaled_get_fn = self._get_fn
107+
self.unscaled_set_fn = self._set_fn
108+
self.unscaled_read_fn = self._read_fn
109+
self._get_fn = scaled_get_fn(self.unscaled_get_fn, self._scale)
110+
self._set_fn = scaled_set_fn(self.unscaled_set_fn, self._scale)
111+
self._read_fn = scaled_read_fn(self.unscaled_read_fn, self._scale)
112+
38113
@classmethod
39114
@functools.lru_cache(CACHE_SIZE)
40-
def _new_from_token(cls, token: str) -> Dtype:
115+
def _new_from_token(cls, token: str, scale: Union[None, float, int] = None) -> Dtype:
41116
token = ''.join(token.split())
42-
return dtype_register.get_dtype(*utils.parse_name_length_token(token))
117+
return dtype_register.get_dtype(*utils.parse_name_length_token(token), scale=scale)
43118

44119
def __hash__(self) -> int:
45-
return hash((self.name, self.length))
120+
return hash((self._name, self._length))
46121

47122
@classmethod
48123
@functools.lru_cache(CACHE_SIZE)
49-
def _create(cls, definition: DtypeDefinition, length: Optional[int]) -> Dtype:
124+
def _create(cls, definition: DtypeDefinition, length: Optional[int], scale: Union[None, float, int]) -> Dtype:
50125
x = cls.__new__(cls)
51-
x.name = definition.name
52-
x.bitlength = x.length = length
53-
x.bits_per_item = definition.multiplier
54-
if x.bitlength is not None:
55-
x.bitlength *= x.bits_per_item
56-
x.set_fn_needs_length = definition.set_fn_needs_length
57-
x.variable_length = definition.variable_length
58-
if x.variable_length or len(dtype_register.names[x.name].allowed_lengths) == 1:
59-
x.read_fn = definition.read_fn
126+
x._name = definition.name
127+
x._bitlength = x._length = length
128+
x._bits_per_item = definition.multiplier
129+
if x._bitlength is not None:
130+
x._bitlength *= x._bits_per_item
131+
x._set_fn_needs_length = definition.set_fn_needs_length
132+
x._variable_length = definition.variable_length
133+
if x._variable_length or len(dtype_register.names[x._name].allowed_lengths) == 1:
134+
x._read_fn = definition.read_fn
60135
else:
61-
x.read_fn = functools.partial(definition.read_fn, length=x.bitlength)
136+
x._read_fn = functools.partial(definition.read_fn, length=x._bitlength)
62137
if definition.set_fn is None:
63-
x.set_fn = None
138+
x._set_fn = None
64139
else:
65-
if x.set_fn_needs_length:
66-
x.set_fn = functools.partial(definition.set_fn, length=x.bitlength)
140+
if x._set_fn_needs_length:
141+
x._set_fn = functools.partial(definition.set_fn, length=x._bitlength)
67142
else:
68-
x.set_fn = definition.set_fn
69-
x.get_fn = definition.get_fn
70-
x.return_type = definition.return_type
71-
x.is_signed = definition.is_signed
143+
x._set_fn = definition.set_fn
144+
x._get_fn = definition.get_fn
145+
x._return_type = definition.return_type
146+
x._is_signed = definition.is_signed
147+
x._set_scale(scale)
72148
return x
73149

74150
def build(self, value: Any, /) -> bitstring.Bits:
75151
"""Create a bitstring from a value."""
76152
b = bitstring.Bits()
77-
self.set_fn(b, value)
153+
self._set_fn(b, value)
78154
return b
79155

80156
def parse(self, b: BitsType, /) -> Any:
81157
"""Parse a bitstring into a value."""
82158
b = bitstring.Bits._create_from_bitstype(b)
83-
return self.get_fn(bitstring.Bits(b))
159+
return self._get_fn(bitstring.Bits(b))
84160

85161
def __str__(self) -> str:
86-
hide_length = self.variable_length or len(dtype_register.names[self.name].allowed_lengths) == 1 or self.length is None
87-
length_str = '' if hide_length else str(self.length)
88-
return f"{self.name}{length_str}"
162+
if self._scale is not None:
163+
return self.__repr__()
164+
hide_length = self._variable_length or len(dtype_register.names[self._name].allowed_lengths) == 1 or self._length is None
165+
length_str = '' if hide_length else str(self._length)
166+
return f"{self._name}{length_str}"
89167

90168
def __repr__(self) -> str:
91-
hide_length = self.variable_length or len(dtype_register.names[self.name].allowed_lengths) == 1 or self.length is None
92-
length_str = '' if hide_length else ', ' + str(self.length)
93-
return f"{self.__class__.__name__}('{self.name}'{length_str})"
169+
hide_length = self._variable_length or len(dtype_register.names[self._name].allowed_lengths) == 1 or self._length is None
170+
length_str = '' if hide_length else ', ' + str(self._length)
171+
scale_str = '' if self._scale is None else f', scale={self._scale}'
172+
return f"{self.__class__.__name__}('{self._name}'{length_str}{scale_str})"
94173

95174
def __eq__(self, other: Any) -> bool:
96175
if isinstance(other, Dtype):
97-
return self.name == other.name and self.length == other.length
176+
return self._name == other._name and self._length == other._length
98177
return False
99178

100179

@@ -186,7 +265,7 @@ def read_fn(bs, start):
186265
self.read_fn = read_fn
187266
self.bitlength2chars_fn = bitlength2chars_fn
188267

189-
def get_dtype(self, length: Optional[int] = None) -> Dtype:
268+
def get_dtype(self, length: Optional[int] = None, scale: Union[None, float, int] = None) -> Dtype:
190269
if self.allowed_lengths:
191270
if length is None:
192271
if len(self.allowed_lengths) == 1:
@@ -198,11 +277,11 @@ def get_dtype(self, length: Optional[int] = None) -> Dtype:
198277
else:
199278
raise ValueError(f"A length of {length} was supplied for the '{self.name}' dtype which is not one of its possible lengths (must be one of {self.allowed_lengths}).")
200279
if length is None:
201-
d = Dtype._create(self, None)
280+
d = Dtype._create(self, None, scale)
202281
return d
203282
if self.variable_length:
204283
raise ValueError(f"A length ({length}) shouldn't be supplied for the variable length dtype '{self.name}'.")
205-
d = Dtype._create(self, length)
284+
d = Dtype._create(self, length, scale)
206285
return d
207286

208287
def __repr__(self) -> str:
@@ -240,13 +319,13 @@ def add_dtype_alias(cls, name: str, alias: str):
240319
setattr(bitstring.bitarray_.BitArray, alias, property(fget=definition.get_fn, fset=definition.set_fn, doc=f"An alias for '{name}'. Read and write."))
241320

242321
@classmethod
243-
def get_dtype(cls, name: str, length: Optional[int]) -> Dtype:
322+
def get_dtype(cls, name: str, length: Optional[int], scale: Union[None, float, int] = None) -> Dtype:
244323
try:
245324
definition = cls.names[name]
246325
except KeyError:
247326
raise ValueError(f"Unknown Dtype name '{name}'.")
248327
else:
249-
return definition.get_dtype(length)
328+
return definition.get_dtype(length, scale)
250329

251330
@classmethod
252331
def __getitem__(cls, name: str) -> DtypeDefinition:

bitstring/scaled_array.py

Lines changed: 0 additions & 77 deletions
This file was deleted.

0 commit comments

Comments
 (0)