如何检查字典是否与 Python 中的另一个字典匹配?

问题描述

如何检查一个字典是否与另一个字典匹配?例如,给定 2 个字典

{
    name: "Alice",lives: {
        city: "Venice",since: 2000
    }
}

{
    name: "Bob",lives: {
        city: "Hong Kong"
    }
}

匹配

{
    name: "^A.*",<- a regexp
    lives: {
        since: "*" <- must have this key too
    }
}

函数将返回 True 或 False。

解决方法

一种解决方案是使用 validation 方法创建自定义类,并在构建匹配对象时为字段使用特定值:

from dataclasses import dataclass
import re
from typing import Optional

@dataclass
class City:
    name: str
    since: Optional[int] = None

    def __str__(self):
        if self.since is not None:
            return f"{self.name} since {self.since}"
        else:
            return f"{self.name}"

@dataclass
class Person:
    name: str
    lives: City

    def __str__(self):
        return f"{self.name} lives in {self.lives}"

    def validate(self,match_spec):
        valid = True

        # validate the name
        m = re.match(match_spec.name,self.name)
        if m is not None:
            print(f"Name {self.name} does not match {match_spec.name}")
            valid = False

        # check if the spec requires the year
        if match_spec.lives.since is not None:
            if self.lives.since is None:
                print(f"Missing year for {self.name}.")
                valid = False

        if valid:
            print("Correct validation")

alice = Person("Alice",City("Venice",2000))
print(alice)
bob = Person("Bob",City("Hong Kong"))
print(bob)

# we change the semantic of person.lives.since:
# a not None value means that we want to check that the field exists
match_spec_1 = Person("^A.*",City(".*",1))

print(f"\nValidate '{alice}' with '{match_spec_1}''")
alice.validate(match_spec_1)

print(f"\nValidate '{bob}' with '{match_spec_1}'")
bob.validate(match_spec_1)

match_spec_2 = Person("^B.*",City(".*"))

print(f"\nValidate '{alice}' with '{match_spec_2}'")
alice.validate(match_spec_2)

print(f"\nValidate '{bob}' with '{match_spec_2}'")
bob.validate(match_spec_2)

打印:

Alice lives in Venice since 2000
Bob lives in Hong Kong

Validate 'Alice lives in Venice since 2000' with '^A.* lives in .* since 1''
Correct validation

Validate 'Bob lives in Hong Kong' with '^A.* lives in .* since 1'
Name Bob does not match ^A.*
Missing year for Bob.

Validate 'Alice lives in Venice since 2000' with '^B.* lives in .*'
Name Alice does not match ^B.*

Validate 'Bob lives in Hong Kong' with '^B.* lives in .*'
Correct validation

您还可以轻松添加对城市名称等的检查。

干杯!

,

如果您想对规范​​进行硬编码,解决方案相当简单。

import re

def meets_spec(dic):
    # Here we just assume the pattern is constant
    pattern = re.compile(r'^A.*')
    # if the pattern has matches and the key 'since'
    # is in the 'lives' dict,return True
    if re.match(pattern,dic.get('name','')):
        # .get() is nice here because it doesn't raise an
        # error when the key doesn't exist. It just
        # returns False and moves on
        if 'since' in dic.get('lives',{}):
            return True
    return False

dict_1 = {
    'name': 'Alice','lives': {
        'city': 'Venice','since': 2000
    }
}
dict_2 = {
    'name': 'Bob','lives': {
        'city': 'Hong Kong'
    }
}
print(meets_spec(dict_1))
print(meets_spec(dict_2))

现在让我们假设您想要更加动态并拥有一个接受任何此类规范的函数。

import re

def meets_spec(dic,spec,required='*'):
    # by default we return True
    # and conditionally prove ourselves False
    result = True
    for k,v in spec.items():
        # this assumes an asterisk is a required item
        # but still allows what defines required to be changed
        if v == required:
            if k not in dic:
                result = False
        # it checks nested dicts recursively
        elif isinstance(v,dict):
            # .get() is used for the same reasons above.
            if not meets_spec(dic.get(k,{}),v):
                result = False
        # and it assumes anything that isn't an asterisk
        # or a nested dict is a regular expression.
        else:
            pattern = re.compile(v)
            if not re.match(pattern,dic.get(k,'')):
                result = False
    return result

dict_1 = {
    'name': 'Alice','lives': {
        'city': 'Hong Kong'
    }
}
spec = {
    'name': '^A.*','lives': {
        'since': '*'
    }
}
print(meets_spec(dict_1,spec))
print(meets_spec(dict_2,spec))
,
import re

a = {
    'name': "Alice",'lives': {
        'city': "Venice",'since': 2000
    }
}

b = {
    'name': "Bob",'lives': {
        'city': "Hong Kong"
    }
}

match_with = {
    'name': "^A.*",'lives': {
        'since': "*"
    }
}

matched = {
    'name': False,'since': False
}


def match_dict(dictionary,match_with):
    for k,v in dictionary.items():
        if k in match_with:
            if type(v) == dict:
                match_dict(v,match_with[k])
            if k == 'name':
                if re.match(match_with[k],dictionary[k]):
                    matched['name'] = True
                    continue
            elif k == 'since':
                matched['since'] = True

    if False in matched.values():
        return False
    else:
        return True

也许这会有所帮助