From df78a1ad2eefffd5d37f3075d35ed9e6cf1ed282 Mon Sep 17 00:00:00 2001 From: Cacahuete Lenovo Ubuntu Date: Mon, 17 Sep 2018 22:11:46 +0200 Subject: [PATCH] Reimplemented __hash__ and __eq__ in StrType, 'cause we want the comparison to be based on class name instead of instance's ID. Added a 'folding' feature in order to hide elements based on keys --- nested_mapping.py | 63 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/nested_mapping.py b/nested_mapping.py index c3ac3de..e08d222 100644 --- a/nested_mapping.py +++ b/nested_mapping.py @@ -1,19 +1,32 @@ from collections import Iterable - +from boltons.iterutils import remap def unique(seq): - """Return a new sequence made of unique elements from seq.""" + """Return a new sequence made of unique elements from seq. + Code borrowed from the Internet. + """ uniques = [] [uniques.append(x) for x in seq if not uniques.count(x)] return type(seq)(uniques) class StrType(str): - """Helper class to print and compare object types. - Provide the object whose type you want to print. + """Helper class to print and compare object types (used in function 'get_structure'). + When instanciating this class, provide the object whose type you want to print. + __hash__ and __eq__ implemented in order to filter duplicates and yet stay compatible with + uniqueness detection (to fit in sets for instance). """ def __init__(self, obj): self.value = type(obj).__name__ + + def __hash__(self): + return hash(self.value) + + def __eq__(self, other): + if not isinstance(other, StrType): + return False + else: + return self.value == other.value def __str__(self): return f"<{self.value}>" @@ -21,18 +34,38 @@ class StrType(str): __repr__ = __str__ -def get_structure(data): +def get_structure(data, folding_keys=[], folding_tag=" (folded)"): """Return a kind of map of the could-be nested data. Duplicates are removed from sequences. + If 'folding_keys' are provided then the corresponding values in the data set will be folded, + that is to say replaced by their data type (thus transformed into leaves). The key will be + appended with 'folding_tag'. """ - if isinstance(data, dict): - return {key:get_structure(value) for key, value in data.items()} - elif (isinstance(data, Iterable) - and not isinstance(data, str) - and not isinstance(data, bytes)): - if data: + if folding_keys: + def visit_fold(path, key, value): + if key in folding_keys: + # We only fold iterable types (except for strings and bytes). + if (isinstance(data, Iterable) + and not isinstance(data, str) + and not isinstance(data, bytes)): + return "".join([key, folding_tag]), type(value)() + return True + + data = remap(data, visit=visit_fold) + + def extract_structure(data): + if data and isinstance(data, dict): + return {key:get_structure(value) for key, value in data.items()} + elif data and (isinstance(data, Iterable) + and not isinstance(data, str) + and not isinstance(data, bytes)): + # The iterable is not empty, we map each of its elements return unique(type(data)([get_structure(item) for item in data])) - return StrType(data) + # It's a leaf (or empty iterable), + # we return the name of its type + return StrType(data) + + return extract_structure(data) if __name__ == "__main__": @@ -64,9 +97,11 @@ if __name__ == "__main__": '2 same dict 1 other': [{'same':3, 'as':b'the other'}, {'same':3, 'as':b'the other'}, {'different':'from','the':'other'}], - 'play me': MaClasse} + 'play me': MaClasse, + 'Two folded keys':{'Fold me :)':[1,2,3,4,5], + 'Fold me too':"folded, can't see me!"}} - structure = get_structure(nested) + structure = get_structure(nested, ['Fold me :)','Fold me too'], ' [+]') print("The nested data:") pprint(nested)