Tengo un dict en python que sigue este formato general:
{'field': ['$.name'], 'group': 'name', 'function': 'some_function'}Quiero hacer una verificación previa del dict para asegurarme de que el 'campo' siempre exista, y que no existan más claves más allá del 'grupo' y la 'función', que son opcionales.
Sé que puedo hacer esto usando una declaración if larga y desordenada, pero estoy pensando que debe haber una manera más limpia.
Esto es lo que tengo actualmente:
if (('field' in dict_name and len(dict_name.keys()) == 1) or ('group' in dict_name and len(dict_name.keys()) == 2) or ('function' in dict_name and len(dict_name.keys()) == 2) or ('group' in dict_name and 'function' in dict_name and len(dict_name.keys()) == 3))Esencialmente, primero estoy comprobando si existe un 'campo', ya que es obligatorio. Luego verifico si es la única tecla (que está bien) o si es una tecla junto a 'grupo' y ninguna otra, o una tecla junto a 'función' y ninguna otra o una tecla junto a ambos 'grupo' y 'función' y no otros.
¿Hay una forma más ordenada de verificar que las claves suministradas son solo estas 3 claves donde dos son opcionales?
Puede usar un set de claves permitidas y verificar las claves del dict usando la función integrada all() :
allowed_keys = {'field', 'group', 'function'} if 'field' in d and all(key in allowed_keys for key in d): pass Esto es en realidad equivalente al método issubset() del set :
Prueba si cada elemento del conjunto está en otro.
Entonces, esto puede volverse aún más compacto al convertir las claves del dict en un conjunto y verificar que sean un subconjunto de las claves permitidas:
allowed_keys = {'field', 'group', 'function'} if 'field' in d and set(d).issubset(allowed_keys): passEn lo que a mí respecta, quieres comprobar que
{'field'} siempre está contenido en el conjunto de sus claves de dictado{'field', 'group', 'function'}¡Así que solo codifícalo!
required_fields = {'field'} allowed_fields = required_fields | {'group', 'function'} d = {'field': 123} # Set any value here if required_fields <= d.keys() <= allowed_fields: print("Yes!") else: print("No!")Esta solución es escalable para cualquier conjunto de campos requeridos y permitidos a menos que tenga algunas condiciones especiales (por ejemplo, claves mutuamente excluyentes)
(gracias a @Duncan por una reducción de código muy elegante)
dict.keys devuelve una vista similar a un set respaldada por los datos originales. Puedes aprovechar eso para escribir una prueba muy concisa:
allowed = {'field', 'group', 'function'} if 'field' in dict_name and dict_name.keys() <= allowed: ... set operator <= es equivalente al método issubset .
Puede usar otras operaciones de conjuntos para la segunda condición:
allowed >= dict_name.keys() len(dict_name.keys() | allowed) <= len(allowed) not (dict_name.keys() - allowed)Además de la legibilidad, el uso de operadores es la única posibilidad cuando se usa una vista de teclas en algunos casos. Por ejemplo, lo siguiente no se ejecuta:
dict_name.keys().issubset(allowed)Pero lo siguiente funciona bien:
dict_name.keys() <= allowedTu puedes hacer
allowed.issuperset(dict_name.keys()) Pero es probable que eso envuelva dict_name.keys() en un objeto de set innecesario. Al mismo tiempo,
allowed >= dict_name.keys() En realidad, cambiará el operador y usará la versión <= debido a cómo los operadores aritméticos de Python resuelven los tipos.
También puede usar paquetes de validación como schema https://pypi.org/project/schema/
from schema import Schema, And my_schema = Schema({ 'field': And(str, len), 'group': And(str, len), 'function': And(str, len) }) data = { 'field': 'Hello', 'group': 'This is a group', 'function': 'some_function' } my_schema.validate(data)Sí, al convertir su dictado con una clase de datos:
from typing import List, Optional from dataclasses import dataclass @dataclass class MyDataclass: field: List[str] group: Optional[str] = None function: Optional[str] = None result = MyDataclass(["$.name"], "name", "some_function") # or, equivalently: result = MyDataclass(field=["$.name"], group="name", function="some_function") # access with result.field, result.group, result.functionPara responder a su pregunta directamente, puede escribir lo siguiente y generará una excepción cuando falte un campo en el diccionario de entrada:
dict_name = {'field': ['$.name'], 'group': 'name', 'function': 'some_function'} MyDataclass(*dict_name) Tenga en cuenta que lo anterior solo funciona cuando sus claves son cadenas, debido al uso del operador splat. ( * )
Una vez convertido a una clase de datos, puede usarlo de manera segura con la certeza de que tiene los campos. Esto es menos propenso a errores, porque le impide mezclar un dictado verificado por parámetros faltantes y un dictado no verificado en diferentes partes de su código. Consulte Analizar, no validar para obtener una explicación completa desde un punto de vista teórico.
Las clases de datos son la forma idiomática de hacerlo en Python, de forma similar a cómo los objetos (diccionarios) son la forma idiomática de hacerlo en JavaScript. Además, si está utilizando un IDE compatible con mypy/pyre/PEP 484, obtendrá sugerencias de tipo sobre los objetos. Gracias a la bidireccionalidad de PEP 484, eso significa que si crea un dictado con un campo faltante y lo pasa a una función que lo convierte en una clase de datos, el verificador de tipos puede verificar el error.
Puede volver a convertir una clase de datos en un dictado usando dataclasses.asdict .
Otra opción es namedtuple.