En resumen, estoy usando un archivo Yaml como configuración para los parámetros de algunas canalizaciones/funciones que estoy usando. En python, este es un diccionario anidado y los parámetros pueden ser una matriz / diccionarios. Sería útil iterar a través de todos los archivos de configuración y buscar aquellos en los que se especifica un subconjunto de valores, por ejemplo
# toy example of all parameters a config file might have - param_a: 1 - param_b: - b1: 'a' - b2: [1,2,3] # want all configs with these values - param_a: 1 - param_b: - b2: [1,2,3]Por supuesto, uno podría hacer recursividad en cada diccionario anidado, pero en lugar de reinventar la rueda, me preguntaba si existe una solución probada y verdadera.
He visto algunas preguntas relacionadas (buscando confirmar diccionarios idénticos) y aparece Deep Diff . Sin embargo, no está claro si al probar un subconjunto, DeepDiff devolverá todas las claves que faltan. ¿Pensamientos?
Por ahora estoy usando esto y asumiendo que yaml se cargó correctamente como un diccionario anidado
def is_config_subset(truth, params): ''' Arguments: ---------- truth (dict): dictionary of parameters to compare to params (dict): dictionary of parameters to test Returns: ---------- result (bool) whether or not `params` is a subset of `truth` ''' if not type(truth) == type(params): return False for key, val in params.items(): if key not in truth: return False if type(val) is dict: if not is_config_subset(truth[key], val): return False else: if not truth[key] == val: return False return True print(is_config_subset({'a':1, 'b':2}, {'b':2})) print(is_config_subset({'a':1, 'b':2}, {'b':2, 'c':3})) print(is_config_subset({'a':1, 'b':2, 'c':[1,2,3]}, {'b':2, 'c':[1,2,3]})) print(is_config_subset({'a':1, 'b':2, 'c':[1,2,3]}, {'b':2, 'c':[1,2]})) print(is_config_subset({'a':1, 'b':2, 'c':[1,2,3]}, {'a':2, 'b':2})) True False True False FalseEste es probablemente un ejemplo simplista y no funcionará en todos los casos.
Creo que la solución más simple, y quizás la más eficiente, sería definir su propia función auxiliar recursiva, algo así como is_subset . Dado que YAML y JSON tienen un formato muy similar, primero puede cargar sus datos YAML en un tipo de Python, ya sea una list o un dict , y pasarlos a una función recursiva, que realiza la verificación del subconjunto según un criterio predefinido.
Por ejemplo, aquí hay un ejemplo simple (en su mayoría completo) para comenzar.
def is_subset(superset, o): """Check if `o` is subset of `superset`""" ss_type, o_type = type(superset), type(o) if ss_type != o_type: return False # now we know that both `superset` and `o` are the same type if o_type is dict: for o_key, o_value in o.items(): ss_value = superset.get(o_key) if not is_subset(ss_value, o_value): return False else: return True if o_type is list: # an empty list can be considered a subset if not o_type: return True # on other hand, if superset list is empty, we return false if not superset: return False first_o_type = type(o[0]) first_ss_type = type(superset[0]) if first_o_type != first_ss_type: return False if first_o_type is dict: merged_ss, merged_o = {}, {} for v in o: if type(v) is dict: # type check to be safe merged_o.update(v) # On Python 3.9+, the syntax would be: # merged_o |= v for v in superset: if type(v) is dict: # type check to be safe merged_ss.update(v) return is_subset(merged_ss, merged_o) elif first_o_type is list: for idx, o_value in enumerate(o): try: ss_value = superset[idx] except IndexError: return False if not is_subset(ss_value, o_value): return False else: return True else: # a list of simple types, like [1, 2, 3] for o_value in o: if o_value not in superset: return False else: return True # it's a simple type - not list or dict return superset == o Tenga en cuenta que hay algunos casos extremos que no cubre, por ejemplo, list s en la configuración YAML con tipos de datos mixtos, como una lista de valores dict y str . Dejaré que usted decida cómo manejar esos casos extremos, en caso de que valga la pena cubrirlos también.
En cualquier caso, así es como usaría la función de ayuda que declaramos anteriormente:
import yaml superset_config = yaml.safe_load(""" # toy example of all parameters a config file might have - param_a: 1 - param_b: - b1: 'a' - b2: [1,2,3] """) subset_config = yaml.safe_load(""" # want all configs with these values - param_a: 1 - param_b: - b2: [1,2,3] """) assert is_subset(superset_config, subset_config) subset_config[0]['param_c'] = 'test' assert not is_subset(superset_config, subset_config)