Skip to content

Instantly share code, notes, and snippets.

@jonatasrenan
Created June 12, 2021 01:43
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jonatasrenan/6bdc4b68d63368a0eca7d6f06b244727 to your computer and use it in GitHub Desktop.
Save jonatasrenan/6bdc4b68d63368a0eca7d6f06b244727 to your computer and use it in GitHub Desktop.
A comparison between python's dict libraries to read and write naturally as a JSON parsing.
"""
A comparison between python's dict libraries to read and write naturally as a JSON parsing.
Based on: https://gist.github.com/NelsonMinar/28c5928adbe1f4502af8
Installation:
pip install pypi-search addict easydict attrdict dotted-dict dotmap munch python-box
"""
from typing import Any
import addict
import easydict
import attrdict
import dotted_dict
import dotmap
import munch
# import treedict # treedict doesn't work with python3
import box # python-box
class DictAnalyzer:
source: dict
class_: Any
package_name: str
def __init__(self, source: dict, class_: Any, package_name: str):
self.source = source
self.class_ = class_
self.package_name = package_name
def instance(self):
from copy import deepcopy
return deepcopy(self.class_(self.source))
def test_bracket_syntax(self):
instance = self.instance()
assert instance["num"]
assert instance["list"][1]
assert instance["nested"]["several"]["levels"]["deep"]
def test_dot_syntax(self):
instance = self.instance()
assert instance.num
assert instance.list[1]
assert instance.nested.several.levels.deep
def test_access_missing_elements_using_get_method(self):
instance = self.instance()
assert instance.get("noObj", {}) == {}
assert instance.get("noNum", 1)
assert instance.get("noList", []) == []
def test_missing_attribute_is_false(self):
instance = self.instance()
assert bool(instance.noObj) is False
def test_add_element_using_bracket_syntax(self):
instance = self.instance()
instance["new"] = 5
assert instance.new == 5
def test_add_element_using_dot_syntax(self):
instance = self.instance()
instance.new = 5
assert instance.new == 5
def test_add_nested_elements_using_bracket_syntax(self):
instance = self.instance()
instance["other"] = {}
instance["other"]["nested"] = {}
instance["other"]["nested"]["thing"] = 6
assert instance.other.nested.thing == 6
def test_add_nested_elements_using_dot_syntax(self):
instance = self.instance()
instance.other = {}
instance.other.nested = {}
instance.other.nested.thing = 6
assert instance.other.nested.thing == 6
def test_add_nested_elements_without_recursive_initialization(self):
instance = self.instance()
instance.other.nested.thing = 6
assert instance.other.nested.thing == 6
def test_default_dict_conversion(self):
instance = self.instance()
assert dict(instance) == self.source
def test_serialization(self):
from json import dumps
instance = self.instance()
assert dumps(instance) == dumps(self.source)
def test_serialization_after_dict_conversion(self):
from json import dumps
instance = self.instance()
assert dumps(dict(instance)) == dumps(self.source)
def instance_size(self):
instance = self.instance()
return f"{len(instance)}"
def json_size(self):
from json import dumps
instance = self.instance()
return f"{len(dumps(instance))}"
def title(self):
from pypi_search.utils import PyPiPage
pypi_page = PyPiPage(self.package_name)
stats = ""
if pypi_page.response:
github_stats = pypi_page.get_github_stats()
if github_stats:
stats = f"({github_stats['stars']})"
return f"{self.package_name}{stats}"
def get_test_methods(self):
import inspect
members = inspect.getmembers(self, predicate=inspect.ismethod)
return {name: method for name, method in members if name.startswith('test_')}
def tests_results(self):
def check(method):
try:
method()
return 'x'
except:
return ''
all_tests = {name: check(method) for name, method in self.get_test_methods().items()}
return [self.title(), *all_tests.values(), self.instance_size(), self.json_size()]
def tests_names(self):
return ["", *self.get_test_methods().keys(), "instance_size", "json_size"]
sourceData = {
"num": 3.4,
"str": "foo",
"list": [0, 1, 2, 3],
"obj": {
"num": 3.4,
"str": "foo",
"list": [0, 1, 2, 3]
},
"nested": {
"several": {
"levels": {
"deep": 5
}
}
},
"empty_obj": {}
}
analyzers = [
DictAnalyzer(sourceData, dict, "builtin"),
DictAnalyzer(sourceData, addict.Dict, "addict"),
DictAnalyzer(sourceData, easydict.EasyDict, "easydict"),
DictAnalyzer(sourceData, attrdict.AttrDict, "attrdict"),
DictAnalyzer(sourceData, dotted_dict.DottedDict, "dotted-dict"),
DictAnalyzer(sourceData, dotmap.DotMap, "dotmap"),
DictAnalyzer(sourceData, munch.Munch, "munch"),
DictAnalyzer(sourceData, box.Box, "python-box"),
]
results = [analyzer.tests_results() for analyzer in analyzers]
results.sort(key=lambda l: sum([1 for i in l if i]))
table = [analyzers[0].tests_names(), *results]
transposed_table = list(map(list, zip(*table)))
# Print Table
print("| " + " | ".join(transposed_table[0]) + " |") # titles
print("| " + " | ".join([":-:" for _ in range(len(transposed_table[0]))]))
for line in transposed_table[1:]:
print(f"| {line[0] } | " + " | ".join(line[1:]) + " |")
@jonatasrenan
Copy link
Author

builtin munch(415) dotted-dict(14) dotmap(279) attrdict(179) addict(2030) easydict(73) python-box(1650)
test_access_missing_elements_using_get_method x x x x x x x x
test_add_element_using_bracket_syntax x x x x x x x
test_add_element_using_dot_syntax x x x x x x x
test_add_nested_elements_using_bracket_syntax x x x
test_add_nested_elements_using_dot_syntax x x
test_add_nested_elements_without_recursive_initialization x x
test_bracket_syntax x x x x x x x x
test_default_dict_conversion x x x x x x x x
test_dot_syntax x x x x x x
test_missing_attribute_is_false x x
test_serialization x x x x x x x
test_serialization_after_dict_conversion x x x x x x x
instance_size 6 6 6 6 6 6 6 6
json_size 170 170 170 2 170 170 170 170

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment