Подтвердить что ты не робот

Как элегантно проверить, имеет ли словарь определенную структуру?

У меня есть словарь со следующей структурой:

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}

Мне предлагается написать функцию, которая принимает произвольный объект в качестве аргумента и проверяет, имеет ли он эту структуру. Вот что я написал:

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

Хотя это работает, мне это совсем не нравится. Есть ли способ сделать это на Pythonic?

Мне разрешено использовать только стандартную библиотеку.

4b9b3361

Ответ 1

Во-первых, я думаю, что больше "Pythonic" может потребовать прощения, а не разрешения - проверить, когда вам нужно было свойство, имело ли это свойство свойство.

Но, с другой стороны, это не поможет, если вас попросили создать что-то, чтобы проверить его на правильность.:)

Итак, если вам нужно проверить, вы можете использовать что-то вроде библиотеки схем, чтобы определить, как должна выглядеть ваша структура данных, а затем проверить другие структуры данных в отношении этой схемы.

Ответ 2

В Python точная идентификация задействованных типов менее важна, чем то, как ведут себя значения. Для определенного использования такого объекта достаточно ли этого объекта? Это означает, что L не должен быть dict, он просто имеет поддержку __getitem__; L[(i,j)]['meta'] не должен быть str, он просто должен поддерживать преобразование в строку через str(L[(i,j)]['meta']); и др.

Учитывая, что релаксация, я просто попытаюсь поймать любые ошибки, возникшие при попытке таких действий, и возвратить False, если они произойдут. Например,

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

Учитывая, что любой объект имеет логическое значение, казалось бы бессмысленным попробовать bool(walls[direction]); скорее, если иметь ровно True или False, поскольку значение не является жестким требованием, я бы просто проверил значение в целом. Аналогичным образом, дополнительные стены могут быть или не быть проблемой, и их не нужно явно проверять.

Ответ 3

Вы можете составить такую ​​проверку (идея из экстракторов Scala). Преимущество состоит в том, что структура валидатора аналогична структуре для тестирования.

Недостатком является то, что многие вызовы функций могут сделать его намного медленнее.

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)

То же самое с переопределением isinstance (тестируется с 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)

Ответ 4

Если ваш формат был более простым, я бы согласился с другим ответом/комментариями использовать существующие библиотеки проверки схемы, такие как schema и voluptuous. Но, учитывая ваш конкретный случай проверки словаря с помощью ключей кортежа и значений этих кортежей в зависимости от значений других членов вашего dict, я думаю, вам лучше писать свой собственный валидатор, чем пытаться уговорить схему чтобы соответствовать вашему формату.

Ответ 5

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

Если совместимость Python 2 важна или необходимо утверждать, что дополнительных ключей нет, сообщите мне.