J'ai un dictionnaire avec la structure suivante:
D = {
'rows': 11,
'cols': 13,
(i, j): {
'meta': 'random string',
'walls': {
'E': True,
'O': False,
'N': True,
'S': True
}
}
}
# i ranging in {0 .. D['rows']-1}
# j ranging in {0 .. D['cols']-1}
On me demande de écrire une fonction qui prend un objet arbitraire en argument et vérifie si elle a cette structure Voici ce que j'ai écrit:
def well_formed(L):
if type(L) != dict:
return False
if 'rows' not in L:
return False
if 'cols' not in L:
return False
nr, nc = L['rows'], L['cols']
# I should also check the int-ness of nr and nc ...
if len(L) != nr*nc + 2:
return False
for i in range(nr):
for j in range(nc):
if not ((i, j) in L
and 'meta' in L[i, j]
and 'walls' in L[i, j]
and type(L[i, j]['meta']) == str
and type(L[i, j]['walls']) == dict
and 'E' in L[i, j]['walls']
and 'N' in L[i, j]['walls']
and 'O' in L[i, j]['walls']
and 'S' in L[i, j]['walls']
and type(L[i, j]['walls']['E']) == bool
and type(L[i, j]['walls']['N']) == bool
and type(L[i, j]['walls']['O']) == bool
and type(L[i, j]['walls']['S']) == bool):
return False
return True
Bien que cela fonctionne, je ne l'aime pas du tout. Existe-t-il un moyen pythonique de le faire?
Je suis autorisé à utiliser uniquement la bibliothèque standard.
Premièrement, je pense que le plus «Pythonique» pourrait être de demander pardon plutôt que d’autorisation - pour vérifier si vous avez besoin d’une propriété si la structure de données en possède une.
Mais d'un autre côté, cela n'aide pas si on vous demande de créer quelque chose pour vérifier que tout est bien formé. :)
Donc, si vous devez vérifier, vous pouvez utiliser quelque chose comme la bibliothèque de schémas pour définir la structure de vos données, puis vérifier les autres structures de données par rapport à ce schéma.
En Python, l'identité exacte des types impliqués est moins importante que le comportement des valeurs. Pour un use défini d'un tel objet, celui-ci suffirait-il? Cela signifie que L
ne doit pas nécessairement être un dict
, il ne dispose que d’un support __getitem__
; L[(i,j)]['meta']
ne doit pas nécessairement être une str
, il doit simplement supporter conversion en chaîne via str(L[(i,j)]['meta'])
; etc.
Compte tenu de cet assouplissement, j’essayerais simplement d’attraper les erreurs soulevées en tentant de telles actions et je renverrais False
le cas échéant. Par exemple,
def well_formed(L):
try:
nr = L['rows']
nc = L['cols']
except KeyError:
return False
try:
for i in range(nr):
for j in range(nc):
str(L[(i,j)]['meta'])
walls = L[(i,j)]['walls']
for direction in walls:
# Necessary?
if direction not in "ENOS":
return False
if walls[direction] not in (True, False):
return False
except KeyError:
return False
return True
Étant donné que tout objet a une valeur booléenne, il semble inutile de tenter bool(walls[direction])
; au lieu de cela, si avoir exactement True
ou False
comme valeur n'est pas une exigence absolue, j’aimerais tout simplement tester la valeur. De même, des murs supplémentaires peuvent ou non être un problème et ne doivent pas être explicitement testés.
Vous pouvez composer une validation comme celle-ci (idée des extracteurs Scala). L'avantage est que la structure du validateur est similaire à la structure à tester.
Un inconvénient est que les nombreux appels de fonctions peuvent le ralentir beaucoup.
class Mapping:
def __init__(self, **kwargs):
self.key_values = [KeyValue(k, v) for k, v in kwargs.items()]
def validate(self, to_validate):
if not isinstance(to_validate, dict):
return False
for validator in self.key_values:
if not validator.validate(to_validate):
return False
return True
class KeyValue:
def __init__(self, key, value):
self.key = key
self.value = value
def validate(self, to_validate):
return self.key in to_validate and self.value.validate(to_validate[self.key])
class Boolean:
def validate(self, to_validate):
return isinstance(to_validate, bool)
class Integer:
def validate(self, to_validate):
return isinstance(to_validate, int)
class String:
def validate(self, to_validate):
return isinstance(to_validate, str)
class CustomValidator:
def validate(self, to_validate):
if not Mapping(rows=Integer(), cols=Integer()).validate(to_validate):
return False
element_validator = Mapping(meta=String(), walls=Mapping(**{k: Boolean() for k in "EONS"}))
for i in range(to_validate['rows']):
for j in range(to_validate['cols']):
if not KeyValue((i, j), element_validator).validate(to_validate):
return False
return True
d = {
'rows': 11,
'cols': 13,
}
d.update({(i, j): {
'meta': 'random string',
'walls': {
'E': True,
'O': False,
'N': True,
'S': True
}
} for i in range(11) for j in range(13)})
assert CustomValidator().validate(d)
Même chose avec isinstance prioritaire (testé avec Python 3.5)
class IsInstanceCustomMeta(type):
def __instancecheck__(self, instance):
return self.validate(instance)
def create_custom_isinstance_class(f):
class IsInstanceCustomClass(metaclass=IsInstanceCustomMeta):
validate = f
return IsInstanceCustomClass
def Mapping(**kwargs):
key_values = [KeyValue(k, v) for k, v in kwargs.items()]
def validate(to_validate):
if not isinstance(to_validate, dict):
return False
for validator in key_values:
if not isinstance(to_validate, validator):
return False
return True
return create_custom_isinstance_class(validate)
def KeyValue(key, value):
return create_custom_isinstance_class(lambda to_validate: key in to_validate and isinstance(to_validate[key], value))
def my_format_validation(to_validate):
if not isinstance(to_validate, Mapping(rows=int, cols=int)):
return False
element_validator = Mapping(meta=str, walls=Mapping(**{k: bool for k in "EONS"}))
for i in range(to_validate['rows']):
for j in range(to_validate['cols']):
if not isinstance(to_validate, KeyValue((i, j), element_validator)):
return False
return True
MyFormat = create_custom_isinstance_class(my_format_validation)
d = {
'rows': 11,
'cols': 13,
}
d.update({(i, j): {
'meta': 'random string',
'walls': {
'E': True,
'O': False,
'N': True,
'S': True
}
} for i in range(11) for j in range(13)})
assert isinstance(d, MyFormat)
Si votre format était plus simple, je serais d'accord avec les autres réponses/commentaires pour utiliser les bibliothèques de validation de schéma existantes, telles que schema et voluptuous . Mais, étant donné que vous devez vérifier un dictionnaire avec des clés de tuples et que les valeurs de ces tuples dépendent des valeurs des autres membres de votre dict, je pense que vous feriez mieux d'écrire votre propre validateur que d'essayer de convaincre un schéma pour s'adapter à votre format.
from itertools import product
def isvalid(d):
try:
for key in product(range(d['rows']), range(d['cols'])):
sub = d[key]
assert (isinstance(sub['meta'], str) and
all(isinstance(sub['walls'][c], bool)
for c in 'EONS'))
except (KeyError, TypeError, AssertionError):
return False
return True
Si la compatibilité avec Python 2 est importante ou s'il est nécessaire d'affirmer qu'il n'y a pas de clé supplémentaire, faites le moi savoir.