From 37782f4b8b86dba83ee03ceed81b76b63ebdc660 Mon Sep 17 00:00:00 2001 From: S Groesz Date: Sat, 10 Oct 2020 05:42:18 +0000 Subject: [PATCH] add Bit object --- bits/__init__.py | 2 +- bits/main.py | 133 ++++++++++++++++++++++++++++++++++++---------- tests/context.py | 1 + tests/test_bit.py | 82 ++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+), 28 deletions(-) create mode 100644 tests/test_bit.py diff --git a/bits/__init__.py b/bits/__init__.py index 0d58038..d936eba 100644 --- a/bits/__init__.py +++ b/bits/__init__.py @@ -1 +1 @@ -from .main import Bits, Bytes +from .main import Bit, Bits, Bytes diff --git a/bits/main.py b/bits/main.py index 0630708..9f6e92d 100644 --- a/bits/main.py +++ b/bits/main.py @@ -1,8 +1,80 @@ -class Bits(int): +class Bit: + """ + Provide a flexible bit representation + """ - def __init__(self, byte): - self.__setvalue(byte) + def __init__(self, var): + if not isinstance(var, (bool, int, str, Bit)): + raise TypeError("Bit must be one of type (int, bool, str)") + if isinstance(var, (int, Bit)): + if var < 0 or var > 1: + raise ValueError("Bit must be 0 or 1") + self.__bit = bool(var) + elif isinstance(var, str): + if var == "0" or var == "1": + self.__bit = bool(int(var)) + else: + raise ValueError('Bit must be "0" or "1"') + elif isinstance(var, bool): + self.__bit = var + + def __str__(self): + return str(int(self.__bit)) + + def __int__(self): + return int(self.__bit) + + def __bool__(self): + return self.__bit + + def __repr__(self): + return f'{self.__class__.__name__}({self.__bit!r})' + + def __eq__(self, compare): + # Massage compare to bool + return bool(self) == bool(Bit(compare)) + + def __ne__(self, compare): + return bool(self) != bool(Bit(compare)) + + def __lt__(self, compare): + return bool(self) < bool(Bit(compare)) + + def __le__(self, compare): + return bool(self) <= bool(Bit(compare)) + + def __gt__(self, compare): + return bool(self) > bool(Bit(compare)) + + def __ge__(self, compare): + return bool(self) >= bool(Bit(compare)) + + def __get__(self, instance, owner): + return self.__bit + + def __hash__(self): + return hash(self.__bit) + + def toggle(self): + self.__bit = not self.__bit + + @property + def lie(self): + """ + Return the opposite of bit, without changing the bit state + """ + return not self.__bit + + +class Bits: + """ + Provide bit operation helpers. + """ + + def __init__(self, var=0): + self.__value = 0 + self.__setvalue(var) def __bytes__(self): return bytes([self.__value]) @@ -21,6 +93,9 @@ class Bits(int): def __str__(self): return self.bin() + def __ord__(self): + return int(self) + def __index__(self): return int(self) @@ -46,49 +121,46 @@ class Bits(int): return int(self) >= int(Bits(compare)) def __getitem__(self, index): - return bool(int(self.bin()[index])) #self.bool(key) + return self.list()[index] def __setitem__(self, index, value): # we'll take the easy way for now - s = list(self.bin()) + l = self.list() # str->int->bool->int : accept bool, str, int, return either "0" or "1" - s[index] = str(int(bool(int(value)))) - self.__setvalue("".join(s)) + l[index] = bool(int(value)) + self.__setvalue(l) def __get__(self, instance, owner): return self.bin() - def __hash__(self): - return None - - def __setvalue(self, value): + def __setvalue(self, var): self.__value = 0 - if isinstance(value, int): - if value < 0 or value > 255: + if isinstance(var, int): + if var < 0 or var > 255: raise ValueError("Integer must be between 0 and 255") - self.__value = value - elif isinstance(value, bytes): - val = 0 - for _byte in value: - val += _byte - if val > 255 or val < 0: + self.__value = var + elif isinstance(var, bytes): + for _byte in var: + self.__value += _byte + if ret > 255 or ret < 0: raise ValueError("Sum value of bytes must be between 0" " and 255") - self.__value = val - elif (len(value) > 0) and (len(value) <=8): - # TODO: apparently, can't use a list when inheriting from 'int' - for bit in range(0, len(value)): - self.__value += (int(bool(int(value[bit]))) * - (2 ** (len(value) - 1 - bit))) + elif (len(var) > 0) and (len(var) <=8): + for bit in range(0, len(var)): + self.__value += (int(bool(int(var[bit]))) * + (2 ** (len(var) - 1 - bit))) else: raise TypeError("Expected object with len <= 8") - def bin(self, pad=True): + def bin(self, pad=True, reverse=False): bitcount = self.__value.bit_length() ret = "" if pad and ((bitcount % 8) > 0): ret = "0" * (8 - (bitcount % 8)) - return ret + bin(self.__value)[2:] + ret += bin(self.__value)[2:] + if reverse: + ret = ret[::-1] + return ret @property def chr(self): @@ -113,6 +185,13 @@ class Bits(int): def nibble(self): return [self.bin()[:4], self.bin()[4:]] + def list(self, pad=True, reverse=False): + ret = [] + bits = self.bin(pad=pad, reverse=reverse) + for bit in bits: + ret.append(bool(int(bit))) + return ret + class Bytes(bytearray): diff --git a/tests/context.py b/tests/context.py index c25a4b8..aff6baa 100644 --- a/tests/context.py +++ b/tests/context.py @@ -3,6 +3,7 @@ import sys sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +from bits import Bit from bits import Bits from bits import Bytes diff --git a/tests/test_bit.py b/tests/test_bit.py new file mode 100644 index 0000000..9bee233 --- /dev/null +++ b/tests/test_bit.py @@ -0,0 +1,82 @@ +from unittest import TestCase + +from .context import Bit + +class TestBit(TestCase): + def setUp(self): + self.true = ["1", 1, True] + self.false = ["0", 0, False] + pass + + def test_init(self): + """ + Test creation of bit object + """ + for t in self.true: + self.assertTrue(Bit(t), f"Bit({t}) == True") + for f in self.false: + self.assertFalse(Bit(f), f"Bit({f}) == False") + + def test_conversion(self): + """ + Test the conversion methods + """ + self.assertEqual(str(Bit(True)), "1", f'str(Bit(True)) == "1"') + self.assertEqual(str(Bit(False)), "0", f'str(Bit(False)) == "0"') + self.assertEqual(int(Bit(True)), 1, f'int(Bit(True)) == 1') + self.assertEqual(int(Bit(False)), 0, f'int(Bit(False)) == 0') + self.assertEqual(bool(Bit(True)), True, f'bool(Bit(True)) == True') + self.assertEqual(bool(Bit(False)), False, f'bool(Bit(False)) == False') + + def test_comparison(self): + """ + Test the comparison operators + """ + self.assertNotEqual(Bit(False), Bit(True), 'Bit(False) != Bit(True)') + self.assertNotEqual(Bit(True), Bit(False), 'Bit(True) != Bit(False)') + self.assertLess(Bit(False), Bit(True), 'Bit(False) < Bit(True)') + self.assertGreater(Bit(True), Bit(False), 'Bit(True) > Bit(False)') + self.assertLessEqual(Bit(False), Bit(True), 'Bit(False) <= Bit(True)') + self.assertLessEqual(Bit(True), Bit(True), 'Bit(True) <= Bit(True)') + self.assertLessEqual(Bit(False), Bit(False), + 'Bit(False) <= Bit(False)') + self.assertGreaterEqual(Bit(True), Bit(False), + 'Bit(True) >= Bit(False)') + self.assertGreaterEqual(Bit(True), Bit(True), + 'Bit(True) >= Bit(True)') + self.assertGreaterEqual(Bit(False), Bit(False), + 'Bit(False) >= Bit(False)') + + def test_lie(self): + """ + Test the lie function/property + """ + self.assertFalse(Bit(True).lie, "Bit(True).lie == False") + self.assertTrue(Bit(False).lie, "Bit(False).lie == True") + + def test_toggle(self): + """ + Test the toggle function + """ + var = Bit(True) + var.toggle() + self.assertFalse(var, "Bit(True).toggle() == False") + var.toggle() + self.assertTrue(var, "Bit(False).toggle() == True") + + def test_errors(self): + """ + Test that errors are raised for invalid values + """ + with self.subTest("Raise TypeError"): + self.assertRaises(TypeError, Bit, 12.7) + self.assertRaises(TypeError, Bit, b'xyz') + self.assertRaises(TypeError, Bit, [True, False]) + self.assertRaises(TypeError, Bit, [True]) + + with self.subTest("Raise ValueError"): + self.assertRaises(ValueError, Bit, "2") + self.assertRaises(ValueError, Bit, 2) + self.assertRaises(ValueError, Bit, -1) + self.assertRaises(ValueError, Bit, "-2") +