Repository: barrust/pyprobables

Issue:
frombytes support
Support having objects loaded directly from bytes to remove the requirement of having to store on disk in some situations.

See this [comment](https://github.com/barrust/pyprobables/commit/9ccbaa25399602705c023d1dd11bf7fda5dd8be8#r62919567) 

@KOLANICH 

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/barrust/pyprobables/pull/89?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus) Report
> Merging [#89](https://codecov.io/gh/barrust/pyprobables/pull/89?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus) (9ffdb9b) into [master](https://codecov.io/gh/barrust/pyprobables/commit/6d0625802b40692be49be88d6b98f4b36b5ea9a2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus) (6d06258) will **increase** coverage by `0.15%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/barrust/pyprobables/pull/89/graphs/tree.svg?width=650&height=150&src=pr&token=SGjJODewBP&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus)](https://codecov.io/gh/barrust/pyprobables/pull/89?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus)

```diff
@@            Coverage Diff             @@
##           master      #89      +/-   ##
==========================================
+ Coverage   99.22%   99.38%   +0.15%     
==========================================
  Files          15       15              
  Lines        1556     1634      +78     
==========================================
+ Hits         1544     1624      +80     
+ Misses         12       10       -2     
```


| [Impacted Files](https://codecov.io/gh/barrust/pyprobables/pull/89?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus) | Coverage Δ | |
|---|---|---|
| [probables/blooms/basebloom.py](https://codecov.io/gh/barrust/pyprobables/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus#diff-cHJvYmFibGVzL2Jsb29tcy9iYXNlYmxvb20ucHk=) | `100.00% <100.00%> (ø)` | |
| [probables/blooms/bloom.py](https://codecov.io/gh/barrust/pyprobables/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus#diff-cHJvYmFibGVzL2Jsb29tcy9ibG9vbS5weQ==) | `100.00% <100.00%> (ø)` | |
| [probables/blooms/countingbloom.py](https://codecov.io/gh/barrust/pyprobables/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus#diff-cHJvYmFibGVzL2Jsb29tcy9jb3VudGluZ2Jsb29tLnB5) | `99.23% <100.00%> (+0.05%)` | :arrow_up: |
| [probables/blooms/expandingbloom.py](https://codecov.io/gh/barrust/pyprobables/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus#diff-cHJvYmFibGVzL2Jsb29tcy9leHBhbmRpbmdibG9vbS5weQ==) | `100.00% <100.00%> (ø)` | |
| [probables/countminsketch/countminsketch.py](https://codecov.io/gh/barrust/pyprobables/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus#diff-cHJvYmFibGVzL2NvdW50bWluc2tldGNoL2NvdW50bWluc2tldGNoLnB5) | `100.00% <100.00%> (ø)` | |
| [probables/cuckoo/countingcuckoo.py](https://codecov.io/gh/barrust/pyprobables/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus#diff-cHJvYmFibGVzL2N1Y2tvby9jb3VudGluZ2N1Y2tvby5weQ==) | `98.50% <100.00%> (+0.12%)` | :arrow_up: |
| [probables/cuckoo/cuckoo.py](https://codecov.io/gh/barrust/pyprobables/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus#diff-cHJvYmFibGVzL2N1Y2tvby9jdWNrb28ucHk=) | `98.44% <100.00%> (+0.06%)` | :arrow_up: |
| [probables/utilities.py](https://codecov.io/gh/barrust/pyprobables/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tyler+Barrus#diff-cHJvYmFibGVzL3V0aWxpdGllcy5weQ==) | `100.00% <100.00%> (+3.92%)` | :arrow_up: |

KOLANICH: ```patch
From a909d3024500a45683f81c0cd0053176937e782f Mon Sep 17 00:00:00 2001
From: KOLANICH <KOLANICH@users.noreply.github.com>
Date: Fri, 7 Jan 2022 13:15:43 +0300
Subject: [PATCH] Renamed `filepath` into `blob` for cuckoo filter.

---
 probables/cuckoo/cuckoo.py | 32 +++++++++++++++++++++++---------
 tests/cuckoo_test.py       |  6 +++---
 2 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/probables/cuckoo/cuckoo.py b/probables/cuckoo/cuckoo.py
index 057fca2..09b0be4 100644
--- a/probables/cuckoo/cuckoo.py
+++ b/probables/cuckoo/cuckoo.py
@@ -13,6 +13,7 @@ from numbers import Number
 from pathlib import Path
 from struct import Struct
 from typing import List, Tuple, Union
+from warnings import warn
 
 from ..exceptions import CuckooFilterFullError, InitializationError
 from ..hashes import KeyT, SimpleHashT, fnv_1a
@@ -58,8 +59,10 @@ class CuckooFilter(object):
         expansion_rate: int = 2,
         auto_expand: bool = True,
         finger_size: int = 4,
-        filepath: Union[str, Path, None] = None,
+        blob: Union[Path, str, IOBase, mmap, ByteString] = None,
         hash_function: Union[SimpleHashT, None] = None,
+        *,
+        filepath: Union[Path, str, IOBase, mmap, ByteString] = None,
     ):
         """setup the data structure"""
         valid_prms = (
@@ -73,6 +76,11 @@ class CuckooFilter(object):
         if not valid_prms:
             msg = "CuckooFilter: capacity, bucket_size, and max_swaps must be an integer greater than 0"
             raise InitializationError(msg)
+
+        if filepath is not None:
+            warn("`filepath` argument is renamed into `blob` because now it accepts `bytes`, `mmap`s `bytearray`s!", DeprecationWarning)
+            blob = filepath
+
         self._bucket_size = int(bucket_size)
         self._cuckoo_capacity = int(capacity)
         self.__max_cuckoo_swaps = int(max_swaps)
@@ -88,12 +96,12 @@ class CuckooFilter(object):
         else:
             self.__hash_func = hash_function  # type: ignore
         self._inserted_elements = 0
-        if filepath is None:
+        if blob is None:
             self._buckets = list()  # type: ignore
             for _ in range(self.capacity):
                 self.buckets.append(list())
-        elif is_valid_file(filepath):
-            self._load(filepath)
+        elif isinstance(blob, (mmap, ByteString)) or is_valid_file(blob):
+            self._load(blob)
         else:
             msg = "CuckooFilter: failed to load provided file"
             raise InitializationError(msg)
@@ -137,19 +145,25 @@ class CuckooFilter(object):
 
     @classmethod
     def load_error_rate(
-        cls, error_rate: float, filepath: Union[str, Path], hash_function: Union[SimpleHashT, None] = None
+        cls, error_rate: float, blob: Union[str, Path], hash_function: Union[SimpleHashT, None] = None,
+        *,
+        filepath: Union[str, Path] = None
     ):
         """Initialize a previously exported Cuckoo Filter based on error rate
 
         Args:
             error_rate (float):
-            filepath (str): The path to the file to load or None if no file
+            blob (str): The path to the file to load or None if no file
             hash_function (function): Hashing strategy function to use \
             `hf(key)`
         Returns:
             CuckooFilter: A Cuckoo Filter object
         """
-        cku = CuckooFilter(filepath=filepath, hash_function=hash_function)
+        if filepath is not None:
+            warn("`filepath` argument is renamed into `blob` because now it accepts `bytes`, `mmap`s `bytearray`s!", DeprecationWarning)
+            blob = filepath
+
+        cku = CuckooFilter(blob=blob, hash_function=hash_function)
         cku._set_error_rate(error_rate)
         return cku
 
@@ -406,9 +420,9 @@ class CuckooFilter(object):
         # if we got here we have an error... we might need to know what is left
         return fingerprint
 
-    def _load(self, file: Union[Path, str, IOBase, mmap, bytes]) -> None:
+    def _load(self, file: Union[Path, str, IOBase, mmap, ByteString]) -> None:
         """load a cuckoo filter from file"""
-        if not isinstance(file, (IOBase, mmap, bytes)):
+        if not isinstance(file, (IOBase, mmap, ByteString)):
             file = Path(file)
             with MMap(file) as filepointer:
                 self._load(filepointer)
diff --git a/tests/cuckoo_test.py b/tests/cuckoo_test.py
index be47cca..902fb60 100644
--- a/tests/cuckoo_test.py
+++ b/tests/cuckoo_test.py
@@ -290,7 +290,7 @@ class TestCuckooFilter(unittest.TestCase):
             md5_out = calc_file_md5(fobj.name)
             self.assertEqual(md5sum, md5_out)
 
-            ckf = CuckooFilter(filepath=fobj.name)
+            ckf = CuckooFilter(blob=fobj.name)
             for i in range(1000):
                 self.assertTrue(ckf.check(str(i)))
 
@@ -304,7 +304,7 @@ class TestCuckooFilter(unittest.TestCase):
 
         def runner():
             """runner"""
-            CuckooFilter(filepath="./test.cko")
+            CuckooFilter(blob="./test.cko")
 
         self.assertRaises(InitializationError, runner)
         try:
@@ -508,7 +508,7 @@ class TestCuckooFilterErrorRate(unittest.TestCase):
             md5_out = calc_file_md5(fobj.name)
             self.assertEqual(md5sum, md5_out)
 
-            ckf = CuckooFilter.load_error_rate(error_rate=0.00001, filepath=fobj.name)
+            ckf = CuckooFilter.load_error_rate(error_rate=0.00001, blob=fobj.name)
             for i in range(1000):
                 self.assertTrue(ckf.check(str(i)))
 
-- 
2.32.0
```
barrust: As for the other diff (filename->blob); I don't mind the concept of that change but would like to tackle that across the board for all the data structures so that they all behave similarly. If you'd like to put an issue in, that would be great. 

Patch:
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 0000000..c8c57a6
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,378 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Use multiple processes to speed up Pylint.
+jobs=1
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code
+extension-pkg-whitelist=
+
+# Allow optimization of some AST trees. This will activate a peephole AST
+# optimizer, which will apply various small optimizations. For instance, it can
+# be used to obtain the result of joining multiple strings with the addition
+# operator. Joining a lot of strings can lead to a maximum recursion error in
+# Pylint and this flag can prevent that. It has one side effect, the resulting
+# AST will be different than the one from reality.
+optimize-ast=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
+confidence=
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time. See also the "--disable" option for examples.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=parameter-unpacking,raw_input-builtin,useless-suppression,unpacking-in-except,dict-iter-method,getslice-method,long-builtin,delslice-method,map-builtin-not-iterating,coerce-method,coerce-builtin,execfile-builtin,round-builtin,old-octal-literal,using-cmp-argument,dict-view-method,cmp-method,next-method-called,indexing-exception,unicode-builtin,oct-method,old-raise-syntax,old-division,xrange-builtin,print-statement,cmp-builtin,old-ne-operator,nonzero-method,metaclass-assignment,import-star-module-level,backtick,unichr-builtin,intern-builtin,reload-builtin,apply-builtin,file-builtin,basestring-builtin,raising-string,long-suffix,zip-builtin-not-iterating,reduce-builtin,hex-method,no-absolute-import,standarderror-builtin,range-builtin-not-iterating,setslice-method,suppressed-message,input-builtin,buffer-builtin,filter-builtin-not-iterating,bad-continuation
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html. You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=yes
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=120
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+# List of optional constructs for which whitespace checking is disabled. `dict-
+# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
+# `empty-line` allows space-only lines.
+no-space-check=trailing-comma,dict-separator
+
+# Maximum number of lines in a module
+max-module-lines=1250
+
+# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
+
+# Number of spaces of indent required inside a hanging  or continued line.
+indent-after-paren=4
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+
+[BASIC]
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Include a hint for the correct naming format with invalid-name
+include-naming-hint=no
+
+# Regular expression matching correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Naming hint for class names
+class-name-hint=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression matching correct inline iteration names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Naming hint for inline iteration names
+inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
+
+# Regular expression matching correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Naming hint for module names
+module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression matching correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for argument names
+argument-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for method names
+method-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for variable names
+variable-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct constant names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Naming hint for constant names
+const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression matching correct attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for attribute names
+attr-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for function names
+function-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct class attribute names
+class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Naming hint for class attribute names
+class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+
+[ELIF]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis. It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set). This supports can work
+# with qualified names.
+ignored-classes=
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the name of dummy variables (i.e. expectedly
+# not used).
+dummy-variables-rgx=_$|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,_cb
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[SPELLING]
+
+# Spelling dictionary name. Available dictionaries: none. To make it working
+# install python-enchant package.
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to indicated private dictionary in
+# --spelling-private-dict-file option instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=optparse
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=12
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branches=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of boolean expressions in a if statement
+max-bool-expr=5
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,_fields,_replace,_source,_make
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d2806f..41be9e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
 # PyProbables Changelog
 
 ### Future Version
+* All Probablistic Data Structures:
+    * Added ability to load each `frombytes()`
 * Cuckoo Filters:
     * Added `fingerprint_size_bits` property
     * Added `error_rate` property
diff --git a/probables/blooms/basebloom.py b/probables/blooms/basebloom.py
index 0344dc8..c603631 100644
--- a/probables/blooms/basebloom.py
+++ b/probables/blooms/basebloom.py
@@ -3,15 +3,15 @@
     Author: Tyler Barrus (barrust@gmail.com)
 """
 
+import array
 import math
-import os
 from binascii import hexlify, unhexlify
 from collections.abc import ByteString
 from io import BytesIO, IOBase
 from mmap import mmap
 from numbers import Number
 from pathlib import Path
-from struct import Struct, calcsize, pack, unpack
+from struct import Struct
 from textwrap import wrap
 from typing import List, Tuple, Union
 
@@ -35,6 +35,7 @@ class BaseBloom(object):
         "__impt_type",
         "__blm_type",
         "__bloom_length",
+        "__impt_struct",
     ]
 
     def __init__(
@@ -60,6 +61,7 @@ class BaseBloom(object):
             self.__impt_type = "B"
         else:
             self.__impt_type = "I"
+        self.__impt_struct = Struct(self.__impt_type)
 
         if blm_type in ["regular", "reg-ondisk", "expanding"]:
             msg = "Insufecient parameters to set up the Bloom Filter"
@@ -73,11 +75,13 @@ class BaseBloom(object):
             assert hex_string is not None
             self._load_hex(hex_string, hash_function)
         elif est_elements is not None and false_positive_rate is not None:
-            vals = self._set_optimized_params(est_elements, float(false_positive_rate), hash_function)
-            self.__hash_func = vals[0]  # type: ignore
-            self.__fpr = vals[1]
-            self.__number_hashes = vals[2]
-            self.__num_bits = vals[3]
+            h_func, fpr, n_hashes, n_bits = self._set_optimized_params(
+                est_elements, float(false_positive_rate), hash_function
+            )
+            self.__hash_func = h_func  # type: ignore
+            self.__fpr = fpr
+            self.__number_hashes = n_hashes
+            self.__num_bits = n_bits
             if blm_type in ["regular", "reg-ondisk"]:
                 self.__bloom_length = int(math.ceil(self.__num_bits / 8.0))
             else:
@@ -185,9 +189,9 @@ class BaseBloom(object):
         tmp = depth if depth is not None else self.number_hashes
         return self.__hash_func(key, tmp)
 
-    @staticmethod
+    @classmethod
     def _set_optimized_params(
-        estimated_elements: int, false_positive_rate: float, hash_function: Union[HashFuncT, None]
+        cls, estimated_elements: int, false_positive_rate: float, hash_function: Union[HashFuncT, None]
     ) -> Tuple[HashFuncT, float, int, int]:
         """set the parameters to the optimal sizes"""
         tmp_hash = hash_function
@@ -202,9 +206,8 @@ class BaseBloom(object):
         if not valid_prms:
             msg = "Bloom: false positive rate must be between 0.0 and 1.0"
             raise InitializationError(msg)
-
-        fpr = pack("f", float(false_positive_rate))
-        t_fpr = unpack("f", fpr)[0]  # to mimic the c version!
+        fpr = cls.__FPR_STRUCT.pack(float(false_positive_rate))
+        t_fpr = float(cls.__FPR_STRUCT.unpack(fpr)[0])  # to mimic the c version!
         # optimal caluclations
         n_els = estimated_elements
         m_bt = math.ceil((-n_els * math.log(t_fpr)) / 0.4804530139182)  # ln(2)^2
@@ -219,9 +222,10 @@ class BaseBloom(object):
 
         return tmp_hash, t_fpr, number_hashes, int(m_bt)
 
-    HEADER_STRUCT_FORMAT = "QQf"
-    HEADER_STRUCT = Struct(HEADER_STRUCT_FORMAT)
-    HEADER_STRUCT_BE = Struct(">" + HEADER_STRUCT_FORMAT)
+    __HEADER_STRUCT_FORMAT = "QQf"
+    __HEADER_STRUCT = Struct(__HEADER_STRUCT_FORMAT)
+    __HEADER_STRUCT_BE = Struct(">" + __HEADER_STRUCT_FORMAT)
+    __FPR_STRUCT = Struct("f")
 
     def __load(
         self,
@@ -230,62 +234,69 @@ class BaseBloom(object):
         hash_function: Union[HashFuncT, None] = None,
     ) -> None:
         """load the Bloom Filter from file"""
-        # read in the needed information, and then call _set_optimized_params
-        # to set everything correctly
         if not isinstance(file, (IOBase, mmap)):
             file = Path(file)
             with MMap(file) as filepointer:
                 self.__load(blm_type, filepointer, hash_function)
         else:
-            offset = self.__class__.HEADER_STRUCT.size
-            file.seek(offset * -1, os.SEEK_END)
-            fpr = self._parse_footer(self.__class__.HEADER_STRUCT, file.read(offset))
-            vals = self._set_optimized_params(self.__est_elements, fpr, hash_function)
-            self.__hash_func = vals[0]  # type: ignore
-            self.__fpr = vals[1]
-            self.__number_hashes = vals[2]
-            self.__num_bits = vals[3]
-            if blm_type in ["regular", "reg-ondisk"]:
-                self.__bloom_length = int(math.ceil(self.__num_bits / 8.0))
-            else:
-                self.__bloom_length = self.number_bits
+            offset = self.__HEADER_STRUCT.size
+            self._parse_footer_set(self.__HEADER_STRUCT, file[-offset:], hash_function)  # type: ignore
+            self._set_bloom_length()
             # now read in the bit array!
-            file.seek(0, os.SEEK_SET)
-            offset = calcsize(self.__impt_type) * self.bloom_length
-            rep = self.__impt_type * self.bloom_length
-            self._bloom = list(unpack(rep, file.read(offset)))
-
-    def _parse_footer(self, stct: Struct, d: ByteString) -> float:
-        tmp_data = stct.unpack_from(bytearray(d))
-        self.__est_elements = tmp_data[0]
-        self._els_added = tmp_data[1]
-        fpr = float(tmp_data[2])
-        return fpr
-
-    def _load_hex(self, hex_string: str, hash_function: Union[HashFuncT, None] = None) -> None:
-        """placeholder for loading from hex string"""
-        offset = self.__class__.HEADER_STRUCT_BE.size * 2
-        fpr = self._parse_footer(self.__class__.HEADER_STRUCT_BE, unhexlify(hex_string[-offset:]))
-        vals = self._set_optimized_params(self.__est_elements, fpr, hash_function)
-        self.__hash_func = vals[0]  # type: ignore
-        self.__fpr = vals[1]
-        self.__number_hashes = vals[2]
-        self.__num_bits = vals[3]
+            self._parse_bloom_array(file)  # type: ignore
+
+    @classmethod
+    def _parse_footer(
+        cls, stct: Struct, d: ByteString, hash_function: Union[HashFuncT, None] = None
+    ) -> Tuple[int, int, float, HashFuncT, int, int]:
+        """parse footer returning the data: estimated elements, elements added,
+        false positive rate, hash function, number hashes, number bits"""
+        e_elms, e_added, fpr = stct.unpack_from(bytearray(d))
+        est_elements = int(e_elms)
+        els_added = int(e_added)
+        fpr = float(fpr)
+        h_func, fpr, n_hashes, n_bits = cls._set_optimized_params(est_elements, fpr, hash_function)
+
+        return est_elements, els_added, float(fpr), h_func, int(n_hashes), int(n_bits)
+
+    def _parse_footer_set(self, stct: Struct, d: ByteString, hash_function: Union[HashFuncT, None] = None) -> None:
+        est_elms, els_added, fpr, hash_func, num_hashes, num_bits = self._parse_footer(stct, d, hash_function)
+        self.__est_elements = est_elms
+        self._els_added = els_added
+        self.__hash_func = hash_func  # type: ignore
+        self.__fpr = fpr
+        self.__number_hashes = num_hashes
+        self.__num_bits = num_bits
+
+    def _parse_bloom_array(self, b: ByteString):
+        offset = self.__impt_struct.size * self.bloom_length
+        a = array.ArrayType(self.__impt_type)
+        a.frombytes(bytes(b[:offset]))
+        self._bloom = a.tolist()
+
+    def _set_bloom_length(self) -> None:
+        """House setting the bloom length based on the bloom filter itself"""
         if self.__blm_type in ["regular", "reg-ondisk"]:
             self.__bloom_length = int(math.ceil(self.__num_bits / 8.0))
         else:
             self.__bloom_length = self.number_bits
 
+    def _load_hex(self, hex_string: str, hash_function: Union[HashFuncT, None] = None) -> None:
+        """placeholder for loading from hex string"""
+        offset = self.__HEADER_STRUCT_BE.size * 2
+        self._parse_footer_set(self.__HEADER_STRUCT_BE, unhexlify(hex_string[-offset:]), hash_function)
+        self._set_bloom_length()
         tmp_bloom = unhexlify(hex_string[:-offset])
-        rep = self.__impt_type * self.bloom_length
-        self._bloom = list(unpack(rep, tmp_bloom))
+        a = array.ArrayType(self.__impt_type)
+        a.frombytes(tmp_bloom)
+        self._bloom = a.tolist()
 
     def export_hex(self) -> str:
         """Export the Bloom Filter as a hex string
 
         Return:
             str: Hex representation of the Bloom Filter"""
-        mybytes = self.__class__.HEADER_STRUCT_BE.pack(
+        mybytes = self.__HEADER_STRUCT_BE.pack(
             self.estimated_elements,
             self.elements_added,
             self.false_positive_rate,
@@ -293,10 +304,9 @@ class BaseBloom(object):
         if self.__blm_type in ["regular", "reg-ondisk"]:
             bytes_string = hexlify(bytearray(self.bloom[: self.bloom_length])) + hexlify(mybytes)
         else:
-            bytes_string = b""
-            for val in self.bloom:
-                bytes_string += hexlify(pack(self.__impt_type, val))
-            bytes_string += hexlify(mybytes)
+            a = array.ArrayType(self.__impt_type)
+            a.fromlist(self.bloom)
+            bytes_string = hexlify(a.tobytes()) + hexlify(mybytes)
         return str(bytes_string, "utf-8")
 
     def export(self, file: Union[Path, str, IOBase, mmap]) -> None:
@@ -310,10 +320,11 @@ class BaseBloom(object):
             with open(file, "wb") as filepointer:
                 self.export(filepointer)  # type:ignore
         else:
-            rep = self.__impt_type * self.bloom_length
-            file.write(pack(rep, *self.bloom))
+            a = array.ArrayType(self.__impt_type)
+            a.fromlist((self.bloom))
+            file.write(a.tobytes())
             file.write(
-                self.__class__.HEADER_STRUCT.pack(
+                self.__HEADER_STRUCT.pack(
                     self.estimated_elements,
                     self.elements_added,
                     self.false_positive_rate,
@@ -352,8 +363,7 @@ class BaseBloom(object):
 
         Returns:
             int: Size of the Bloom Filter when exported to disk"""
-        tmp_b = calcsize(self.__impt_type)
-        return (self.bloom_length * tmp_b) + self.__class__.HEADER_STRUCT.size
+        return (self.bloom_length * self.__impt_struct.size) + self.__HEADER_STRUCT.size
 
     def current_false_positive_rate(self) -> float:
         """Calculate the current false positive rate based on elements added
diff --git a/probables/blooms/bloom.py b/probables/blooms/bloom.py
index 450b458..2f3d5ed 100644
--- a/probables/blooms/bloom.py
+++ b/probables/blooms/bloom.py
@@ -3,12 +3,13 @@
     Author: Tyler Barrus (barrust@gmail.com)
     URL: https://github.com/barrust/bloom
 """
-
+import array
 import mmap
 import os
+from collections.abc import ByteString
 from pathlib import Path
 from shutil import copyfile
-from struct import calcsize, pack, unpack
+from struct import Struct
 from typing import Union
 
 from ..exceptions import InitializationError, NotSupportedError
@@ -75,24 +76,23 @@ def _tmp_intersection(first: SimpleBloomT, second: SimpleBloomT) -> "BloomFilter
 
 
 class BloomFilter(BaseBloom):
-    """ Simple Bloom Filter implementation for use in python;
-        It can read and write the same format as the c version
-        (https://github.com/barrust/bloom)
-
-        Args:
-            est_elements (int): The number of estimated elements to be added
-            false_positive_rate (float): The desired false positive rate
-            filepath (str): Path to file to load
-            hex_string (str): Hex based representation to be loaded
-            hash_function (function): Hashing strategy function to use \
-            `hf(key, number)`
-        Returns:
-            BloomFilter: A Bloom Filter object
-        Note:
-            Initialization order of operations:
-                1) From file
-                2) From Hex String
-                3) From params """
+    """Simple Bloom Filter implementation for use in python;
+    It can read and write the same format as the c version
+    (https://github.com/barrust/bloom)
+
+    Args:
+        est_elements (int): The number of estimated elements to be added
+        false_positive_rate (float): The desired false positive rate
+        filepath (str): Path to file to load
+        hex_string (str): Hex based representation to be loaded
+        hash_function (function): Hashing strategy function to use `hf(key, number)`
+    Returns:
+        BloomFilter: A Bloom Filter object
+    Note:
+        Initialization order of operations:
+            1) From file
+            2) From Hex String
+            3) From params"""
 
     __slots__ = BaseBloom.__slots__
 
@@ -114,6 +114,27 @@ class BloomFilter(BaseBloom):
             hash_function,
         )
 
+    __HEADER_STRUCT_FORMAT = "QQf"
+    __HEADER_STRUCT = Struct(__HEADER_STRUCT_FORMAT)
+    # __HEADER_STRUCT_BE = Struct(">" + __HEADER_STRUCT_FORMAT)
+
+    @classmethod
+    def frombytes(cls, b: ByteString, hash_function: Union[HashFuncT, None] = None) -> "BloomFilter":
+        """
+        Args:
+            b (ByteString): The bytes to load as a Bloom Filter
+            hash_function (function): Hashing strategy function to use `hf(key, number)`
+        Returns:
+            BloomFilter: A Bloom Filter object
+        """
+        offset = cls.__HEADER_STRUCT.size
+        est_elms, _, fpr, _, _, _ = cls._parse_footer(cls.__HEADER_STRUCT, bytes(b[-offset:]))
+        blm = BloomFilter(est_elements=est_elms, false_positive_rate=fpr, hash_function=hash_function)
+        blm._parse_footer_set(cls.__HEADER_STRUCT, bytes(b[-offset:]))
+        blm._set_bloom_length()
+        blm._parse_bloom_array(b)
+        return blm
+
     def __str__(self) -> str:
         """output statistics of the bloom filter"""
         on_disk = "no" if self.is_on_disk is False else "yes"
@@ -248,7 +269,7 @@ class BloomFilterOnDisk(BaseBloom):
                 2) From Hex String
                 3) Only filepath provided """
 
-    __slots__ = ["__file_pointer", "__filename", "__export_offset"]
+    __slots__ = ["__file_pointer", "__filename"]
 
     def __init__(
         self,
@@ -273,24 +294,24 @@ class BloomFilterOnDisk(BaseBloom):
 
         self.__file_pointer = None
         self.__filename = Path(filepath)
-        self.__export_offset = calcsize("Qf")
         self._on_disk = True
 
         if est_elements is not None and false_positive_rate is not None:
             # no need to check the file since this will over write it
             fpr = false_positive_rate
-            vals = super(BloomFilterOnDisk, self)._set_optimized_params(est_elements, fpr, hash_function)
+            h_func, fpr, _, _ = self._set_optimized_params(est_elements, fpr, hash_function)
             super(BloomFilterOnDisk, self).__init__(
                 "reg-ondisk",
                 est_elements=est_elements,
-                false_positive_rate=vals[1],
-                hash_function=vals[0],
+                false_positive_rate=fpr,
+                hash_function=h_func,
             )
             # do the on disk things
             with open(filepath, "wb") as filepointer:
-                for _ in range(self.bloom_length):
-                    filepointer.write(pack("B", int(0)))
-                filepointer.write(pack("QQf", est_elements, 0, false_positive_rate))
+                a = array.ArrayType("B")
+                a.extend([0 for _ in range(self.bloom_length)])
+                a.tofile(filepointer)
+                filepointer.write(self.CNT_FOOTER_STUCT.pack(est_elements, 0, false_positive_rate))
                 filepointer.flush()
             self.__load(filepath, hash_function)
         elif is_hex_string(hex_string):
@@ -301,6 +322,19 @@ class BloomFilterOnDisk(BaseBloom):
             msg = "Insufecient parameters to set up the On Disk Bloom Filter"
             raise InitializationError(msg)
 
+    CNT_ELM_STRUCT = Struct("B")
+    CNT_EXP_ELM_STRUCT = Struct("Q")
+    CNT_FOOTER_STUCT = Struct("QQf")
+    CNT_EXPORT_OFFSET = Struct("Qf")
+
+    @classmethod
+    def frombytes(cls, b: ByteString, hash_function: Union[HashFuncT, None] = None) -> "BloomFilter":
+        """
+        Raises: NotSupportedError
+        """
+        msg = "Loading from bytes is currently not supported by the on disk Bloom Filter"
+        raise NotSupportedError(msg)
+
     def __del__(self) -> None:
         """handle if user doesn't close the on disk Bloom Filter"""
         self.close()
@@ -312,7 +346,7 @@ class BloomFilterOnDisk(BaseBloom):
         """Clean up the BloomFilterOnDisk object"""
         if self.__file_pointer is not None:
             self.__update()
-            self._bloom.close()  # close the mmap
+            self._bloom.close()  # type: ignore
             self.__file_pointer.close()
             self.__file_pointer = None
 
@@ -321,15 +355,15 @@ class BloomFilterOnDisk(BaseBloom):
         # read the file, set the optimal params
         # mmap everything
         with open(filepath, "r+b") as filepointer:
-            offset = calcsize("QQf")
+            offset = self.CNT_FOOTER_STUCT.size
             filepointer.seek(offset * -1, os.SEEK_END)
-            mybytes = unpack("QQf", filepointer.read(offset))
-            vals = super(BloomFilterOnDisk, self)._set_optimized_params(mybytes[0], mybytes[2], hash_function)
+            est_els, _, fpr = self.CNT_FOOTER_STUCT.unpack_from(filepointer.read(offset))
+            hash_func, fpr, _, _ = self.__class__._set_optimized_params(est_els, fpr, hash_function)
         super(BloomFilterOnDisk, self).__init__(
             "reg-ondisk",
-            est_elements=mybytes[0],
-            false_positive_rate=vals[1],
-            hash_function=vals[0],
+            est_elements=est_els,
+            false_positive_rate=fpr,
+            hash_function=hash_func,
         )
         self.__file_pointer = open(filepath, "r+b")  # type: ignore
         self._bloom = mmap.mmap(self.__file_pointer.fileno(), 0)  # type: ignore
@@ -435,14 +469,14 @@ class BloomFilterOnDisk(BaseBloom):
 
     def _get_element(self, idx: int) -> int:
         """wrappper to use similar functions always!"""
-        return unpack("B", bytes([self._bloom[idx]]))[0]  # type: ignore
+        return int(self.CNT_ELM_STRUCT.unpack(bytes([self._bloom[idx]]))[0])
 
     def __update(self):
         """update the on disk Bloom Filter and ensure everything is out
         to disk"""
         self._bloom.flush()
-        self.__file_pointer.seek(-self.__export_offset, os.SEEK_END)
-        self.__file_pointer.write(pack("Q", self.elements_added))
+        self.__file_pointer.seek(-self.CNT_EXPORT_OFFSET.size, os.SEEK_END)
+        self.__file_pointer.write(self.CNT_EXP_ELM_STRUCT.pack(self.elements_added))
         self.__file_pointer.flush()
 
     def _cnt_number_bits_set(self) -> int:
diff --git a/probables/blooms/countingbloom.py b/probables/blooms/countingbloom.py
index f233526..bc61393 100644
--- a/probables/blooms/countingbloom.py
+++ b/probables/blooms/countingbloom.py
@@ -4,7 +4,9 @@
     URL: https://github.com/barrust/counting_bloom
 """
 
+from collections.abc import ByteString
 from pathlib import Path
+from struct import Struct
 from typing import Union
 
 from ..constants import UINT32_T_MAX, UINT64_T_MAX
@@ -62,6 +64,27 @@ class CountingBloomFilter(BaseBloom):
             hash_function=hash_function,
         )
 
+    __HEADER_STRUCT_FORMAT = "QQf"
+    __HEADER_STRUCT = Struct(__HEADER_STRUCT_FORMAT)
+    # __HEADER_STRUCT_BE = Struct(">" + __HEADER_STRUCT_FORMAT)
+
+    @classmethod
+    def frombytes(cls, b: ByteString, hash_function: Union[HashFuncT, None] = None) -> "CountingBloomFilter":
+        """
+        Args:
+            b (ByteString): the bytes to load as a Counting Bloom Filter
+            hash_function (function): Hashing strategy function to use `hf(key, number)`
+        Returns:
+            CountingBloomFilter: A Counting Bloom Filter object
+        """
+        offset = cls.__HEADER_STRUCT.size
+        est_elms, _, fpr, _, _, _ = cls._parse_footer(cls.__HEADER_STRUCT, bytes(b[-offset:]))
+        blm = CountingBloomFilter(est_elements=est_elms, false_positive_rate=fpr, hash_function=hash_function)
+        blm._parse_footer_set(cls.__HEADER_STRUCT, bytes(b[-offset:]))
+        blm._set_bloom_length()
+        blm._parse_bloom_array(b)
+        return blm
+
     def __str__(self) -> str:
         """string representation of the counting bloom filter"""
         on_disk = "no" if self.is_on_disk is False else "yes"
diff --git a/probables/blooms/expandingbloom.py b/probables/blooms/expandingbloom.py
index 80d9278..8d857f6 100644
--- a/probables/blooms/expandingbloom.py
+++ b/probables/blooms/expandingbloom.py
@@ -4,12 +4,13 @@
     URL: https://github.com/barrust/pyprobables
 """
 
-import os
+import array
+from collections.abc import ByteString
 from io import BytesIO, IOBase
 from mmap import mmap
 from pathlib import Path
-from struct import calcsize, pack, unpack
-from typing import Union
+from struct import Struct
+from typing import Tuple, Union
 
 from ..exceptions import RotatingBloomFilterError
 from ..hashes import HashFuncT, HashResultsT, KeyT
@@ -41,7 +42,7 @@ class ExpandingBloomFilter(object):
         "__fpr",
         "__est_elements",
         "__hash_func",
-        "__added_elements",
+        "_added_elements",
     ]
 
     def __init__(
@@ -56,7 +57,7 @@ class ExpandingBloomFilter(object):
         self.__fpr = false_positive_rate
         self.__est_elements = est_elements
         self.__hash_func = hash_function
-        self.__added_elements = 0  # total added...
+        self._added_elements = int(0)  # total added...
 
         if is_valid_file(filepath):
             self.__load(filepath)
@@ -64,6 +65,24 @@ class ExpandingBloomFilter(object):
             # add in the initial bloom filter!
             self.__add_bloom_filter()
 
+    __FOOTER_STRUCT = Struct("QQQf")
+    __S_INT64_STRUCT = Struct("Q")
+
+    @classmethod
+    def frombytes(cls, b: ByteString, hash_function: Union[HashFuncT, None] = None) -> "ExpandingBloomFilter":
+        """
+        Args:
+            b (ByteString): The bytes to load as a Expanding Bloom Filter
+            hash_function (function): Hashing strategy function to use `hf(key, number)`
+        Returns:
+            ExpandingBloomFilter: A Bloom Filter object
+        """
+        size, est_els, added_els, fpr = cls._parse_footer(b)
+        blm = ExpandingBloomFilter(est_elements=est_els, false_positive_rate=fpr, hash_function=hash_function)
+        blm._parse_blooms(b, size)
+        blm._added_elements = added_els
+        return blm
+
     def __contains__(self, key: KeyT) -> bool:
         """setup the `in` functionality"""
         return self.check(key)
@@ -95,7 +114,7 @@ class ExpandingBloomFilter(object):
     @property
     def elements_added(self) -> int:
         """int: The total number of elements added"""
-        return self.__added_elements
+        return self._added_elements
 
     def push(self) -> None:
         """Push a new expansion onto the Bloom Filter"""
@@ -146,7 +165,7 @@ class ExpandingBloomFilter(object):
                               it likely has been inserted before \
                               `False` will only insert if not found in the \
                               Bloom Filter """
-        self.__added_elements += 1
+        self._added_elements += 1
         if force or not self.check_alt(hashes):
             self.__check_for_growth()
             self._blooms[-1].add_alt(hashes)
@@ -177,11 +196,12 @@ class ExpandingBloomFilter(object):
             filepointer = file  # type:ignore
             # add all the different Bloom bit arrays...
             for blm in self._blooms:
-                rep = "Q" + "B" * blm.bloom_length
-                filepointer.write(pack(rep, blm.elements_added, *blm.bloom))
+                filepointer.write(self.__S_INT64_STRUCT.pack(blm.elements_added))
+                a = array.ArrayType("B")
+                a.fromlist(blm.bloom)
+                a.tofile(filepointer)
             filepointer.write(
-                pack(
-                    "QQQf",
+                self.__FOOTER_STRUCT.pack(
                     len(self._blooms),
                     self.estimated_elements,
                     self.elements_added,
@@ -195,29 +215,41 @@ class ExpandingBloomFilter(object):
             with MMap(file) as filepointer:
                 self.__load(filepointer)
         else:
-            offset = calcsize("QQQf")
-            file.seek(offset * -1, os.SEEK_END)
-            size, est_els, els_added, fpr = unpack("QQQf", file.read(offset))
-
-            file.seek(0, os.SEEK_SET)
-            # set the basic defaults
+            size, est_els, els_added, fpr = self._parse_footer(file)  # type: ignore
             self._blooms = list()
-            self.__added_elements = els_added
+            self._added_elements = els_added
             self.__fpr = fpr
             self.__est_elements = est_els
-            for _ in range(size):
-                blm = BloomFilter(
-                    est_elements=self.__est_elements,
-                    false_positive_rate=self.__fpr,
-                    hash_function=self.__hash_func,
-                )
-                # now we need to read in the correct number of bytes...
-                offset = calcsize("Q") + calcsize("B") * blm.bloom_length
-                rep = "Q" + "B" * blm.bloom_length
-                unpacked = list(unpack(rep, file.read(offset)))
-                blm._bloom = unpacked[1:]
-                blm.elements_added = unpacked[0]
-                self._blooms.append(blm)
+            self._parse_blooms(file, size)  # type:ignore
+
+    @classmethod
+    def _parse_footer(cls, b: ByteString) -> Tuple[int, int, int, float]:
+        offset = cls.__FOOTER_STRUCT.size
+        size, est_els, els_added, fpr = cls.__FOOTER_STRUCT.unpack(bytes(b[-offset:]))
+        return int(size), int(est_els), int(els_added), float(fpr)
+
+    def _parse_blooms(self, b: ByteString, size: int) -> None:
+        # reset the bloom list
+        self._blooms = list()
+        c = Struct("B")
+        blm_size = 0
+        start = 0
+        end = 0
+        for _ in range(size):
+            blm = BloomFilter(
+                est_elements=self.__est_elements,
+                false_positive_rate=self.__fpr,
+                hash_function=self.__hash_func,
+            )
+            if blm_size == 0:
+                blm_size = c.size * blm.bloom_length
+            end = start + self.__S_INT64_STRUCT.size + blm_size
+            blm._els_added = int(self.__S_INT64_STRUCT.unpack(bytes(b[start : start + self.__S_INT64_STRUCT.size]))[0])
+            a = array.ArrayType("B")
+            a.frombytes(bytes(b[start + self.__S_INT64_STRUCT.size : end]))
+            blm._bloom = a.tolist()
+            self._blooms.append(blm)
+            start = end
 
 
 class RotatingBloomFilter(ExpandingBloomFilter):
@@ -247,7 +279,7 @@ class RotatingBloomFilter(ExpandingBloomFilter):
         "__fpr",
         "__est_elements",
         "__hash_func",
-        "__added_elements",
+        "_added_elements",
         "_queue_size",
     ]
 
@@ -267,11 +299,32 @@ class RotatingBloomFilter(ExpandingBloomFilter):
             hash_function=hash_function,
         )
         self._queue_size = max_queue_size
-        self.__added_elements = self.elements_added
+        self._added_elements = self.elements_added
         self.__est_elements = self.estimated_elements
         self.__fpr = self.false_positive_rate
         self.__hash_func = hash_function
 
+    @classmethod
+    def frombytes(  # type:ignore
+        cls, b: ByteString, max_queue_size: int, hash_function: Union[HashFuncT, None] = None
+    ) -> "RotatingBloomFilter":
+        """
+        Args:
+            b (ByteString): The bytes to load as a Expanding Bloom Filter
+            max_queue_size (int): This is the number is used to determine the maximum number \
+                of Bloom Filters. Total elements added is based on `max_queue_size * est_elements`
+            hash_function (function): Hashing strategy function to use `hf(key, number)`
+        Returns:
+            RotatingBloomFilter: A Bloom Filter object
+        """
+        size, est_els, added_els, fpr = cls._parse_footer(b)
+        blm = RotatingBloomFilter(
+            est_elements=est_els, false_positive_rate=fpr, max_queue_size=max_queue_size, hash_function=hash_function
+        )
+        blm._parse_blooms(b, size)
+        blm._added_elements = added_els
+        return blm
+
     @property
     def max_queue_size(self) -> int:
         """int: The maximum size for the queue"""
@@ -291,7 +344,7 @@ class RotatingBloomFilter(ExpandingBloomFilter):
                               it likely has been inserted before \
                               `False` will only insert if not found in the \
                               Bloom Filter """
-        self.__added_elements += 1
+        self._added_elements += 1
         if force or not self.check_alt(hashes):
             self.__rotate_bloom_filter()
             self._blooms[-1].add_alt(hashes)
diff --git a/probables/countminsketch/countminsketch.py b/probables/countminsketch/countminsketch.py
index 5b94d72..bccf314 100644
--- a/probables/countminsketch/countminsketch.py
+++ b/probables/countminsketch/countminsketch.py
@@ -4,14 +4,15 @@
     URL: https://github.com/barrust/count-min-sketch
 """
 
+import array
 import math
-import os
+from collections.abc import ByteString
 from io import BytesIO, IOBase
 from mmap import mmap
 from numbers import Number
 from pathlib import Path
-from struct import calcsize, pack, unpack
-from typing import Dict, Union
+from struct import Struct
+from typing import Dict, Tuple, Union
 
 from ..constants import INT32_T_MAX, INT32_T_MIN, INT64_T_MAX, INT64_T_MIN
 from ..exceptions import CountMinSketchError, InitializationError, NotSupportedError
@@ -115,6 +116,9 @@ class CountMinSketch(object):
         else:
             self._hash_function = hash_function  # type: ignore
 
+    __FOOTER_STRUCT = Struct("IIq")
+    __BASIC_BIN_STRUCT = Struct("i")
+
     def __str__(self) -> str:
         """string representation of the count min sketch"""
         msg = (
@@ -143,6 +147,21 @@ class CountMinSketch(object):
             self.export(f)
             return f.getvalue()
 
+    @classmethod
+    def frombytes(cls, b: ByteString, hash_function: Union[HashFuncT, None] = None) -> "CountMinSketch":
+        """
+        Args:
+            b (ByteString): The bytes to load as a Count-Min Sketch
+            hash_function (function): Hashing strategy function to use `hf(key, number)`
+        Returns:
+            CountMinSketch: A count-min sketch object
+        """
+        offset = cls.__FOOTER_STRUCT.size
+        width, depth, _ = cls.__FOOTER_STRUCT.unpack_from(bytes(b[-offset:]))
+        cms = CountMinSketch(width=width, depth=depth, hash_function=hash_function)
+        cms._parse_bytes(b)
+        return cms
+
     @property
     def width(self) -> int:
         """int: The width of the count-min sketch
@@ -334,9 +353,10 @@ class CountMinSketch(object):
                 self.export(filepointer)  # type: ignore
         else:
             # write out the bins
-            rep = "i" * len(self._bins)
-            file.write(pack(rep, *self._bins))
-            file.write(pack("IIq", self.width, self.depth, self.elements_added))
+            a = array.ArrayType("i")
+            a.extend([x for x in self._bins])
+            file.write(a.tobytes())
+            file.write(self.__FOOTER_STRUCT.pack(self.width, self.depth, self.elements_added))
 
     def join(self, second: "CountMinSketch") -> None:
         """ Join two count-min sketchs into a single count-min sketch; the
@@ -388,20 +408,28 @@ class CountMinSketch(object):
             with MMap(file) as filepointer:
                 self.__load(filepointer)
         else:
-            offset = calcsize("IIq")
-            file.seek(offset * -1, os.SEEK_END)
-            mybytes = unpack("IIq", file.read(offset))
-            self.__width = mybytes[0]
-            self.__depth = mybytes[1]
-            self.__elements_added = mybytes[2]
-            self.__confidence = 1 - (1 / math.pow(2, self.depth))
-            self.__error_rate = 2 / self.width
-
-            file.seek(0, os.SEEK_SET)
-            length = self.width * self.depth
-            rep = "i" * length
-            offset = calcsize(rep)
-            self._bins = list(unpack(rep, file.read(offset)))
+            self._parse_bytes(file)  # type: ignore
+
+    @classmethod
+    def _parse_footer(cls, file: ByteString) -> Tuple[int, int, int]:
+        """return width, depth and elements added, in that order"""
+        offset = cls.__FOOTER_STRUCT.size
+        width, depth, elements_added = cls.__FOOTER_STRUCT.unpack_from(bytes(file[-offset:]))
+        return width, depth, elements_added
+
+    def _parse_bytes(self, file: ByteString):
+        """parse bytes or a mapped file to setup the CountMin-Sketch"""
+        width, depth, els_added = self._parse_footer(file)
+        self.__width = width
+        self.__depth = depth
+        self.__elements_added = els_added
+        self.__confidence = 1 - (1 / math.pow(2, self.depth))
+        self.__error_rate = 2 / self.width
+
+        offset = self.__BASIC_BIN_STRUCT.size * self.width * self.depth
+        a = array.ArrayType("i")
+        a.frombytes(bytes(file[:offset]))
+        self._bins = list(a)
 
     def __get_values_sorted(self, hashes: HashResultsT) -> HashResultsT:
         """get the values sorted"""
@@ -566,6 +594,23 @@ class HeavyHitters(CountMinSketch):
         self.__num_hitters = num_hitters
         self.__smallest = 0
 
+    @classmethod
+    def frombytes(  # type: ignore
+        cls, b: ByteString, num_hitters: int = 100, hash_function: Union[HashFuncT, None] = None
+    ) -> "HeavyHitters":
+        """
+        Args:
+            b (ByteString): The bytes to load as a Expanding Bloom Filter
+            num_hitters (int): The maximum number of distinct elements to track
+            hash_function (function): Hashing strategy function to use `hf(key, number)`
+        Returns:
+            HeavyHitters: A Bloom Filter object
+        """
+        width, depth, _ = cls._parse_footer(b)
+        hh = HeavyHitters(width=width, depth=depth, num_hitters=num_hitters, hash_function=hash_function)
+        hh._parse_bytes(b)
+        return hh
+
     def __str__(self) -> str:
         """heavy hitters string rep"""
         msg = super(HeavyHitters, self).__str__()
@@ -670,7 +715,28 @@ class HeavyHitters(CountMinSketch):
 
 
 class StreamThreshold(CountMinSketch):
-    """keep track of those elements over a certain threshold"""
+    """ Find and track those elements that are above a certain threshold
+
+        Args:
+            threshold (int): The threshold above which an element will be tracked
+            width (int): The width of the count-min sketch
+            depth (int): The depth of the count-min sketch
+            confidence (float): The level of confidence desired
+            error_rate (float): The desired error rate
+            filepath (str): Path to file to load
+            hash_function (function): Hashing strategy function to use `hf(key, number)`
+        Returns:
+            StreamThreshold: A Count-Min Sketch object
+        Note:
+            Initialization order of operations:
+                1) From file
+                2) Width and depth
+                3) Confidence and error rate
+        Note:
+            Default query type is `min`
+        Note:
+            For width and depth, width may realistically be in the thousands \
+            while depth is in the single digit to teens  """
 
     __slots__ = ["__threshold", "__meets_threshold"]
 
@@ -688,6 +754,23 @@ class StreamThreshold(CountMinSketch):
         self.__threshold = threshold
         self.__meets_threshold = dict()  # type: ignore
 
+    @classmethod
+    def frombytes(  # type: ignore
+        cls, b: ByteString, threshold: int = 100, hash_function: Union[HashFuncT, None] = None
+    ) -> "StreamThreshold":
+        """
+        Args:
+            b (ByteString): The bytes to load as a Expanding Bloom Filter
+            threshold (int): The threshold above which an element will be tracked
+            hash_function (function): Hashing strategy function to use `hf(key, number)`
+        Returns:
+            StreamThreshold: A Bloom Filter object
+        """
+        width, depth, _ = cls._parse_footer(b)
+        st = StreamThreshold(width=width, depth=depth, threshold=threshold, hash_function=hash_function)
+        st._parse_bytes(b)
+        return st
+
     def __str__(self) -> str:
         """stream threshold string rep"""
         msg = super(StreamThreshold, self).__str__()
diff --git a/probables/cuckoo/countingcuckoo.py b/probables/cuckoo/countingcuckoo.py
index aa6d5e6..8dcafc3 100644
--- a/probables/cuckoo/countingcuckoo.py
+++ b/probables/cuckoo/countingcuckoo.py
@@ -3,12 +3,13 @@
     Author: Tyler Barrus (barrust@gmail.com)
 """
 
-import os
+import array
 import random
+from collections.abc import ByteString
 from io import IOBase
 from mmap import mmap
 from pathlib import Path
-from struct import calcsize, pack, unpack
+from struct import Struct
 from typing import List, Union
 
 from ..exceptions import CuckooFilterFullError
@@ -67,6 +68,9 @@ class CountingCuckooFilter(CuckooFilter):
             hash_function,
         )
 
+    __FOOTER_STRUCT = Struct("II")
+    __BIN_STRUCT = Struct("II")
+
     @classmethod
     def init_error_rate(
         cls,
@@ -99,8 +103,7 @@ class CountingCuckooFilter(CuckooFilter):
             expansion_rate=expansion_rate,
             hash_function=hash_function,
         )
-        cku._error_rate = error_rate
-        cku._fingerprint_size = cku._calc_fingerprint_size()
+        cku._set_error_rate(error_rate)
         return cku
 
     @classmethod
@@ -118,8 +121,26 @@ class CountingCuckooFilter(CuckooFilter):
             CuckooFilter: A Cuckoo Filter object
         """
         cku = CountingCuckooFilter(filepath=filepath, hash_function=hash_function)
-        cku._error_rate = error_rate
-        cku._fingerprint_size = cku._calc_fingerprint_size()
+        cku._set_error_rate(error_rate)
+        return cku
+
+    @classmethod
+    def frombytes(
+        cls, b: ByteString, error_rate: Union[float, None] = None, hash_function: Union[SimpleHashT, None] = None
+    ) -> "CountingCuckooFilter":
+        """
+        Args:
+            b (ByteString): The bytes to load as a Expanding Bloom Filter
+            error_rate (float): The error rate of the cuckoo filter, if used to generate the original filter
+            hash_function (function): Hashing strategy function to use `hf(key, number)`
+        Returns:
+            CountingCuckooFilter: A Bloom Filter object
+        """
+        cku = CountingCuckooFilter(hash_function=hash_function)
+        cku._load(b)  # type: ignore
+
+        # if error rate is provided, use it
+        cku._set_error_rate(error_rate)
         return cku
 
     def __contains__(self, val: KeyT) -> bool:
@@ -217,15 +238,15 @@ class CountingCuckooFilter(CuckooFilter):
         else:
             filepointer = file  # type:ignore
             for bucket in self.buckets:
-                # do something for each...
-                rep = len(bucket) * "II"
-                wbyt = pack(rep, *[x for x in self.__bucket_decomposition(bucket)])
-                filepointer.write(wbyt)
+                a = array.ArrayType("I")
+                a.fromlist([x for x in self.__bucket_decomposition(bucket)])
+                filepointer.write(a.tobytes())
                 leftover = self.bucket_size - len(bucket)
-                rep = leftover * "II"
-                filepointer.write(pack(rep, *([0] * (leftover * 2))))
+                t = array.ArrayType("I")
+                t.fromlist([0 for _ in range(leftover * 2)])
+                filepointer.write(t.tobytes())
             # now put out the required information at the end
-            filepointer.write(pack("II", self.bucket_size, self.max_swaps))
+            filepointer.write(self.__FOOTER_STRUCT.pack(self.bucket_size, self.max_swaps))
 
     def _insert_fingerprint_alt(
         self, fingerprint: int, idx_1: int, idx_2: int, count: int = 1
@@ -272,34 +293,39 @@ class CountingCuckooFilter(CuckooFilter):
             return idx_2
         return None
 
-    def _load(self, file: Union[Path, str, IOBase, mmap]) -> None:
+    def _load(self, file: Union[Path, str, IOBase, mmap, bytes]) -> None:
         """load a cuckoo filter from file"""
-        if not isinstance(file, (IOBase, mmap)):
+        if not isinstance(file, (IOBase, mmap, bytes)):
             file = Path(file)
             with MMap(file) as filepointer:
                 self._load(filepointer)
         else:
-            offset = calcsize("II")
-            int_size = calcsize("II")
-            file.seek(offset * -1, os.SEEK_END)
-            list_size = file.tell()
-            mybytes = unpack("II", file.read(offset))
-            self._bucket_size = mybytes[0]
-            self.__max_cuckoo_swaps = mybytes[1]
-            self._cuckoo_capacity = list_size // int_size // self.bucket_size
+            self._parse_footer(file)  # type: ignore
             self._inserted_elements = 0
-            # now pull everything in!
-            file.seek(0, os.SEEK_SET)
-            self._buckets = list()
-            for i in range(self.capacity):
-                self.buckets.append(list())
-                for _ in range(self.bucket_size):
-                    finger, count = unpack("II", file.read(int_size))
-                    if finger > 0:
-                        ccb = CountingCuckooBin(finger, count)
-                        self.buckets[i].append(ccb)
-                        self._inserted_elements += count
-                        self.__unique_elements += 1
+            self._parse_buckets(file)  # type: ignore
+
+    def _parse_footer(self, d: ByteString) -> None:
+        bucket_size, max_swaps = self.__FOOTER_STRUCT.unpack(bytes(d[-self.__FOOTER_STRUCT.size :]))
+        self._bucket_size = int(bucket_size)
+        self.__max_cuckoo_swaps = int(max_swaps)
+
+    def _parse_buckets(self, d: ByteString) -> None:
+        bin_size = self.__BIN_STRUCT.size
+        self._cuckoo_capacity = (len(bytes(d)) - bin_size) // bin_size // self.bucket_size
+        start = 0
+        end = bin_size
+        self._buckets = list()
+        for i in range(self.capacity):
+            self.buckets.append(list())
+            for _ in range(self.bucket_size):
+                finger, count = self.__BIN_STRUCT.unpack(bytes(d[start:end]))
+                if finger > 0:
+                    ccb = CountingCuckooBin(finger, count)
+                    self.buckets[i].append(ccb)
+                    self._inserted_elements += count
+                    self.__unique_elements += 1
+                start = end
+                end += bin_size
 
     def _expand_logic(self, extra_fingerprint: "CountingCuckooBin") -> None:
         """the logic to acutally expand the cuckoo filter"""
diff --git a/probables/cuckoo/cuckoo.py b/probables/cuckoo/cuckoo.py
index d5fc57d..057fca2 100644
--- a/probables/cuckoo/cuckoo.py
+++ b/probables/cuckoo/cuckoo.py
@@ -11,7 +11,7 @@ from io import BytesIO, IOBase
 from mmap import mmap
 from numbers import Number
 from pathlib import Path
-from struct import Struct, calcsize, pack
+from struct import Struct
 from typing import List, Tuple, Union
 
 from ..exceptions import CuckooFilterFullError, InitializationError
@@ -132,8 +132,7 @@ class CuckooFilter(object):
             expansion_rate=expansion_rate,
             hash_function=hash_function,
         )
-        cku._error_rate = error_rate
-        cku._fingerprint_size = cku._calc_fingerprint_size()
+        cku._set_error_rate(error_rate)
         return cku
 
     @classmethod
@@ -151,8 +150,26 @@ class CuckooFilter(object):
             CuckooFilter: A Cuckoo Filter object
         """
         cku = CuckooFilter(filepath=filepath, hash_function=hash_function)
-        cku._error_rate = error_rate
-        cku._fingerprint_size = cku._calc_fingerprint_size()
+        cku._set_error_rate(error_rate)
+        return cku
+
+    @classmethod
+    def frombytes(
+        cls, b: ByteString, error_rate: Union[float, None] = None, hash_function: Union[SimpleHashT, None] = None
+    ) -> "CuckooFilter":
+        """
+        Args:
+            b (ByteString): The bytes to load as a Expanding Bloom Filter
+            error_rate (float): The error rate of the cuckoo filter, if used to generate the original filter
+            hash_function (function): Hashing strategy function to use `hf(key, number)`
+        Returns:
+            CuckooFilter: A Bloom Filter object
+        """
+        cku = CuckooFilter(hash_function=hash_function)
+        cku._load(b)  # type: ignore
+
+        # if error rate is provided, use it
+        cku._set_error_rate(error_rate)
         return cku
 
     def __contains__(self, key: KeyT) -> bool:
@@ -337,13 +354,14 @@ class CuckooFilter(object):
                 bucket = self.buckets[i]
                 # do something for each...
                 if isinstance(bucket, list):
-                    self.buckets[i] = bucket = convert_to_typed(self.__class__.SINGLE_INT_C, bucket)  # type: ignore
+                    a = convert_to_typed(self.SINGLE_INT_C, bucket)
+                    self.buckets[i] = a.tolist()
+                    bucket = a  # type: ignore
                 filepointer.write(bucket.tobytes())  # type: ignore
                 leftover = self.bucket_size - len(bucket)
-                rep = leftover * "I"
-                filepointer.write(pack(rep, *([0] * leftover)))
+                convert_to_typed(self.SINGLE_INT_C, [0 for _ in range(leftover)]).tofile(filepointer)
             # now put out the required information at the end
-            filepointer.write(self.__class__.HEADER_STRUCT.pack(self.bucket_size, self.max_swaps))
+            filepointer.write(self.HEADER_STRUCT.pack(self.bucket_size, self.max_swaps))
 
     def __bytes__(self) -> bytes:
         """Export cuckoo filter to `bytes`"""
@@ -388,9 +406,9 @@ class CuckooFilter(object):
         # if we got here we have an error... we might need to know what is left
         return fingerprint
 
-    def _load(self, file: Union[Path, str, IOBase, mmap]) -> None:
+    def _load(self, file: Union[Path, str, IOBase, mmap, bytes]) -> None:
         """load a cuckoo filter from file"""
-        if not isinstance(file, (IOBase, mmap)):
+        if not isinstance(file, (IOBase, mmap, bytes)):
             file = Path(file)
             with MMap(file) as filepointer:
                 self._load(filepointer)
@@ -401,7 +419,7 @@ class CuckooFilter(object):
             self._parse_buckets(file)  # type: ignore
 
     SINGLE_INT_C = "I"
-    SINGLE_INT_SIZE = calcsize(SINGLE_INT_C)
+    SINGLE_INT_SIZE = Struct(SINGLE_INT_C).size
     HEADER_STRUCT = Struct("II")
 
     def _parse_footer(self, d: ByteString) -> None:
@@ -425,6 +443,13 @@ class CuckooFilter(object):
         self._inserted_elements += len(bucket)
         return bucket
 
+    def _set_error_rate(self, error_rate: Union[float, None]) -> None:
+        """set error rate correctly"""
+        # if error rate is provided, use it
+        if error_rate is not None:
+            self._error_rate = error_rate
+            self._fingerprint_size = self._calc_fingerprint_size()
+
     def _check_if_present(self, idx_1, idx_2, fingerprint):
         """wrapper for checking if fingerprint is already inserted"""
         if fingerprint in self.buckets[idx_1]:
diff --git a/probables/utilities.py b/probables/utilities.py
index e5b5004..00a14f9 100644
--- a/probables/utilities.py
+++ b/probables/utilities.py
@@ -62,15 +62,27 @@ class MMap(object):
 
     @property
     def closed(self) -> bool:
+        """Is the MMap closed"""
         return self._closed
 
     @property
     def map(self) -> mmap.mmap:
+        """Return a pointer to the mmap"""
         return self.m
 
     @property
     def path(self) -> Path:
+        """Return the path to the mmap'd file"""
         return self.p
 
     def close(self) -> None:
+        """Close the MMap class includeing cleaning up open files, etc"""
         self.__exit__()
+
+    def seek(self, pos: int, whence: int) -> None:
+        """Implement a method to seek on top of the MMap class"""
+        self.m.seek(pos, whence)
+
+    def read(self, n: int = -1) -> bytes:
+        """Implement a method to read from the file on top of the MMap class"""
+        return self.m.read(n)


Test patch:
diff --git a/tests/bloom_test.py b/tests/bloom_test.py
index b280efb..6b5be99 100644
--- a/tests/bloom_test.py
+++ b/tests/bloom_test.py
@@ -142,8 +142,7 @@ class TestBloomFilter(unittest.TestCase):
         self.assertEqual(230041400, blm.number_bits)
 
     def test_bf_intersection_diff(self):
-        """make sure checking for different bloom filters works
-        intersection"""
+        """make sure checking for different bloom filters works intersection"""
         blm = BloomFilter(est_elements=10, false_positive_rate=0.05)
         blm.add("this is a test")
         blm2 = BloomFilter(est_elements=100, false_positive_rate=0.05)
@@ -337,6 +336,7 @@ class TestBloomFilter(unittest.TestCase):
         self.assertEqual(md5_out, md5_val)
 
     def test_bf_bytes(self):
+        """test exporting BloomFilter to bytes"""
         md5_val = "8d27e30e1c5875b0edcf7413c7bdb221"
         blm = BloomFilter(est_elements=10, false_positive_rate=0.05)
         blm.add("this is a test")
@@ -344,6 +344,22 @@ class TestBloomFilter(unittest.TestCase):
         md5_out = hashlib.md5(b).hexdigest()
         self.assertEqual(md5_out, md5_val)
 
+    def test_bf_frombytes(self):
+        """test loading BloomFilter from bytes"""
+        blm = BloomFilter(est_elements=10, false_positive_rate=0.05)
+        blm.add("this is a test")
+        bytes_out = bytes(blm)
+
+        blm2 = BloomFilter.frombytes(bytes_out)
+
+        self.assertEqual(blm2.false_positive_rate, 0.05000000074505806)
+        self.assertEqual(blm2.estimated_elements, 10)
+        self.assertEqual(blm2.number_hashes, 4)
+        self.assertEqual(blm2.number_bits, 63)
+        self.assertEqual(blm2.elements_added, 1)
+        self.assertEqual(blm2.is_on_disk, False)
+        self.assertEqual(blm2.bloom_length, 63 // 8 + 1)
+
     def test_bf_load_file(self):
         """test loading bloom filter from file"""
         blm = BloomFilter(est_elements=10, false_positive_rate=0.05)
@@ -713,6 +729,7 @@ class TestBloomFilterOnDisk(unittest.TestCase):
                 self.assertEqual(md5_1, md5_2)
 
     def test_bfod_bytes(self):
+        """test exporting an on disk Bloom Filter to bytes"""
         md5_val = "8d27e30e1c5875b0edcf7413c7bdb221"
         with NamedTemporaryFile(dir=os.getcwd(), suffix=".blm", delete=DELETE_TEMP_FILES) as fobj:
             blm = BloomFilterOnDisk(fobj.name, 10, 0.05)
@@ -721,6 +738,28 @@ class TestBloomFilterOnDisk(unittest.TestCase):
             md5_out = hashlib.md5(b).hexdigest()
             self.assertEqual(md5_out, md5_val)
 
+    def test_bfod_frombytes(self):
+        """test loading an on disk BloomFilter from bytes (raises exception)"""
+        with NamedTemporaryFile(dir=os.getcwd(), suffix=".blm", delete=DELETE_TEMP_FILES) as fobj:
+            blm = BloomFilterOnDisk(fobj.name, 10, 0.05)
+            blm.add("this is a test")
+            bytes_out = bytes(blm)
+        self.assertRaises(NotSupportedError, lambda: BloomFilterOnDisk.frombytes(bytes_out))
+
+    def test_bfod_frombytes_msg(self):
+        """test loading an on disk BloomFilter from bytes (message)"""
+        with NamedTemporaryFile(dir=os.getcwd(), suffix=".blm", delete=DELETE_TEMP_FILES) as fobj:
+            blm = BloomFilterOnDisk(fobj.name, 10, 0.05)
+            blm.add("this is a test")
+            bytes_out = bytes(blm)
+        try:
+            BloomFilterOnDisk.frombytes(bytes_out)
+        except NotSupportedError as ex:
+            msg = "Loading from bytes is currently not supported by the on disk Bloom Filter"
+            self.assertEqual(str(ex), msg)
+        else:
+            self.assertEqual(True, False)
+
     def test_bfod_export_hex(self):
         """test that page error is thrown correctly"""
         hex_val = "6da491461a6bba4d000000000000000a000000000000000a3d4ccccd"
@@ -810,9 +849,7 @@ class TestBloomFilterOnDisk(unittest.TestCase):
             self.assertEqual(blm3, None)
 
     def test_bfod_intersection_diff(self):
-        """make sure checking for different bloom filters on disk works
-        intersection
-        """
+        """make sure checking for different bloom filters on disk works intersection"""
         with NamedTemporaryFile(dir=os.getcwd(), suffix=".blm", delete=DELETE_TEMP_FILES) as fobj:
             blm = BloomFilterOnDisk(fobj.name, est_elements=10, false_positive_rate=0.05)
             blm.add("this is a test")
@@ -822,9 +859,7 @@ class TestBloomFilterOnDisk(unittest.TestCase):
             self.assertEqual(blm3, None)
 
     def test_bfod_jaccard_diff(self):
-        """make sure checking for different bloom filters on disk works
-        jaccard
-        """
+        """make sure checking for different bloom filters on disk works jaccard"""
         with NamedTemporaryFile(dir=os.getcwd(), suffix=".blm", delete=DELETE_TEMP_FILES) as fobj:
             blm = BloomFilterOnDisk(fobj.name, est_elements=10, false_positive_rate=0.05)
             blm.add("this is a test")
diff --git a/tests/countingbloom_test.py b/tests/countingbloom_test.py
index d6e97cf..4580f08 100644
--- a/tests/countingbloom_test.py
+++ b/tests/countingbloom_test.py
@@ -126,9 +126,9 @@ class TestCountingBloomFilter(unittest.TestCase):
             self.assertEqual(md5_out, md5_val)
 
     def test_cbf_bytes(self):
+        """test exporting counting bloom filter to bytes"""
         md5_val = "0b83c837da30e25f768f0527c039d341"
         blm = CountingBloomFilter(est_elements=10, false_positive_rate=0.01)
-        # print("\n\n\n{}\n\n\n".format(blm.bloom_length))
         blm.add("test")
         blm.add("out")
         blm.add("the")
@@ -140,11 +140,39 @@ class TestCountingBloomFilter(unittest.TestCase):
         blm.add("Test")
         blm.add("out")
         blm.add("test")
-        # blm.export(fobj.name)
 
         md5_out = hashlib.md5(bytes(blm)).hexdigest()
         self.assertEqual(md5_out, md5_val)
 
+    def test_cbf_frombytes(self):
+        """test loading counting bloom filter from bytes"""
+        blm = CountingBloomFilter(est_elements=10, false_positive_rate=0.05)
+        blm.add("test")
+        blm.add("out")
+        blm.add("the")
+        blm.add("counting")
+        blm.add("bloom")
+        blm.add("filter")
+
+        blm.add("test")
+        blm.add("Test")
+        blm.add("out")
+        blm.add("test")
+        bytes_out = bytes(blm)
+
+        blm2 = CountingBloomFilter.frombytes(bytes_out)
+        self.assertEqual(blm.false_positive_rate, 0.05000000074505806)
+        self.assertEqual(blm.estimated_elements, 10)
+        self.assertEqual(blm.number_hashes, 4)
+        self.assertEqual(blm.number_bits, 63)
+        self.assertEqual(blm.elements_added, 10)
+        self.assertEqual(blm.is_on_disk, False)
+        self.assertEqual(blm.bloom_length, 63)
+
+        self.assertEqual(bytes(blm2), bytes(blm))
+        self.assertTrue(blm2.check("test"))
+        self.assertFalse(blm2.check("something"))
+
     def test_cbf_load_file(self):
         """test loading bloom filter from file"""
         with NamedTemporaryFile(dir=os.getcwd(), suffix=".cbm", delete=DELETE_TEMP_FILES) as fobj:
diff --git a/tests/countingcuckoo_test.py b/tests/countingcuckoo_test.py
index 9a9eade..9ef3195 100644
--- a/tests/countingcuckoo_test.py
+++ b/tests/countingcuckoo_test.py
@@ -218,6 +218,20 @@ class TestCountingCuckooFilter(unittest.TestCase):
         md5_out = hashlib.md5(bytes(cko)).hexdigest()
         self.assertEqual(md5sum, md5_out)
 
+    def test_c_cuckoo_filter_frombytes(self):
+        """test initializing a counting cuckoo filter frombytes"""
+        cko = CountingCuckooFilter(capacity=1000, bucket_size=2, auto_expand=False)
+        for i in range(100):
+            cko.add(str(i))
+        bytes_out = bytes(cko)
+
+        cko2 = CountingCuckooFilter.frombytes(bytes_out)
+
+        self.assertEqual(bytes_out, bytes(cko2))
+        for i in range(100):
+            self.assertTrue(cko2.check(str(i)))
+        self.assertFalse(cko2.check("999"))
+
     def test_c_cuckoo_filter_load(self):
         """test loading a saved counting cuckoo filter"""
         md5sum = "6a98c2df1ec9fbb4f75f8e6392696b9b"
@@ -284,6 +298,8 @@ class TestCountingCuckooFilter(unittest.TestCase):
 
 
 class TestCuckooFilterErrorRate(unittest.TestCase):
+    """Test CountingCuckooFilter using Error Rate"""
+
     def test_c_cuckoo_filter_er_default(self):
         """test cuckoo filter default properties"""
         cko = CountingCuckooFilter.init_error_rate(0.00001)
@@ -367,6 +383,21 @@ class TestCuckooFilterErrorRate(unittest.TestCase):
         md5_out = hashlib.md5(bytes(cko)).hexdigest()
         self.assertEqual(md5sum, md5_out)
 
+    def test_c_cuckoo_filter_er_frombytes(self):
+        """test initializing a couting cuckoo filter from bytes"""
+        cko = CountingCuckooFilter.init_error_rate(0.00001, capacity=3000)
+        for i in range(1000):
+            cko.add(str(i))
+        bytes_out = bytes(cko)
+
+        cko2 = CountingCuckooFilter.frombytes(bytes_out, error_rate=0.00001)
+
+        self.assertEqual(bytes_out, bytes(cko2))
+        for i in range(1000):
+            self.assertTrue(cko2.check(str(i)))
+        self.assertFalse(cko2.check("9999"))
+        self.assertEqual(cko2.capacity, 3000)
+
     def test_c_cuckoo_filter_er_remove(self):
         """test removing from the counting cuckoo filter"""
         cko = CountingCuckooFilter.init_error_rate(0.00001)
diff --git a/tests/countminsketch_test.py b/tests/countminsketch_test.py
index ebc6de1..20cd79d 100644
--- a/tests/countminsketch_test.py
+++ b/tests/countminsketch_test.py
@@ -213,6 +213,18 @@ class TestCountMinSketch(unittest.TestCase):
         md5_out = hashlib.md5(bytes(cms)).hexdigest()
         self.assertEqual(md5_out, md5_val)
 
+    def test_cms_frombytes(self):
+        """test loading a count-min sketch from bytes"""
+        cms = CountMinSketch(width=1000, depth=5)
+        cms.add("this is a test", 100)
+        bytes_out = bytes(cms)
+
+        cms2 = CountMinSketch.frombytes(bytes_out)
+        self.assertEqual(bytes(cms2), bytes(cms))
+        self.assertEqual(cms2.width, 1000)
+        self.assertEqual(cms2.depth, 5)
+        self.assertEqual(cms2.check("this is a test"), 100)
+
     def test_cms_load(self):
         """test loading a count-min sketch from file"""
         md5_val = "fb1c39dd1a73f1ef0d7fc79f60fc028e"
@@ -688,6 +700,28 @@ class TestHeavyHitters(unittest.TestCase):
             self.assertEqual(hh2.add("this is a test", 1), 101)
             self.assertEqual(hh2.heavy_hitters, {"this is a test": 101})
 
+    def test_hh_bytes(self):
+        """test exporting a heavy hitters sketch as bytes"""
+        md5_val = "fb1c39dd1a73f1ef0d7fc79f60fc028e"
+
+        hh1 = HeavyHitters(num_hitters=1000, width=1000, depth=5)
+        hh1.add("this is a test", 100)
+        self.assertEqual(hashlib.md5(bytes(hh1)).hexdigest(), md5_val)
+
+    def test_hh_frombytes(self):
+        """testinitializing a heavy hitters from bytes"""
+        hh1 = HeavyHitters(num_hitters=1000, width=1000, depth=5)
+        hh1.add("this is a test", 100)
+        bytes_out = bytes(hh1)
+
+        hh2 = HeavyHitters.frombytes(bytes_out, num_hitters=500)
+        self.assertEqual(hh2.width, 1000)
+        self.assertEqual(hh2.depth, 5)
+        self.assertEqual(hh2.number_heavy_hitters, 500)
+        self.assertEqual(hh2.elements_added, 100)
+        self.assertEqual(bytes(hh2), bytes(hh1))
+        self.assertEqual(hh2.check("this is a test"), 100)
+
     def test_hh_str(self):
         """test the string representation of the heavy hitters sketch"""
         hh1 = HeavyHitters(num_hitters=2, width=1000, depth=5)
@@ -870,6 +904,32 @@ class TestStreamThreshold(unittest.TestCase):
             self.assertEqual(st2.add("this is a test", 1), 101)
             self.assertEqual(st2.meets_threshold, {"this is a test": 101})
 
+    def test_streamthreshold_bytes(self):
+        """test exporting a streaming threshold as bytes"""
+        md5_val = "fb1c39dd1a73f1ef0d7fc79f60fc028e"
+        st1 = StreamThreshold(threshold=10, width=1000, depth=5)
+        st1.add("this is a test", 100)
+        bytes_out = bytes(st1)
+        self.assertEqual(hashlib.md5(bytes_out).hexdigest(), md5_val)
+
+    def test_streamthreshold_frombytes(self):
+        """testinitializing a heavy hitters from bytes"""
+        st1 = StreamThreshold(threshold=10, width=1000, depth=5)
+        st1.add("this is a test", 100)
+        bytes_out = bytes(st1)
+
+        st2 = StreamThreshold.frombytes(bytes_out, threshold=10)
+        self.assertEqual(st2.width, 1000)
+        self.assertEqual(st2.depth, 5)
+        self.assertEqual(st2.threshold, 10)
+        self.assertEqual(st2.elements_added, 100)
+        self.assertEqual(bytes(st2), bytes(st1))
+        self.assertEqual(st2.check("this is a test"), 100)
+        # show on load that the tracking of stream threshold is gone
+        self.assertEqual(st2.meets_threshold, dict())
+        self.assertEqual(st2.add("this is a test", 1), 101)
+        self.assertEqual(st2.meets_threshold, {"this is a test": 101})
+
     def test_streamthreshold_str(self):
         """test the string representation of the stream threshold sketch"""
         st1 = StreamThreshold(threshold=10, width=1000, depth=5)
diff --git a/tests/cuckoo_test.py b/tests/cuckoo_test.py
index 7bda5e3..be47cca 100644
--- a/tests/cuckoo_test.py
+++ b/tests/cuckoo_test.py
@@ -64,6 +64,7 @@ class TestCuckooFilter(unittest.TestCase):
         """test using a different hash function"""
 
         def my_hash(key):
+            """fake hash"""
             return int(hashlib.sha512(key.encode("utf-8")).hexdigest(), 16)
 
         cko = CuckooFilter(
@@ -264,6 +265,20 @@ class TestCuckooFilter(unittest.TestCase):
         md5_out = hashlib.md5(bytes(cko)).hexdigest()
         self.assertEqual(md5sum, md5_out)
 
+    def test_cuckoo_filter_frombytes(self):
+        """test initializing a cuckoo filter from bytes"""
+        cko = CuckooFilter()
+        for i in range(1000):
+            cko.add(str(i))
+        bytes_out = bytes(cko)
+
+        cko2 = CuckooFilter.frombytes(bytes_out)
+        self.assertEqual(bytes_out, bytes(cko2))
+
+        for i in range(1000):
+            self.assertTrue(cko2.check(str(i)))
+        self.assertFalse(cko2.check("9999"))
+
     def test_cuckoo_filter_load(self):
         """test loading a saved cuckoo filter"""
         md5sum = "1371760d4ee9ccbe83e0144919750140"
@@ -352,7 +367,7 @@ class TestCuckooFilter(unittest.TestCase):
             self.assertEqual(True, False)
 
     def test_invalid_buckets(self):
-        """test invalid capacity"""
+        """test invalid buckets"""
 
         def runner():
             """runner"""
@@ -368,7 +383,7 @@ class TestCuckooFilter(unittest.TestCase):
             self.assertEqual(True, False)
 
     def test_invalid_swaps(self):
-        """test invalid capacity"""
+        """test invalid swaps"""
 
         def runner():
             """runner"""
@@ -384,7 +399,7 @@ class TestCuckooFilter(unittest.TestCase):
             self.assertEqual(True, False)
 
     def test_invalid_capacity_2(self):
-        """test invalid capacity"""
+        """test invalid capacity (2)"""
 
         def runner():
             """runner"""
@@ -400,7 +415,7 @@ class TestCuckooFilter(unittest.TestCase):
             self.assertEqual(True, False)
 
     def test_invalid_buckets_2(self):
-        """test invalid capacity"""
+        """test invalid buckets (2)"""
 
         def runner():
             """runner"""
@@ -416,7 +431,7 @@ class TestCuckooFilter(unittest.TestCase):
             self.assertEqual(True, False)
 
     def test_invalid_swaps_2(self):
-        """test invalid capacity"""
+        """test invalid swaps (2)"""
 
         def runner():
             """runner"""
@@ -433,6 +448,8 @@ class TestCuckooFilter(unittest.TestCase):
 
 
 class TestCuckooFilterErrorRate(unittest.TestCase):
+    """Test Cuckoo Filter using Error Rate"""
+
     def test_cuckoo_filter_er_default(self):
         """test cuckoo filter default properties"""
         cko = CuckooFilter.init_error_rate(0.00001)
@@ -514,6 +531,20 @@ class TestCuckooFilterErrorRate(unittest.TestCase):
         md5_out = hashlib.md5(bytes(cko)).hexdigest()
         self.assertEqual(md5sum, md5_out)
 
+    def test_cuckoo_filter_er_frombytes(self):
+        """test initializing a cuckoo filter frombytes"""
+        cko = CuckooFilter.init_error_rate(0.00001)
+        for i in range(1000):
+            cko.add(str(i))
+        bytes_out = bytes(cko)
+
+        cko2 = CuckooFilter.frombytes(bytes_out, error_rate=0.00001)
+        self.assertEqual(bytes_out, bytes(cko2))
+
+        for i in range(1000):
+            self.assertTrue(cko2.check(str(i)))
+        self.assertFalse(cko2.check("9999"))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/expandingbloom_test.py b/tests/expandingbloom_test.py
index fa2d194..5ff513e 100644
--- a/tests/expandingbloom_test.py
+++ b/tests/expandingbloom_test.py
@@ -21,6 +21,8 @@ DELETE_TEMP_FILES = True
 
 
 class TestExpandingBloomFilter(unittest.TestCase):
+    """Test ExpandingBloomFilter"""
+
     def test_ebf_init(self):
         """test the initialization of an expanding bloom filter"""
         blm = ExpandingBloomFilter(est_elements=10, false_positive_rate=0.05)
@@ -58,6 +60,7 @@ class TestExpandingBloomFilter(unittest.TestCase):
         self.assertEqual(blm.check("this is another test"), True)
         self.assertEqual(blm.check("this is yet another test!"), False)
         self.assertEqual(blm.check("this is not another test"), False)
+        self.assertEqual(blm.elements_added, 102)
 
     def test_ebf_contains(self):
         """ensure that "in" functionality for the expanding bloom filter works"""
@@ -99,6 +102,23 @@ class TestExpandingBloomFilter(unittest.TestCase):
         blm = ExpandingBloomFilter(est_elements=25, false_positive_rate=0.05)
         self.assertEqual(hashlib.md5(bytes(blm)).hexdigest(), "eb5769ae9babdf7b37d6ce64d58812bc")
 
+    def test_ebf_frombytes(self):
+        """expanding Bloom Filter load bytes test"""
+        blm = ExpandingBloomFilter(est_elements=25, false_positive_rate=0.05)
+        for i in range(105):
+            blm.add(str(i))
+        bytes_out = bytes(blm)
+
+        blm2 = ExpandingBloomFilter.frombytes(bytes_out)
+        self.assertEqual(blm2.expansions, 3)
+        self.assertEqual(blm2.false_positive_rate, 0.05000000074505806)
+        self.assertEqual(blm2.estimated_elements, 25)
+        self.assertEqual(blm2.elements_added, 105)
+        self.assertEqual(bytes(blm2), bytes(blm))
+
+        for i in range(105):
+            self.assertTrue(blm.check(str(i)))
+
     def test_ebf_import_empty(self):
         """test that expanding Bloom Filter is correct on import"""
         with NamedTemporaryFile(dir=os.getcwd(), suffix=".ebf", delete=DELETE_TEMP_FILES) as fobj:
@@ -131,6 +151,8 @@ class TestExpandingBloomFilter(unittest.TestCase):
 
 
 class TestRotatingBloomFilter(unittest.TestCase):
+    """Test RotatingBloomFilter"""
+
     def test_rbf_init(self):
         """test the initialization of an rotating bloom filter"""
         blm = RotatingBloomFilter(est_elements=10, false_positive_rate=0.05, max_queue_size=10)
@@ -169,7 +191,10 @@ class TestRotatingBloomFilter(unittest.TestCase):
         self.assertEqual(blm.check("test"), False)  # it should roll off
         self.assertEqual(blm.current_queue_size, 5)
 
+        self.assertEqual(blm.elements_added, 51)
+
     def test_rbf_push_pop(self):
+        """test forcing push and pop"""
         blm = RotatingBloomFilter(est_elements=10, false_positive_rate=0.05, max_queue_size=5)
         self.assertEqual(blm.current_queue_size, 1)
         blm.add("test")
@@ -232,6 +257,23 @@ class TestRotatingBloomFilter(unittest.TestCase):
         blm = RotatingBloomFilter(est_elements=25, false_positive_rate=0.05)
         self.assertEqual(hashlib.md5(bytes(blm)).hexdigest(), "eb5769ae9babdf7b37d6ce64d58812bc")
 
+    def test_rfb_from_bytes(self):
+        """basic rotating Bloom Filter export bytes test"""
+        blm = RotatingBloomFilter(est_elements=25, false_positive_rate=0.05, max_queue_size=3)
+        for i in range(105):
+            blm.add(str(i))
+        bytes_out = bytes(blm)
+
+        blm2 = RotatingBloomFilter.frombytes(bytes_out, max_queue_size=3)
+        self.assertEqual(blm2.expansions, 2)
+        self.assertEqual(blm2.false_positive_rate, 0.05000000074505806)
+        self.assertEqual(blm2.estimated_elements, 25)
+        self.assertEqual(blm2.elements_added, 105)
+        self.assertEqual(blm2.current_queue_size, 3)
+        self.assertEqual(bytes(blm2), bytes(blm))
+        for i in range(105):
+            self.assertEqual(blm.check(str(i)), blm2.check(str(i)))
+
     def test_rbf_import_empty(self):
         """test that rotating Bloom Filter is correct on import"""
         with NamedTemporaryFile(dir=os.getcwd(), suffix=".rbf", delete=DELETE_TEMP_FILES) as fobj:
@@ -261,6 +303,7 @@ class TestRotatingBloomFilter(unittest.TestCase):
                 self.assertEqual("{}".format(i) in blm2, True)
             self.assertEqual(blm2.current_queue_size, 10)
             self.assertEqual(blm2.expansions, 9)
+            self.assertEqual(blm2.elements_added, 15)
 
 
 if __name__ == "__main__":
diff --git a/tests/test_utilities.py b/tests/test_utilities.py
index f5fb254..e57aa35 100644
--- a/tests/test_utilities.py
+++ b/tests/test_utilities.py
@@ -12,23 +12,23 @@ sys.path.insert(0, str(this_dir.parent))
 
 from utilities import different_hash
 
-from probables.utilities import get_x_bits, is_hex_string, is_valid_file
+from probables.utilities import MMap, get_x_bits, is_hex_string, is_valid_file
 
 DELETE_TEMP_FILES = True
 
 
 class TestProbablesUtilities(unittest.TestCase):
-    """ test the utilities for pyprobables """
+    """test the utilities for pyprobables"""
 
     def test_is_hex(self):
-        """ test the is valid hex function """
-        self.assertTrue(is_hex_string("1234678909abcdef"))
-        self.assertTrue(is_hex_string("1234678909ABCDEF"))
-        self.assertFalse(is_hex_string("1234678909abcdfq"))
-        self.assertFalse(is_hex_string("1234678909ABCDEFQ"))
+        """test the is valid hex function"""
+        self.assertTrue(is_hex_string("123467890abcdef"))
+        self.assertTrue(is_hex_string("123467890ABCDEF"))
+        self.assertFalse(is_hex_string("123467890abcdfq"))
+        self.assertFalse(is_hex_string("123467890ABCDEFQ"))
 
     def test_is_valid_file(self):
-        """ test the is valid file function """
+        """test the is valid file function"""
         with NamedTemporaryFile(dir=os.getcwd(), suffix=".rbf", delete=DELETE_TEMP_FILES) as fobj:
             self.assertFalse(is_valid_file(None))
             self.assertFalse(is_valid_file("./file_doesnt_exist.txt"))
@@ -37,7 +37,7 @@ class TestProbablesUtilities(unittest.TestCase):
             self.assertTrue(is_valid_file(fobj.name))
 
     def test_get_x_bits(self):
-        """ test the get x bits function """
+        """test the get x bits function"""
         for i in range(8):
             res = get_x_bits(i, 4, 2, True)
             self.assertEqual(res, i % 4)
@@ -49,7 +49,7 @@ class TestProbablesUtilities(unittest.TestCase):
                 self.assertEqual(res, 1)
 
     def test_get_x_bits_large(self):
-        """ test it on much larger numbers """
+        """test it on much larger numbers"""
         res = different_hash("this is a test", 1)[0]
         # 1010100101011011100100010101010011110000001010011010000101001011
         tmp1 = get_x_bits(res, 64, 32, True)
@@ -82,6 +82,21 @@ class TestProbablesUtilities(unittest.TestCase):
         self.assertEqual(1, tmp1)
         self.assertEqual(1, tmp2)
 
+    def test_mmap_functionality(self):
+        """test some of the MMap class functionality"""
+        data = b"this is a test of the MMap system!"
+        with NamedTemporaryFile(dir=os.getcwd(), suffix=".rbf", delete=DELETE_TEMP_FILES) as fobj:
+            with open(fobj.name, "wb") as fobj:
+                fobj.write(data)
+            m = MMap(fobj.name)
+            self.assertFalse(m.closed)
+            self.assertEqual(data, m.read())
+            m.seek(0, os.SEEK_SET)
+            self.assertEqual(data[:5], m.read(5))
+            self.assertEqual(data[5:], m.read())
+            m.close()
+            self.assertTrue(m.closed)
+
 
 if __name__ == "__main__":
     unittest.main()